Esta actualización reorganiza el proyecto de reverse engineering de la API de ADIF con los siguientes cambios: Estructura del proyecto: - Movida documentación principal a carpeta docs/ - Consolidados archivos markdown redundantes en CLAUDE.md (contexto completo del proyecto) - Organización de tests en carpeta tests/ con README explicativo - APK renombrado de base.apk a adif.apk para mayor claridad Archivos de código: - Movidos adif_auth.py y adif_client.py a la raíz (antes en api_testing_scripts/) - Eliminados scripts de testing obsoletos y scripts de Frida no utilizados - Nuevos tests detallados: test_endpoints_detailed.py y test_onepaths_with_real_trains.py Descubrimientos: - Documentados nuevos hallazgos en docs/NEW_DISCOVERIES.md - Actualización de onePaths funcionando con commercialNumber real (devuelve 200) - Extraídos 1587 códigos de estación en station_codes.txt Configuración: - Actualizado .gitignore con mejores patrones para Python e IDEs - Eliminados archivos temporales de depuración y logs
509 lines
14 KiB
Markdown
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
|