Initial import of ADIF API reverse-engineering toolkit
This commit is contained in:
479
docs/API_REQUEST_BODIES.md
Normal file
479
docs/API_REQUEST_BODIES.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# Request Body Analysis - ADIF API
|
||||
|
||||
> Reverse engineering of package `com.adif.elcanomovil.serviceNetworking`
|
||||
> Date: 2025-12-04
|
||||
|
||||
## Table of Contents
|
||||
- [1. Authentication Headers](#1-authentication-headers)
|
||||
- [2. Request Bodies](#2-request-bodies)
|
||||
- [3. Endpoints and Base URLs](#3-endpoints-and-base-urls)
|
||||
- [4. Network Configuration](#4-network-configuration)
|
||||
- [5. Authentication System](#5-authentication-system)
|
||||
- [6. Code References](#6-code-references)
|
||||
|
||||
---
|
||||
|
||||
## 1. Authentication Headers
|
||||
|
||||
### 1.1 Static Headers
|
||||
|
||||
**File:** `ServicePaths.java:67-76`
|
||||
|
||||
#### Circulations
|
||||
```
|
||||
User-key: f4ce9fbfa9d721e39b8984805901b5df
|
||||
Content-Type: application/json;charset=utf-8
|
||||
```
|
||||
|
||||
#### Stations
|
||||
```
|
||||
User-key: 0d021447a2fd2ac64553674d5a0c1a6f
|
||||
Content-Type: application/json;charset=utf-8
|
||||
```
|
||||
|
||||
#### AVISA (Login/Refresh)
|
||||
```
|
||||
Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
|
||||
```
|
||||
|
||||
**Decoded (Base64):**
|
||||
```
|
||||
avista_client_android:8y[6J7!fJ<_*faq57#g9*!4Ipkc#X-AN
|
||||
```
|
||||
|
||||
### 1.2 Dynamic Headers (Generated by ElcanoAuth)
|
||||
|
||||
**File:** `serviceNetworking/interceptors/auth/ElcanoAuth.java`
|
||||
|
||||
The interceptor adds the X-Elcano headers produced by the HMAC calculation:
|
||||
|
||||
```
|
||||
X-Elcano-Host: <host>
|
||||
X-Elcano-Client: AndroidElcanoApp
|
||||
X-Elcano-Date: <timestamp>
|
||||
X-Elcano-UserId: <uuid>
|
||||
Authorization: HMAC-SHA256 Credential=... SignedHeaders=... Signature=...
|
||||
```
|
||||
|
||||
**Generation algorithm (summary):**
|
||||
- Canonical headers (order matters): `content-type`, `x-elcano-host`, `x-elcano-client`, `x-elcano-date`, `x-elcano-userid`
|
||||
- Canonical request: method + path + query + canonical headers + SHA256(payload)
|
||||
- String to sign: `HMAC-SHA256\n<timestamp>\n<date>/<client>/<userId>/elcano_request\n<hash>`
|
||||
- Signature key: HMAC(secretKey, date) → HMAC(result, client) → HMAC(result, "elcano_request")
|
||||
|
||||
---
|
||||
|
||||
## 2. Request Bodies
|
||||
|
||||
### 2.1 Circulations - Departures/Arrivals/Between Stations
|
||||
|
||||
**Endpoints:**
|
||||
- `/portroyalmanager/secure/circulationpaths/departures/traffictype/`
|
||||
- `/portroyalmanager/secure/circulationpaths/arrivals/traffictype/`
|
||||
- `/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/`
|
||||
|
||||
**Model:** `TrafficCirculationPathRequest`
|
||||
**File:** `circulations/model/request/TrafficCirculationPathRequest.java:10-212`
|
||||
|
||||
```json
|
||||
{
|
||||
"commercialService": "YES|NOT|BOTH",
|
||||
"commercialStopType": "YES|NOT|BOTH",
|
||||
"destinationStationCode": "string or null",
|
||||
"originStationCode": "string or null",
|
||||
"page": {
|
||||
"pageNumber": 0
|
||||
},
|
||||
"stationCode": "string or null",
|
||||
"trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL"
|
||||
}
|
||||
```
|
||||
|
||||
#### Real Example
|
||||
```json
|
||||
{
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"page": {
|
||||
"pageNumber": 0
|
||||
},
|
||||
"stationCode": "60000",
|
||||
"trafficType": "ALL"
|
||||
}
|
||||
```
|
||||
|
||||
#### Allowed Values
|
||||
|
||||
**commercialService / commercialStopType** (`CirculationPathRequest.java:65-67`):
|
||||
- `YES` - Only commercial services/stops
|
||||
- `NOT` - Without commercial services/stops
|
||||
- `BOTH` - All types
|
||||
|
||||
**trafficType** (`TrafficType.java:16-21`):
|
||||
- `CERCANIAS` - Commuter trains
|
||||
- `AVLDMD` - High speed long/medium distance
|
||||
- `OTHERS` - Other types
|
||||
- `TRAVELERS` - Passengers
|
||||
- `GOODS` - Freight
|
||||
- `ALL` - All types
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Circulations - Specific Routes
|
||||
|
||||
**Endpoints:**
|
||||
- `/portroyalmanager/secure/circulationpathdetails/onepaths/`
|
||||
- `/portroyalmanager/secure/circulationpathdetails/severalpaths/`
|
||||
|
||||
**Model:** `OneOrSeveralPathsRequest`
|
||||
**File:** `circulations/model/request/OneOrSeveralPathsRequest.java:11-140`
|
||||
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true/false/null,
|
||||
"commercialNumber": "string or null",
|
||||
"destinationStationCode": "string or null",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "string or null"
|
||||
}
|
||||
```
|
||||
|
||||
#### Real Example
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true,
|
||||
"commercialNumber": "04138",
|
||||
"destinationStationCode": "60000",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "71801"
|
||||
}
|
||||
```
|
||||
|
||||
**Important notes:**
|
||||
- `launchingDate` is a **millisecond** timestamp (Long in Java)
|
||||
- `allControlPoints` indicates whether to include every control point in the route
|
||||
- Optional fields should be omitted rather than set to `null`
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Train Compositions
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/circulationpaths/compositions/path/`
|
||||
|
||||
**Model:** `OneOrSeveralPathsRequest` (same as routes)
|
||||
**File:** `compositions/CompositionsService.java:14-18`
|
||||
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true/false/null,
|
||||
"commercialNumber": "string or null",
|
||||
"destinationStationCode": "string or null",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "string or null"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Stations - Single Station Details
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/stations/onestation/`
|
||||
|
||||
**Model:** `OneStationRequest`
|
||||
**File:** `stations/model/OneStationRequest.java:9-93`
|
||||
|
||||
```json
|
||||
{
|
||||
"detailedInfo": {
|
||||
"extendedStationInfo": true,
|
||||
"stationActivities": true,
|
||||
"stationBanner": true,
|
||||
"stationCommercialServices": true,
|
||||
"stationInfo": true,
|
||||
"stationServices": true,
|
||||
"stationTransportServices": true
|
||||
},
|
||||
"stationCode": "60000",
|
||||
"token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Important notes:**
|
||||
- The `detailedInfo` object controls what is returned
|
||||
- All boolean fields default to `true` (see `DetailedInfoDTO.java:149`)
|
||||
- `token` is required
|
||||
|
||||
#### DetailedInfo fields
|
||||
|
||||
**File:** `stations/model/DetailedInfoDTO.java:10-151`
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `extendedStationInfo` | boolean | Extended station information |
|
||||
| `stationActivities` | boolean | Station activities |
|
||||
| `stationBanner` | boolean | Banner/announcements |
|
||||
| `stationCommercialServices` | boolean | Commercial services |
|
||||
| `stationInfo` | boolean | Basic station information |
|
||||
| `stationServices` | boolean | Available services |
|
||||
| `stationTransportServices` | boolean | Transport services |
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Station Observations
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/stationsobservations/`
|
||||
|
||||
**Model:** `StationObservationsRequest`
|
||||
**File:** `stationObservations/model/StationObservationsRequest.java:10-53`
|
||||
|
||||
```json
|
||||
{
|
||||
"stationCodes": ["60000", "71801"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Real Example
|
||||
```json
|
||||
{
|
||||
"stationCodes": ["60000", "71801", "79600"]
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Array of station codes (strings)
|
||||
- Required field
|
||||
- Can contain multiple codes
|
||||
|
||||
---
|
||||
|
||||
## 3. Endpoints and Base URLs
|
||||
|
||||
### 3.1 Base URLs
|
||||
|
||||
**File:** `di/NetworkModule.java:73-159`
|
||||
|
||||
| Service | Base URL | Authentication |
|
||||
|---------|----------|----------------|
|
||||
| **Circulations** | `https://circulacion.api.adif.es` | Secured (with AuthHeaderInterceptor) |
|
||||
| **Stations** | `https://estaciones.api.adif.es` | Secured (with AuthHeaderInterceptor) |
|
||||
| **AVISA** | `https://avisa.adif.es` | Basic (without AuthHeaderInterceptor) |
|
||||
| **Elcano Web** | `https://elcanoweb.adif.es/api/` | - |
|
||||
|
||||
### 3.2 Full Paths - Stations
|
||||
|
||||
**File:** `ServicePaths.java:106-112`
|
||||
|
||||
```
|
||||
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
|
||||
POST /portroyalmanager/secure/stations/onestation/
|
||||
POST /portroyalmanager/secure/stationsobservations/
|
||||
```
|
||||
|
||||
### 3.3 Full Paths - Circulations
|
||||
|
||||
**File:** `ServicePaths.java:41-51`
|
||||
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
|
||||
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
|
||||
```
|
||||
|
||||
### 3.4 Full Paths - Compositions
|
||||
|
||||
**File:** `ServicePaths.java:55-61`
|
||||
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/compositions/path/
|
||||
```
|
||||
|
||||
### 3.5 Full Paths - AVISA
|
||||
|
||||
**Files:** `ServicePaths.java:82-92` and `ServicePaths.java:29-37`
|
||||
|
||||
```
|
||||
POST /avisa-ws/api/token (login)
|
||||
POST /avisa-ws/api/token (refresh)
|
||||
POST /avisa-ws/api/v1/client (register)
|
||||
GET /avisa-ws/api/v1/station (stations)
|
||||
GET /avisa-ws/api/v1/category (categories)
|
||||
GET /avisa-ws/api/v1/incidence (incidences list)
|
||||
GET /avisa-ws/api/v1/incidence/{id} (incidence details)
|
||||
POST /avisa-ws/api/v1/incidence (create incidence)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Network Configuration
|
||||
|
||||
### 4.1 OkHttpClient Configuration
|
||||
|
||||
**File:** `di/NetworkModule.java:100-132`
|
||||
|
||||
#### Basic client
|
||||
```kotlin
|
||||
OkHttpClient.Builder()
|
||||
.certificatePinner(certificatePinner)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
```
|
||||
|
||||
#### Secured client (with authentication)
|
||||
```kotlin
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(AuthHeaderInterceptor(userId))
|
||||
.certificatePinner(certificatePinner)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
```
|
||||
|
||||
**Timeouts:**
|
||||
- Connect timeout: 60 seconds
|
||||
- Read timeout: 60 seconds
|
||||
|
||||
### 4.2 Services using the secured client
|
||||
|
||||
**File:** `di/NetworkModule.java`
|
||||
|
||||
- `CirculationService` (line 73)
|
||||
- `StationsService` (line 142)
|
||||
- `StationObservationsService` (line 135)
|
||||
- `CompositionsService` (line 156)
|
||||
|
||||
### 4.3 Services using the basic client
|
||||
|
||||
- `AvisaLoginService` (line 50)
|
||||
- `AvisaStationsService` (line 57)
|
||||
- `IncidenceService` (line 80)
|
||||
- `SubscriptionsService` (line 149)
|
||||
|
||||
---
|
||||
|
||||
## 5. Authentication System
|
||||
|
||||
### 5.1 AuthHeaderInterceptor
|
||||
|
||||
**File:** `interceptors/AuthHeaderInterceptor.java:27-84`
|
||||
|
||||
This interceptor runs on **every** request for secured services.
|
||||
|
||||
#### Authentication process
|
||||
|
||||
1. **Generate persistent User ID**
|
||||
- Uses `GeneratePersistentUserIdUseCase`
|
||||
- The ID is stored and reused between sessions
|
||||
|
||||
2. **Build the token**
|
||||
```java
|
||||
ElcanoClientAuth.Builder()
|
||||
.host(request.url().host())
|
||||
.contentType("application/json;charset=utf-8")
|
||||
.path(request.url().encodedPath())
|
||||
.params(request.url().encodedQuery())
|
||||
.xElcanoClient("AndroidElcanoApp")
|
||||
.xElcanoUserId(userId)
|
||||
.httpMethodName(request.method())
|
||||
.payload(bodyJsonWithoutSpaces)
|
||||
.build()
|
||||
```
|
||||
|
||||
3. **Generate headers**
|
||||
- The `ElcanoClientAuth` object generates authentication headers
|
||||
- They are added automatically to the request
|
||||
|
||||
#### Generated headers
|
||||
|
||||
```
|
||||
X-CanalMovil-Authentication: <calculated_token>
|
||||
X-CanalMovil-deviceID: <device_id>
|
||||
X-CanalMovil-pushID: <push_id>
|
||||
```
|
||||
|
||||
### 5.2 GetKeysHelper class
|
||||
|
||||
**File:** `AuthHeaderInterceptor.java:44`
|
||||
|
||||
Provides keys for authentication:
|
||||
- `getKeysHelper.a()` - First key
|
||||
- `getKeysHelper.b()` - Second key
|
||||
|
||||
These keys are used by the signing/authentication algorithm.
|
||||
|
||||
### 5.3 Certificate Pinning
|
||||
|
||||
**File:** `di/NetworkModule.java:64-70`
|
||||
|
||||
The app uses **Certificate Pinning** to prevent MITM attacks:
|
||||
- Expected SSL certificates are in `PinningRepository`
|
||||
- Loaded asynchronously at startup
|
||||
- All requests validate the server certificate
|
||||
|
||||
---
|
||||
|
||||
## 6. Code References
|
||||
|
||||
### 6.1 Key Files
|
||||
|
||||
| File | Location | Description |
|
||||
|------|----------|-------------|
|
||||
| `ServicePaths.java` | `serviceNetworking/` | Paths and static headers |
|
||||
| `AuthHeaderInterceptor.java` | `serviceNetworking/interceptors/` | Auth header generation |
|
||||
| `NetworkModule.java` | `serviceNetworking/di/` | Retrofit/OkHttp configuration |
|
||||
| `CirculationService.java` | `serviceNetworking/circulations/` | Circulation API |
|
||||
| `StationsService.java` | `serviceNetworking/stations/` | Stations API |
|
||||
| `StationObservationsService.java` | `serviceNetworking/stationObservations/` | Observations API |
|
||||
| `CompositionsService.java` | `serviceNetworking/compositions/` | Compositions API |
|
||||
|
||||
### 6.2 Request Models
|
||||
|
||||
| Model | File | Usage |
|
||||
|-------|------|-------|
|
||||
| `TrafficCirculationPathRequest` | `circulations/model/request/` | Departures, Arrivals, BetweenStations |
|
||||
| `OneOrSeveralPathsRequest` | `circulations/model/request/` | OnePaths, SeveralPaths, Compositions |
|
||||
| `OneStationRequest` | `stations/model/` | Station details |
|
||||
| `DetailedInfoDTO` | `stations/model/` | Detailed info configuration |
|
||||
| `StationObservationsRequest` | `stationObservations/model/` | Station observations |
|
||||
|
||||
### 6.3 Important Code Lines
|
||||
|
||||
- Static headers: `ServicePaths.java:67-76`
|
||||
- Circulations user-key: `ServicePaths.java:67`
|
||||
- Stations user-key: `ServicePaths.java:68`
|
||||
- AVISA login token: `ServicePaths.java:70`
|
||||
- Auth interceptor: `AuthHeaderInterceptor.java:38-83`
|
||||
- Circulations base URL: `NetworkModule.java:76`
|
||||
- Stations base URL: `NetworkModule.java:145`
|
||||
- Enum TrafficType: `TrafficType.java:16-21`
|
||||
- Enum State: `CirculationPathRequest.java:65-67`
|
||||
|
||||
---
|
||||
|
||||
## 7. Additional Notes
|
||||
|
||||
### 7.1 JSON Serialization
|
||||
|
||||
- **Library used:** Moshi (configured in `NetworkModule.java:87-96`)
|
||||
- **Format:** JSON field names match Java property names exactly
|
||||
- **Null handling:** Optional fields are **omitted** by the client; sending explicit `null` values leads to 401 responses
|
||||
- **Date format:** Millisecond timestamps (Long)
|
||||
|
||||
### 7.2 Security Considerations
|
||||
|
||||
1. **Hardcoded user-keys:** API keys are in the code (easy to extract)
|
||||
2. **Certificate Pinning:** Makes proxy interception harder
|
||||
3. **Dynamic authentication:** X-Elcano headers require the HMAC algorithm to be reproduced
|
||||
4. **AVISA token:** Base64 credentials in code (decodable)
|
||||
|
||||
### 7.3 Testing
|
||||
|
||||
Use the Python client (`adif_client.py`) or the signed test scripts in `/tests` to validate each endpoint. All endpoints are currently reproducible with the shipped keys and HMAC implementation.
|
||||
|
||||
---
|
||||
|
||||
**Last update:** 2025-12-05
|
||||
**Source:** Decompiled ADIF El Cano Movil APK
|
||||
**Tools:** JADX, Ghidra, manual Java code analysis
|
||||
Reference in New Issue
Block a user