Files
adif-api-reverse-engineering/docs/API_REQUEST_BODIES.md
Dasemu 68fac80520 Refactor: reorganización completa del proyecto y documentación consolidada
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
2025-12-05 11:22:13 +01:00

14 KiB

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.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

{
  "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

{
  "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

{
  "allControlPoints": true/false/null,
  "commercialNumber": "string o null",
  "destinationStationCode": "string o null",
  "launchingDate": 1733356800000,
  "originStationCode": "string o null"
}

Ejemplo Real

{
  "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

{
  "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

{
  "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

{
  "stationCodes": ["60000", "71801"]
}

Ejemplo Real

{
  "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

OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .connectTimeout(60, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .build()

Cliente Securizado (con autenticación)

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

    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