Verificador de Galileo OSNMA en el lado del receptor
Un verificador en el lado del receptor para la autenticación del mensaje de navegación del servicio abierto de Galileo (OSNMA), en Rust puro. Lee páginas I/NAV e informa, por satélite y por época, de si los datos de navegación están autenticados criptográficamente, y de por qué falló una comprobación.
De carácter consultivo. Esto es una salida de análisis y monitorización, no un equipo de navegación homologado. El estado de autenticación que se informa aquí no es un servicio de integridad de la navegación y no debe ser la única base de ninguna decisión de navegación o de seguridad.
AVISO: propiedad intelectual de la interfaz Galileo OSNMA#
Kshana no concede ningún derecho sobre la propiedad intelectual de la Unión Europea en el Documento de Control de Interfaz de la señal en el espacio de Galileo OSNMA (los «ICD IPRs»). El ICD lleva su propia autorización (su Anexo E), que es no exclusiva y libre de regalías y tiene sus propios términos, alcance y límites. Quien use, construya o distribuya software basado en este módulo se apoya directamente en esa autorización y debe leer y aceptar por sí mismo sus términos. La licencia de Kshana (AGPL-3.0-only, o la licencia comercial) cubre únicamente el código propio de Kshana.
Este módulo implementa los algoritmos que describen los documentos siguientes. El texto del ICD no se reproduce; los valores numéricos (disposiciones de campos, enumeraciones, la tabla de consulta de MAC) se reexpresan en la notación propia de Kshana, con la fuente citada en el código. El ICD y las Receiver Guidelines son © Unión Europea; aquí se reconoce la fuente. Kshana no ha sido desarrollado por la Unión Europea, la Comisión Europea, EUSPA ni ningún otro organismo, ni es utilizado, aprobado o respaldado por ellos.
Documentos de referencia#
Publicados por el European GNSS Service Centre.
| Documento | Versión | Fecha |
|---|---|---|
| Galileo OSNMA Signal-in-Space ICD («SIS ICD») | Edición 1,1 | octubre de 2023 |
| Galileo OSNMA Receiver Guidelines | Edición 1,3 | enero de 2024 |
Los números de sección y de tabla citados en el código y aquí se refieren a esas versiones.
Qué se verifica#
La cadena de confianza, desde la raíz hasta los datos:
- Clave pública (ICD 3.2.2, 6,2). Un DSM-PKR se reensambla a partir de sus bloques y se recorre su camino de Merkle hasta una raíz de Merkle de confianza (SHA-256).
- KROOT (ICD 3.2.3, 6,3). Se reensambla un DSM-KROOT; su firma ECDSA (P-256/SHA-256 o P-521/SHA-512) se comprueba frente a la clave pública que nombra, sobre la cabecera NMA y los parámetros de la cadena.
- Claves TESLA (ICD 5,5, 6,4). Cada clave de un mensaje MACK se recorre hacia atrás hasta la KROOT o hasta una clave verificada anterior, con la función hash de la cadena.
- MACSEQ (ICD 4.1.2, 6,6). Los campos flexibles Tag-Info se comprueban frente al MACSEQ con la clave que abre ese MACK.
- Etiquetas (ICD 4,2, 6,7). Cada etiqueta se recalcula (HMAC-SHA-256 o CMAC-AES, truncada a la longitud de etiqueta de la cadena) sobre las identidades de los satélites, la hora de la subtrama, la posición de la etiqueta, el estado NMA y los datos de navegación que cubre, y se compara. Se tratan ADKD 0 (efemérides, reloj y estado), ADKD 4 (parámetros de temporización) y ADKD 12 (MAC lenta, clave publicada diez subtramas después), con autenticación cruzada de satélites que no llevan OSNMA por sí mismos, y etiquetas ficticias (COP 0).
Una etiqueta que nombra un valor reservado, o cuyo ADKD no coincide con la ranura de la tabla de consulta de MAC, se aparta (las Receiver Guidelines indican descartarla) y se informa como discarded, no como fallo.
Las páginas se comprueban frente a su CRC (el CRC-24Q de Galileo) antes que nada; una página que falla se descarta y se informa de ello (bad_crc).
Valores de estado#
Por etiqueta: authenticated, failed con un motivo (tag_mismatch, macseq_mismatch, key_chain_mismatch, key_conflict), pending con un motivo (no_chain, awaiting_key, no_nav_data, unknown_maclt, unsupported_function, service_not_usable, chain_mismatch), o discarded. Una etiqueta ficticia (COP 0) aparece como authenticated a nivel de etiqueta, ya que demuestra la clave, pero no cubre ningún dato de navegación y nunca cuenta para un satélite.
Por satélite, en la forma que consume el monitor receiver-trust:
Authenticatedsolo mientras los datos de efemérides y de reloj verificados más recientes (ADKD 0 o- de ese satélite sean recientes (dentro de 600 s de la hora del verificador por defecto) y sigan siendo los datos en uso. La comprobación es sobre los datos, no sobre el IODnav: el verificador conserva el SHA-256 de la cadena de bits autenticada (los Word Types 1 a 5 tal como los cubre ADKD 0) y de la cadena más reciente de ese tipo que envía el satélite, y deben ser iguales. Un IODnav copiado sobre otras palabras, o bits alterados del Word Type 5 (que no llevan IODnav), se lee como
Unavailablehasta que los nuevos datos se autentican. Una subtrama que lleva palabras de efemérides pero no un conjunto completo de Word Types 1 a 5 bajo un mismo IODnav retira el estado de la misma manera (toda subtrama E1-B genuina lleva las cinco); una sin ninguna palabra de efemérides no dice nada. Las etiquetas de otro satélite (autenticación cruzada) cuentan para el satélite cuyos datos cubren.
- de ese satélite sean recientes (dentro de 600 s de la hora del verificador por defecto) y sigan siendo los datos en uso. La comprobación es sobre los datos, no sobre el IODnav: el verificador conserva el SHA-256 de la cadena de bits autenticada (los Word Types 1 a 5 tal como los cubre ADKD 0) y de la cadena más reciente de ese tipo que envía el satélite, y deben ser iguales. Un IODnav copiado sobre otras palabras, o bits alterados del Word Type 5 (que no llevan IODnav), se lee como
Failedcuando una comprobación sobre los datos del satélite falló dentro de la memoria de fallos (600 s por defecto de la hora del verificador), aunque datos posteriores se verifiquen. Una discrepancia se imputa al satélite cuyos datos cubre la etiqueta y al satélite que transmitió la etiqueta, ya que cualquiera de los dos podría tener la culpa; una discrepancia de MACSEQ se imputa al satélite transmisor.Unavailableen caso contrario, y para todos los satélites tras un mensaje de alerta verificado, con un estado NMA «don't use» o un estado reservado, y hasta que se haya verificado la primera clave.
El valor global es Failed si algún satélite falló, Authenticated si al menos uno está autenticado y ninguno falló, y en otro caso Unavailable. $PKSOS,<A|F|N>[,<sat>:<A|F|N>...] es la sentencia correspondiente; kshana::osnma::pksos_sentence la construye.
Hora#
La cadena de claves muestra que una clave pertenece a una subtrama dada, no que la subtrama sea la actual. Por ello el verificador nunca toma el «ahora» de una página sin verificar. Su hora es la posterior entre la hora de referencia dada con set_reference_time (--reference-time) y la subtrama de la última clave TESLA verificada (una clave no puede conocerse antes de su ranura, así que demuestra que la hora no es anterior). La memoria de fallos, la ventana de validez de una autenticación y la expulsión de datos antiguos funcionan con esa hora. Una subtrama con sello de más de tres subtramas posterior a la última clave verificada se aparta salvo que su propia clave demuestre su hora; los mensajes de clave y de alerta que contiene se siguen procesando, ya que llevan su propia prueba (una firma o la raíz de Merkle).
El reloj se detiene cuando OSNMA se detiene, ya que solo las claves lo mueven. Quien llama en vivo debe llamar a set_reference_time con la hora del host en cada subtrama; la autenticación caduca entonces por sí sola, y poll_clock (que también se ejecuta con cada subtrama) devuelve un evento KeyStall cuando ninguna clave se ha verificado durante más tiempo que la ventana de autenticación.
Lo que esto no da es frescura. Sin una hora de referencia de una fuente que un atacante no pueda mover, una repetición de datos auténticos antiguos con sellos antiguos se autentica y los sellos de las páginas valen solo lo que valga su fuente: la CLI lo indica (freshness: NOT enforced) siempre que no se da hora de referencia. Con una hora de referencia la tolerancia es inferior a una subtrama (por defecto 29 s; los valores mayores se rechazan). Una hora de referencia es un único instante: las subtramas más alejadas de ella que la tolerancia se apartan y se cuentan, de modo que sirve para datos en vivo o recién grabados, no para una grabación larga. Para una grabación, omita la hora de referencia y lea el resultado como «auténtico, no necesariamente fresco». Las Receiver Guidelines dan el requisito formal.
Los sellos de página de un flujo u-blox proceden de los propios datos sin autenticar (véase Entrada), así que tampoco ofrecen protección de frescura.
Cadenas, claves y alertas#
Las cadenas verificadas se conservan por identificador de cadena (CID); las etiquetas se comprueban con la cadena que nombra la cabecera NMA, y una cabecera que nombra una cadena que no se verificó deja sin comprobar las etiquetas de la subtrama (chain_mismatch). Un segundo KROOT de una cadena en vigor añade un ancla; un KROOT de otra cadena se conserva junto a ella, de modo que anunciar de nuevo la cadena antigua tras una renovación no deshace la nueva.
La cabecera NMA no está autenticada, así que lo que anuncia cuenta solo cuando se confirma: cuando más de un satélite envía la misma cabecera en una subtrama, o cuando el mensaje firmado de un KROOT verificado la lleva (la cabecera forma parte de ese mensaje). Ambas confirmaciones no son iguales. La coincidencia de más de un satélite protege contra una anomalía de un solo satélite, no contra un suplantador: un suplantador puede falsificar dos identificadores de satélite. Lo que eso le da es denegación de servicio, y nada más, ya que un «don't use» confirmado borra los datos autenticados de todos los satélites: levantarlo, falsificado o genuino, no restablece nada hasta que nuevas etiquetas se verifican bajo subtramas operativas genuinas. (Antes de ese borrado, un levantamiento falsificado podía devolver, durante hasta la ventana de autenticación, datos que ya se habían autenticado.) La confirmación real es la cabecera firmada por el KROOT, que también levanta de nuevo un «don't use» cuando lleva un estado operativo. La cabecera de un solo satélite no cambia nada salvo la comprobación de etiquetas de su propia subtrama. Un KROOT cuyo tiempo de aplicabilidad no es posterior al de la cadena que se tiene para el mismo identificador (una repetición de uno más antiguo) se rechaza (stale); un KROOT de la misma cadena añade un ancla.
El estado y el valor CPKS de la cabecera confirmada dirigen la revocación. Con estado NMA «don't use», CPKS «chain revoked» elimina la cadena que nombra la cabecera y rechaza su KROOT a partir de entonces; CPKS «public key revoked» elimina la clave pública que firmó esa cadena, y toda cadena firmada con ella. Si la cadena de sustitución para ese identificador se adoptó antes de confirmarse la cabecera, por mucho tiempo que antes fuera, la clave revocada es la de la cadena anterior, no la nueva; esa memoria se conserva hasta que se lleva a cabo la revocación o se confirma la cabecera de otro procedimiento, y una cabecera de revocación repetida en cada subtrama se lleva a cabo una sola vez. Los mismos valores CPKS con estado operativo marcan la transición a la sustitución y no revocan nada. La cabecera no está autenticada en sí misma, de modo que una revocación falsificada solo puede quitar autenticación, y es definitiva hasta que se reinicia el verificador. Esa definitividad abarca también la clave vigente: una revocación falsificada de la clave pública restablece la memoria de la clave sustituida y revoca la clave en uso, y solo un reinicio lo deshace.
Un DSM-PKR con NPKT 4 es el mensaje de alerta; se verifica a través del árbol de Merkle como una clave, y su campo aleatorio NPK forma parte de la hoja. Una alerta verificada borra todas las cadenas y claves públicas, rechaza todo KROOT y clave pública posteriores, y deja todos los satélites en Unavailable, como indican las Receiver Guidelines, hasta que se reinicia el verificador.
El campo de relleno de un DSM-KROOT es un hash del mensaje firmado y de la firma. Se comprueba antes que la firma, y también indica con qué cabecera NMA se construyó el mensaje firmado cuando la cabecera cambió mientras se ensamblaba el mensaje.
Lo que no se verifica, o no se ejercita#
- Frescura. Véase Hora más arriba: necesita una hora de referencia externa.
- Renovación y revocación. Las cadenas y las claves se siguen en la medida en que las ejercitan los flujos de los vectores publicados (renovación, fin de cadena, revocación de cadena y de clave, nueva clave pública, nuevo árbol de Merkle, alerta), no todos los casos que permite el ICD. Una revocación anunciada por la cabecera sin autenticar se acepta tal cual y es definitiva para la sesión. Un nuevo árbol de Merkle solo se recoge a través de la raíz de Merkle que se da al verificador.
- Hash del árbol de Merkle. Los mensajes DSM-PKR se comprueban con un árbol SHA-256. Un árbol SHA3-256, que el ICD permite en el futuro, no se admite. La función hash de la cadena puede ser SHA-256 o SHA3-256.
- Listas de estado de claves. La revocación se sigue a través de la cabecera NMA y de los mensajes del flujo; no se leen las listas de revocación de certificados.
- Cobertura de datos autenticados. ADKD 4 con COP 1 enviado en una subtrama impar no puede autenticar (el Word Type 10 puede enviarse una o dos subtramas antes); esto cuesta solo disponibilidad, nunca corrección.
- Operación en tiempo constante. No se proporciona ni hace falta: el verificador no guarda secretos (solo claves públicas, claves de cadena publicadas y datos recibidos).
- Calidad de la señal y de los datos. Las páginas se comprueban con CRC y las páginas de alerta se descartan, pero no se sabe nada de la señal por la que llegaron. No se usa la recuperación Reed-Solomon de las palabras I/NAV 1 a 4: los datos deben recibirse directamente.
- No es un servicio de integridad de la posición. La autenticación dice que los datos vinieron del sistema. No dice nada de la señal por la que llegaron ni de la calidad de la posición.
Entrada#
- Formato de página simple. Un par de páginas por línea:
<svid> <gst_seconds> <60 hex digits>, se admiten comentarios#.gst_secondses el GST en segundos (week * 604800 + time of week) al inicio de la página; el hexadecimal son los 240 bits de las mitades par e impar (colas incluidas), con el bit más significativo primero. - CSV de vectores de prueba. La disposición de los vectores publicados (
SVID,NumNavBits,NavBitsHEX). La hora de inicio procede de--start-gsto de un nombre de archivo con fecha. - u-blox UBX-RXM-SFRBX. Un flujo de bytes UBX sin procesar (
--format ubx, detectado automáticamente). Los mensajes I/NAV E1-B de Galileo se reconstruyen en pares de páginas (src/osnma/ubx.rs); se ignoran otros sistemas, E5b, las páginas de alerta y las tramas con suma de comprobación errónea. SFRBX no lleva hora, así que cada página se sella con la hora de sus propios datos (tipos de palabra 5 y 0) y solo se acepta si las páginas consecutivas con hora concuerdan con el número de páginas recibidas entre ellas; las páginas que no se pueden situar se descartan, nunca se adivinan. Esa hora procede de los propios datos y no da protección de frescura (véase Hora más arriba). El identificador de señal 0 (E1-C) no lleva datos, así que aceptarlo junto con E1-B es inocuo. Un par de sincronización suelto con una longitud absurda no retiene al lector. El código de entramado es una tercera copia del entramado UBX del árbol (src/realdata/ubx.rs, la ingesta de receiver-trust); se mantiene aparte para que el comportamiento de receiver-trust no cambie, y las tres podrían compartir un lector más adelante. La disposición de palabras de un mensaje SFRBX se toma de descripciones públicas y se comprueba solo con tramas sintéticas, no con un receptor en vivo.
Línea de comandos#
kshana osnma verify <input> [--format pages|vector-csv|ubx] [--start-gst SECONDS]
[--merkle-root HEX] [--public-key ID:TYPE:HEX]
[--reference-time SECONDS [--max-time-error SECONDS]] [--epochs] [--json]
La salida de texto da el estado por satélite y, con --epochs, por etiqueta y época; --json da lo mismo con la lista de etiquetas. El estado de salida 3 significa que falló una comprobación sobre los datos de algún satélite, y 2 significa un error de uso o de entrada. El aviso de carácter consultivo anterior se imprime primero en todos los caminos, errores y uso incluidos.
Desde Python y WebAssembly#
kshana.osnma_verify(data, format="", start_gst=None, merkle_root_hex="", public_keys=None, reference_time=None, max_time_error=None) (Python) y osnma_verify(input_base64, format, start_gst, merkle_root_hex, public_keys, reference_time, max_time_error) (WebAssembly) ejecutan el mismo verificador sobre bytes en memoria y devuelven el JSON que imprime kshana osnma verify --json, aviso de carácter consultivo incluido. El servidor MCP no lo incluye: la entrada es un flujo largo de receptor sin procesar que la línea de comandos y Python atienden mejor, y una herramienta de agente añadiría una superficie de entrada grande sin beneficio. Se aplican el mismo aviso y los mismos límites; el estado no es un servicio de integridad de la navegación.
Pruebas y datos de prueba#
Los datos de prueba son sintéticos y los generan el propio código y las propias pruebas de Kshana. En este repositorio no se almacena ningún vector del ICD ni de las Guidelines, ningún ejemplo resuelto ni material de claves. Las pruebas por defecto (tests/osnma_synthetic.rs y las pruebas unitarias) construyen una cadena, páginas y etiquetas a partir de las ecuaciones y comprueban la coherencia y que se detecta la corrupción deliberada: un bit de navegación invertido, un bit de clave invertido, etiquetas repetidas, sellos de hora erróneos, una hora fuera de la referencia, datos ausentes y los ataques que halló la revisión (una subtrama falsificada de un futuro lejano que no debe borrar un fallo, una autenticación que debe caducar y seguir al IODnav, una alerta, el estado NMA «don't use», etiquetas ficticias, discrepancia de MACSEQ, claves en conflicto, revocación y renovación de cadena, un relleno o una firma erróneos), con CMAC-AES, una cadena SHA3-256 y una firma P-521 llevadas a través del verificador. Como esos datos proceden de nuestra propia lectura del ICD, muestran autoconsistencia, no ajuste a la norma.
Las primitivas se comprueban frente a pruebas de respuesta conocida publicadas, ninguna tomada del ICD: RFC 4231 (HMAC-SHA-256), RFC 4493 (CMAC-AES-128), FIPS 197 (AES), FIPS 202 (SHA3-256) y RFC 6979 (ECDSA P-256). Las firmas P-521 se comprueban en cuanto a codificación y selección de hash con una clave desechable creada en la prueba; la aritmética de la curva es la de la biblioteca.
El ajuste a la norma se comprueba mediante tests/osnma_official_vectors.rs, que es opcional: lee una copia del archivo oficial de vectores de prueba que usted mismo descarga, del directorio que nombra KSHANA_OSNMA_VECTORS, y no hace nada cuando la variable no está definida. No almacena ni redistribuye nada, y las ejecuciones de prueba por defecto no usan la red. Da al verificador solo una raíz de Merkle y las claves públicas de un árbol, como las tendría un receptor, de modo que las cadenas se establecen a partir del DSM-KROOT firmado del flujo y se recorre todo el camino: Merkle, firma, cadena de claves, MACSEQ y etiquetas. Afirma, por conjunto de vectores, un mínimo de etiquetas autenticadas, que ninguna falló, qué cadenas y claves anuncia el flujo, etiquetas autenticadas de ADKD 0, 4 y 12, que la autenticación se reanuda tras el fin de cadena, la revocación de cadena, la revocación de clave y la nueva clave pública, que el mensaje de alerta se verifica y no deja nada autenticado, y que todo vector usa solo HMAC-SHA-256, una cadena SHA-256, claves de 128 bits, etiquetas de 40 bits y entradas de tabla 33 y 34. Otras opciones de algoritmos y parámetros del ICD quedan, por tanto, cubiertas solo por las pruebas de respuesta conocida y sintéticas anteriores.
Dependencias#
Crates de RustCrypto, Rust puro, MIT OR Apache-2.0, compiladas sin características por defecto: hmac, cmac, aes, sha3, p256 y p521 (la característica ecdsa, usada solo para la verificación), además de sha2, que ya estaba en el árbol. Se compilan para wasm32-unknown-unknown y pasan cargo deny.