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
14 KiB
Análisis de Request Bodies - API ADIF
Ingeniería reversa del paquete
com.adif.elcanomovil.serviceNetworkingFecha: 2025-12-04
Tabla de Contenidos
- 1. Headers de Autenticación
- 2. Request Bodies
- 3. Endpoints y URLs Base
- 4. Configuración de Red
- 5. Sistema de Autenticación
- 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
{
"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 comercialesNOT- Sin servicios/paradas comercialesBOTH- Todos los tipos
trafficType (TrafficType.java:16-21):
CERCANIAS- Trenes de cercaníasAVLDMD- Alta velocidad larga y media distanciaOTHERS- Otros tiposTRAVELERS- ViajerosGOODS- MercancíasALL- 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:
launchingDatees 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
detailedInfocontrola qué información se devuelve en la respuesta - Todos los campos booleanos por defecto son
true(verDetailedInfoDTO.java:149) - El
tokenes 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
-
Generación de User ID Persistente
- Usa
GeneratePersistentUserIdUseCase - El ID se guarda y reutiliza entre sesiones
- Usa
-
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() -
Generación de Headers
- El objeto
ElcanoClientAuthgenera headers de autenticación - Se añaden automáticamente a la petición
- El objeto
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 clavegetKeysHelper.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
- User-keys hardcodeadas: Las claves API están en el código (fáciles de extraer)
- Certificate Pinning: Dificulta interceptar tráfico con proxy
- Autenticación dinámica: Los headers X-CanalMovil requieren conocer el algoritmo
- AVISA token: Credenciales Base64 en el código (pueden decodificarse)
7.3 Testing
Para probar estos endpoints:
-
Extraer el algoritmo de autenticación:
- Analizar clase
ElcanoClientAuth(no incluida en estos archivos) - O bien, usar Frida para hookear y capturar headers generados
- Analizar clase
-
Bypass Certificate Pinning:
- Usar Frida con script de bypass SSL pinning
- O modificar el APK para deshabilitar pinning
-
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