Files
adif-api-reverse-engineering/API_REQUEST_BODIES.md

509 lines
14 KiB
Markdown

# Análisis de Request Bodies - API ADIF
> Ingeniería reversa del paquete `com.adif.elcanomovil.serviceNetworking`
>
> Fecha: 2025-12-04
## Tabla de Contenidos
- [1. Headers de Autenticación](#1-headers-de-autenticación)
- [2. Request Bodies](#2-request-bodies)
- [3. Endpoints y URLs Base](#3-endpoints-y-urls-base)
- [4. Configuración de Red](#4-configuración-de-red)
- [5. Sistema de Autenticación](#5-sistema-de-autenticación)
- [6. Referencias de Código](#6-referencias-de-código)
---
## 1. Headers de Autenticación
### 1.1 Headers Estáticos
**Archivo:** `ServicePaths.java:67-76`
#### Para Circulaciones
```
User-key: f4ce9fbfa9d721e39b8984805901b5df
Content-Type: application/json;charset=utf-8
```
#### Para Estaciones
```
User-key: 0d021447a2fd2ac64553674d5a0c1a6f
Content-Type: application/json;charset=utf-8
```
#### Para AVISA (Login/Refresh)
```
Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
```
**Decodificado (Base64):**
```
avista_client_android:8y[6J7!fJ<_*faq57#g9*!4Ipkc#X-AN
```
### 1.2 Headers Dinámicos (Generados por AuthHeaderInterceptor)
**Archivo:** `AuthHeaderInterceptor.java:38-83`
La aplicación genera automáticamente estos headers adicionales:
```
X-CanalMovil-Authentication: <token_generado>
X-CanalMovil-deviceID: <device_id>
X-CanalMovil-pushID: <push_id>
```
**Algoritmo de generación:**
El token se calcula usando la clase `ElcanoClientAuth` con:
- Host del servidor
- Path completo de la URL
- Parámetros de query
- Método HTTP (GET/POST)
- Payload (body serializado sin espacios)
- ID de usuario persistente
- Cliente: "AndroidElcanoApp"
---
## 2. Request Bodies
### 2.1 Circulaciones - Salidas/Llegadas/Entre Estaciones
**Endpoints:**
- `/portroyalmanager/secure/circulationpaths/departures/traffictype/`
- `/portroyalmanager/secure/circulationpaths/arrivals/traffictype/`
- `/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/`
**Modelo:** `TrafficCirculationPathRequest`
**Archivo:** `circulations/model/request/TrafficCirculationPathRequest.java:10-212`
```json
{
"commercialService": "YES|NOT|BOTH",
"commercialStopType": "YES|NOT|BOTH",
"destinationStationCode": "string o null",
"originStationCode": "string o null",
"page": {
"pageNumber": 0
},
"stationCode": "string o null",
"trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL"
}
```
#### Ejemplo Real
```json
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": null,
"originStationCode": null,
"page": {
"pageNumber": 0
},
"stationCode": "60000",
"trafficType": "ALL"
}
```
#### Valores Permitidos
**commercialService / commercialStopType** (`CirculationPathRequest.java:65-67`):
- `YES` - Solo servicios/paradas comerciales
- `NOT` - Sin servicios/paradas comerciales
- `BOTH` - Todos los tipos
**trafficType** (`TrafficType.java:16-21`):
- `CERCANIAS` - Trenes de cercanías
- `AVLDMD` - Alta velocidad larga y media distancia
- `OTHERS` - Otros tipos
- `TRAVELERS` - Viajeros
- `GOODS` - Mercancías
- `ALL` - Todos los tipos
---
### 2.2 Circulaciones - Rutas Específicas
**Endpoints:**
- `/portroyalmanager/secure/circulationpathdetails/onepaths/`
- `/portroyalmanager/secure/circulationpathdetails/severalpaths/`
**Modelo:** `OneOrSeveralPathsRequest`
**Archivo:** `circulations/model/request/OneOrSeveralPathsRequest.java:11-140`
```json
{
"allControlPoints": true/false/null,
"commercialNumber": "string o null",
"destinationStationCode": "string o null",
"launchingDate": 1733356800000,
"originStationCode": "string o null"
}
```
#### Ejemplo Real
```json
{
"allControlPoints": true,
"commercialNumber": "04138",
"destinationStationCode": "60000",
"launchingDate": 1733356800000,
"originStationCode": "71801"
}
```
**Notas importantes:**
- `launchingDate` es un timestamp en **milisegundos** (tipo Long en Java)
- `allControlPoints`: indica si se quieren todos los puntos de control de la ruta
- Todos los campos son opcionales (pueden ser null)
---
### 2.3 Composiciones de Trenes
**Endpoint:** `/portroyalmanager/secure/circulationpaths/compositions/path/`
**Modelo:** `OneOrSeveralPathsRequest` (mismo que rutas)
**Archivo:** `compositions/CompositionsService.java:14-18`
```json
{
"allControlPoints": true/false/null,
"commercialNumber": "string o null",
"destinationStationCode": "string o null",
"launchingDate": 1733356800000,
"originStationCode": "string o null"
}
```
---
### 2.4 Estaciones - Detalles de una Estación
**Endpoint:** `/portroyalmanager/secure/stations/onestation/`
**Modelo:** `OneStationRequest`
**Archivo:** `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"
}
```
**Notas importantes:**
- El objeto `detailedInfo` controla qué información se devuelve en la respuesta
- Todos los campos booleanos por defecto son `true` (ver `DetailedInfoDTO.java:149`)
- El `token` es requerido
#### Campos de DetailedInfo
**Archivo:** `stations/model/DetailedInfoDTO.java:10-151`
| Campo | Tipo | Descripción |
|-------|------|-------------|
| `extendedStationInfo` | boolean | Información extendida de la estación |
| `stationActivities` | boolean | Actividades de la estación |
| `stationBanner` | boolean | Banner/anuncios de la estación |
| `stationCommercialServices` | boolean | Servicios comerciales |
| `stationInfo` | boolean | Información básica |
| `stationServices` | boolean | Servicios disponibles |
| `stationTransportServices` | boolean | Servicios de transporte |
---
### 2.5 Observaciones de Estaciones
**Endpoint:** `/portroyalmanager/secure/stationsobservations/`
**Modelo:** `StationObservationsRequest`
**Archivo:** `stationObservations/model/StationObservationsRequest.java:10-53`
```json
{
"stationCodes": ["60000", "71801"]
}
```
#### Ejemplo Real
```json
{
"stationCodes": ["60000", "71801", "79600"]
}
```
**Notas:**
- Array de códigos de estación (strings)
- Campo requerido
- Puede contener múltiples códigos
---
## 3. Endpoints y URLs Base
### 3.1 URLs Base
**Archivo:** `di/NetworkModule.java:73-159`
| Servicio | URL Base | Autenticación |
|----------|----------|---------------|
| **Circulaciones** | `https://circulacion.api.adif.es` | Securizada (con AuthHeaderInterceptor) |
| **Estaciones** | `https://estaciones.api.adif.es` | Securizada (con AuthHeaderInterceptor) |
| **AVISA** | `https://avisa.adif.es` | Básica (sin AuthHeaderInterceptor) |
| **Elcano Web** | `https://elcanoweb.adif.es/api/` | - |
### 3.2 Paths Completos - Estaciones
**Archivo:** `ServicePaths.java:106-112`
```
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
POST /portroyalmanager/secure/stations/onestation/
POST /portroyalmanager/secure/stationsobservations/
```
### 3.3 Paths Completos - Circulaciones
**Archivo:** `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 Paths Completos - Composiciones
**Archivo:** `ServicePaths.java:55-61`
```
POST /portroyalmanager/secure/circulationpaths/compositions/path/
```
### 3.5 Paths Completos - AVISA
**Archivo:** `ServicePaths.java:82-92` y `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. Configuración de Red
### 4.1 Configuración de OkHttpClient
**Archivo:** `di/NetworkModule.java:100-132`
#### Cliente Básico
```kotlin
OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()
```
#### Cliente Securizado (con autenticación)
```kotlin
OkHttpClient.Builder()
.addInterceptor(AuthHeaderInterceptor(userId))
.certificatePinner(certificatePinner)
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()
```
**Timeouts:**
- Connect timeout: 60 segundos
- Read timeout: 60 segundos
### 4.2 Servicios que Usan Cliente Securizado
**Archivo:** `di/NetworkModule.java`
- `CirculationService` (línea 73)
- `StationsService` (línea 142)
- `StationObservationsService` (línea 135)
- `CompositionsService` (línea 156)
### 4.3 Servicios que Usan Cliente Básico
- `AvisaLoginService` (línea 50)
- `AvisaStationsService` (línea 57)
- `IncidenceService` (línea 80)
- `SubscriptionsService` (línea 149)
---
## 5. Sistema de Autenticación
### 5.1 AuthHeaderInterceptor
**Archivo:** `interceptors/AuthHeaderInterceptor.java:27-84`
Este interceptor se ejecuta en **todas** las peticiones de los servicios securizados.
#### Proceso de Autenticación
1. **Generación de User ID Persistente**
- Usa `GeneratePersistentUserIdUseCase`
- El ID se guarda y reutiliza entre sesiones
2. **Construcción del 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. **Generación de Headers**
- El objeto `ElcanoClientAuth` genera headers de autenticación
- Se añaden automáticamente a la petición
#### Headers Generados
```
X-CanalMovil-Authentication: <token_calculado>
X-CanalMovil-deviceID: <device_id>
X-CanalMovil-pushID: <push_id>
```
### 5.2 Clase GetKeysHelper
**Archivo:** `AuthHeaderInterceptor.java:44`
Proporciona claves para la autenticación:
- `getKeysHelper.a()` - Primera clave
- `getKeysHelper.b()` - Segunda clave
Estas claves se usan en el algoritmo de firma/autenticación.
### 5.3 Certificate Pinning
**Archivo:** `di/NetworkModule.java:64-70`
La aplicación usa **Certificate Pinning** para prevenir ataques MITM:
- Los certificados SSL esperados están en `PinningRepository`
- Se cargan de forma asíncrona al inicio
- Todas las peticiones verifican el certificado del servidor
---
## 6. Referencias de Código
### 6.1 Archivos Clave
| Archivo | Ubicación | Descripción |
|---------|-----------|-------------|
| `ServicePaths.java` | `serviceNetworking/` | Paths y headers estáticos |
| `AuthHeaderInterceptor.java` | `serviceNetworking/interceptors/` | Generación de auth headers |
| `NetworkModule.java` | `serviceNetworking/di/` | Configuración Retrofit/OkHttp |
| `CirculationService.java` | `serviceNetworking/circulations/` | API de circulaciones |
| `StationsService.java` | `serviceNetworking/stations/` | API de estaciones |
| `StationObservationsService.java` | `serviceNetworking/stationObservations/` | API de observaciones |
| `CompositionsService.java` | `serviceNetworking/compositions/` | API de composiciones |
### 6.2 Modelos de Request
| Modelo | Archivo | Uso |
|--------|---------|-----|
| `TrafficCirculationPathRequest` | `circulations/model/request/` | Departures, Arrivals, BetweenStations |
| `OneOrSeveralPathsRequest` | `circulations/model/request/` | OnePaths, SeveralPaths, Compositions |
| `OneStationRequest` | `stations/model/` | Detalles de estación |
| `DetailedInfoDTO` | `stations/model/` | Configuración de info detallada |
| `StationObservationsRequest` | `stationObservations/model/` | Observaciones de estaciones |
### 6.3 Líneas de Código Importantes
- Headers estáticos: `ServicePaths.java:67-76`
- User-key circulaciones: `ServicePaths.java:67`
- User-key estaciones: `ServicePaths.java:68`
- AVISA login token: `ServicePaths.java:70`
- Auth interceptor: `AuthHeaderInterceptor.java:38-83`
- Base URL circulaciones: `NetworkModule.java:76`
- Base URL estaciones: `NetworkModule.java:145`
- Enum TrafficType: `TrafficType.java:16-21`
- Enum State: `CirculationPathRequest.java:65-67`
---
## 7. Notas Adicionales
### 7.1 Serialización JSON
- **Biblioteca usada:** Moshi (configurado en `NetworkModule.java:87-96`)
- **Formato:** Los nombres de campos en JSON coinciden exactamente con los nombres de propiedades en Java
- **Null handling:** Los campos null se incluyen en el JSON
- **Formato de fecha:** Timestamps en milisegundos (Long)
### 7.2 Consideraciones de Seguridad
1. **User-keys hardcodeadas:** Las claves API están en el código (fáciles de extraer)
2. **Certificate Pinning:** Dificulta interceptar tráfico con proxy
3. **Autenticación dinámica:** Los headers X-CanalMovil requieren conocer el algoritmo
4. **AVISA token:** Credenciales Base64 en el código (pueden decodificarse)
### 7.3 Testing
Para probar estos endpoints:
1. **Extraer el algoritmo de autenticación:**
- Analizar clase `ElcanoClientAuth` (no incluida en estos archivos)
- O bien, usar Frida para hookear y capturar headers generados
2. **Bypass Certificate Pinning:**
- Usar Frida con script de bypass SSL pinning
- O modificar el APK para deshabilitar pinning
3. **Interceptar tráfico:**
- mitmproxy con Frida
- Burp Suite con Frida
- Captura directa con tcpdump/Wireshark
---
## 8. Próximos Pasos
- [ ] Extraer y analizar clase `ElcanoClientAuth`
- [ ] Reverse engineering del algoritmo de firma
- [ ] Capturar tráfico real con Frida
- [ ] Implementar generador de headers de autenticación
- [ ] Probar endpoints con Postman/curl
- [ ] Documentar respuestas de cada endpoint
---
**Última actualización:** 2025-12-04
**Fuente:** APK decompilado de ADIF El Cano Móvil
**Herramientas:** JADX, análisis manual de código Java