# 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: X-CanalMovil-deviceID: X-CanalMovil-pushID: ``` **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: X-CanalMovil-deviceID: X-CanalMovil-pushID: ``` ### 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