# Codex — Radio Babasónica Alexa: Security Hardening FINAL

## Proyecto

Trabajar exclusivamente sobre el proyecto real:

```text
C:\xampp\htdocs\radio-babasonica-alexa\
```

Endpoint público actual:

```text
https://aproposito.ddns.net/radio-babasonica-alexa/public/
```

Stream de Radio Babasónica:

```text
https://aproposito.ddns.net/live/radiobbs/index.m3u8
```

Esta tarea es la **última iteración de seguridad antes de conectar la Skill con Alexa Developer Console**.

---

# Objetivo

Cerrar dos pendientes de seguridad detectados en la revisión del backend:

1. Normalizar correctamente `SignatureCertChainUrl` **antes** de validarla.
2. Validar criptográficamente la cadena X.509 completa descargada desde Amazon hasta una CA raíz confiable.

Debe conservarse intacta la implementación ya existente de:

```text
Signature-256
SHA-256
anti-downgrade
timestamp <= 150 s
Skill ID
SAN echo-api.amazon.com
logging sanitizado
AudioPlayer
PlaybackController
```

---

# Referencia oficial obligatoria

Usar como fuente principal:

```text
https://developer.amazon.com/en-US/docs/alexa/custom-skills/host-a-custom-skill-as-a-web-service.html
```

Amazon establece, entre otras cosas:

```text
- Normalizar SignatureCertChainUrl eliminando dot segments, duplicate slashes y fragment.
- Validar después:
    protocol HTTPS
    hostname s3.amazonaws.com
    path comenzando exactamente con /echo.api/
    puerto 443 si se especifica.
- Descargar la cadena X.509 PEM.
- El primer certificado es el signing certificate de Amazon.
- Debe haber uno o más certificados que formen una cadena de confianza.
- Validar NotBefore / NotAfter.
- Verificar SAN echo-api.amazon.com.
- Validar que TODOS los certificados formen una cadena de confianza
  hasta una CA raíz confiable.
- Usar la clave pública del signing certificate para validar Signature-256.
```

Si la documentación oficial actual contradice algún detalle menor de este documento, seguir la documentación oficial y documentar la diferencia.

---

# Restricciones obligatorias

NO modificar:

```text
SignalLive
MediaMTX
mediamtx.yml
FFmpeg
radiobbs
/live/
WordPress
app C#
Skill del Mundial
```

NO reiniciar:

```text
Apache
MediaMTX
FFmpeg
streamers
Windows
```

NO configurar Alexa Developer Console todavía.

NO inventar:

```text
Skill ID
certificados raíz
CA bundles
certificados de Amazon
tokens
secretos
```

NO deshabilitar validaciones existentes.

NO permitir bypass de seguridad en producción.

---

# Paso 1 — Inspección completa del proyecto real

Antes de modificar nada, inventariar:

```text
C:\xampp\htdocs\radio-babasonica-alexa\
```

Registrar todos los archivos existentes.

Revisar como mínimo:

```text
src\AlexaRequestVerifier.php
src\AlexaRequestHandler.php
src\SanitizedLogger.php
src\bootstrap.php
public\index.php
config\config.php
tests\run.php
README.md
```

También comprobar:

```text
interaction-model\
storage\
public\
config\
src\
tests\
```

Si algún archivo que aparecía en reportes anteriores no existe realmente, registrarlo.

---

# Paso 2 — Confirmar estado actual de Signature-256

Antes de tocar el código, verificar que actualmente existe esta lógica:

```text
Signature-256 presente
    -> SHA-256
    -> si falla: RECHAZAR
    -> NO downgrade

Signature-256 ausente
    -> Signature
    -> SHA-1 fallback
```

Confirmar que el body firmado es el RAW body exacto.

No modificar esta lógica salvo que sea estrictamente necesario.

---

# Paso 3 — Normalización de SignatureCertChainUrl

Implementar una función dedicada, testeable y centralizada.

Nombre sugerido:

```php
normalizeSignatureCertChainUrl()
```

o equivalente.

## Orden obligatorio

La lógica debe ser conceptualmente:

```text
header original
    ↓
parsear URL de forma segura
    ↓
eliminar fragment
    ↓
normalizar PATH
    ├─ duplicate slashes
    └─ dot segments "." y ".."
    ↓
reconstruir URL normalizada
    ↓
validar criterios Amazon
```

NO validar primero una URL no normalizada y después transformarla.

---

# Paso 4 — Reglas de normalización

La normalización debe eliminar:

```text
.
..
duplicate slashes
fragment #...
```

Ejemplo oficial:

Entrada:

```text
https://s3.amazonaws.com/echo.api/../echo.api/echo-api-cert.pem
```

Normalizada:

```text
https://s3.amazonaws.com/echo.api/echo-api-cert.pem
```

También debe evitar que la normalización permita salir de la ruta autorizada.

Ejemplos a probar:

```text
https://s3.amazonaws.com/echo.api/./echo-api-cert.pem
https://s3.amazonaws.com//echo.api//echo-api-cert.pem
https://s3.amazonaws.com/echo.api/foo/../echo-api-cert.pem
https://s3.amazonaws.com/echo.api/../echo.api/echo-api-cert.pem
https://s3.amazonaws.com/echo.api/echo-api-cert.pem#fragment
```

---

# Paso 5 — Validación posterior a normalización

Después de normalizar, exigir exactamente:

## Scheme

```text
https
```

case-insensitive.

Rechazar:

```text
http
ftp
file
data
```

## Host

Exactamente:

```text
s3.amazonaws.com
```

case-insensitive.

Rechazar:

```text
evil.com
s3.amazonaws.com.evil.com
amazonaws.com
127.0.0.1
localhost
direcciones IP
userinfo@host
```

No resolver otro hostname equivalente.

## Path

Debe comenzar, case-sensitive, con:

```text
/echo.api/
```

Aceptar:

```text
/echo.api/echo-api-cert.pem
```

Rechazar:

```text
/ECHO.API/
/Echo.Api/
/invalid.path/
/echo.api.evil/
```

## Port

Si no aparece:

```text
aceptar
```

Si aparece:

```text
debe ser exactamente 443
```

Rechazar cualquier otro puerto.

---

# Paso 6 — Prevención SSRF

`SignatureCertChainUrl` controla una descarga desde el backend, por lo que esta validación debe ocurrir **ANTES de hacer cualquier request HTTP**.

Debe ser imposible provocar descargas desde:

```text
localhost
127.0.0.1
::1
LAN privada
otros dominios
otros puertos
file://
```

No seguir redirects hacia hosts no permitidos.

Si la implementación HTTP permite redirects:

```text
preferencia: desactivarlos
```

o, si fueran necesarios:

```text
validar cada destino con las mismas reglas antes de seguirlo
```

No basta con validar sólo la URL inicial.

---

# Paso 7 — Descargar cadena PEM

Sólo después de validar la URL normalizada.

La descarga debe:

```text
usar HTTPS
validar TLS del servidor
tener timeout razonable
tener límite de tamaño
rechazar respuestas vacías
rechazar contenido no interpretable como PEM
```

No guardar permanentemente contenido arbitrario.

La caché pública de certificados puede mantenerse si ya existe y es segura.

---

# Paso 8 — Separar correctamente la cadena PEM

La respuesta puede contener varios bloques:

```text
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
```

Extraer TODOS los certificados en orden.

Debe existir al menos:

```text
certificado 0 = signing certificate
```

y una cadena posterior suficiente para validación.

No asumir un número fijo de intermediarios.

No asumir nombres concretos de CA.

No hardcodear certificados de Amazon.

---

# Paso 9 — Validar signing certificate

Sobre el certificado 0:

Comprobar:

```text
NotBefore
NotAfter
SAN contiene exactamente echo-api.amazon.com
```

La validación de SAN debe leer la extensión correcta del certificado.

No aceptar coincidencias parciales como:

```text
evil-echo-api.amazon.com
echo-api.amazon.com.evil.com
```

Debe existir el DNS SAN correcto.

---

# Paso 10 — Validar cadena X.509 completa

Este punto es crítico.

No basta con:

```text
openssl_x509_parse()
```

ni con:

```text
validar sólo SAN y fechas
```

Debe comprobarse criptográficamente:

```text
signing certificate
       ↓ firmado por
intermediate certificate
       ↓
otro intermediate (si existe)
       ↓
trusted root CA
```

hasta una raíz confiable.

El root confiable puede no venir incluido en la cadena descargada.

---

# Paso 11 — Determinar CA trust store real en Windows/XAMPP

Antes de implementar la verificación, inspeccionar el entorno PHP/OpenSSL actual.

Ejecutar:

```cmd
C:\xampp\php\php.exe -i
```

Buscar:

```text
openssl.cafile
openssl.capath
curl.cainfo
OpenSSL Library Version
```

También inspeccionar, sin modificar:

```text
C:\xampp\php\php.ini
C:\xampp\apache\bin\
C:\xampp\php\extras\
```

Buscar archivos existentes tipo:

```text
cacert.pem
ca-bundle.crt
curl-ca-bundle.crt
```

NO asumir una ruta.

NO descargar ni inventar un CA bundle si ya existe uno confiable utilizado por XAMPP/PHP/cURL.

Registrar exactamente qué trust source se utilizará.

---

# Paso 12 — Mecanismo de chain validation

Implementar la validación dentro de PHP usando capacidades criptográficas de OpenSSL, preferiblemente:

```text
openssl_x509_checkpurpose()
```

o una alternativa PHP/OpenSSL equivalente que valide la cadena frente al trust store confiable.

Conceptualmente:

```text
leaf/signing certificate
+
intermediate certificate(s) como untrusted chain
+
CA bundle confiable del sistema/XAMPP
        ↓
OpenSSL chain verification
        ↓
VALID / INVALID
```

NO ejecutar un `openssl.exe` por cada request Alexa si puede evitarse.

La verificación debe vivir en el backend PHP.

Si la API de PHP requiere un archivo temporal para los intermediarios:

```text
crear archivo temporal seguro
usarlo sólo durante la verificación
eliminarlo en finally
```

No dejar residuos con certificados por cada request.

---

# Paso 13 — No confundir TLS de descarga con cadena Alexa

Son dos controles distintos:

```text
A) cURL valida TLS contra s3.amazonaws.com
B) el backend valida criptográficamente la cadena X.509 descargada
```

Ambos deben existir.

Que cURL confíe en el certificado HTTPS de S3 NO sustituye la validación de la cadena del certificado de firma Alexa.

---

# Paso 14 — Resultado ante chain inválida

Si falla cualquier punto:

```text
URL
download
PEM parsing
vigencia
SAN
chain trust
Signature-256
timestamp
Skill ID
```

la request debe rechazarse.

No continuar con el controller.

No reproducir audio.

No revelar detalles criptográficos al cliente.

Usar HTTP 400 cuando corresponda conforme a Alexa.

Registrar causa sanitizada.

---

# Paso 15 — Cache

Si ya existe cache de certificados:

```text
storage\cert-cache\
```

puede mantenerse.

Pero al leer desde caché deben volver a aplicarse las validaciones necesarias.

No asumir:

```text
"está cacheado = está validado para siempre"
```

Como mínimo:

```text
vigencia
SAN
chain trust
```

deben seguir siendo válidos.

La expiración de caché existente puede mantenerse si es razonable.

---

# Paso 16 — Tests de URL normalization

Agregar tests como mínimo:

| Entrada | Resultado |
|---|---|
| `https://s3.amazonaws.com/echo.api/echo-api-cert.pem` | ACEPTAR |
| `https://s3.amazonaws.com:443/echo.api/echo-api-cert.pem` | ACEPTAR |
| `/echo.api/../echo.api/` | NORMALIZAR Y ACEPTAR |
| `/echo.api/./` | NORMALIZAR Y ACEPTAR |
| `//echo.api//` | NORMALIZAR Y ACEPTAR |
| URL con `#fragment` | quitar fragment y validar |
| `http://s3.amazonaws.com/...` | RECHAZAR |
| `https://evil.com/...` | RECHAZAR |
| `https://s3.amazonaws.com.evil.com/...` | RECHAZAR |
| `https://s3.amazonaws.com:444/...` | RECHAZAR |
| `/ECHO.API/` | RECHAZAR |
| `/invalid.path/` | RECHAZAR |
| localhost | RECHAZAR |
| 127.0.0.1 | RECHAZAR |

Agregar otros casos adversariales razonables.

---

# Paso 17 — Tests criptográficos de cadena

Crear PKI DE PRUEBA local, exclusivamente dentro de tests, para comprobar la lógica.

Generar durante tests:

```text
Test Root CA
    ↓
Test Intermediate CA
    ↓
Test Leaf Certificate
```

El leaf de prueba debe contener SAN:

```text
echo-api.amazon.com
```

No usar claves reales.

No guardar secretos de producción.

Probar como mínimo:

## Caso 1 — cadena válida

```text
leaf válido
intermediate válido
root confiable de test
```

Resultado:

```text
ACEPTAR
```

## Caso 2 — root no confiable

Resultado:

```text
RECHAZAR
```

## Caso 3 — intermediate incorrecto

Resultado:

```text
RECHAZAR
```

## Caso 4 — firma del leaf alterada / cadena rota

Resultado:

```text
RECHAZAR
```

## Caso 5 — leaf expirado

Resultado:

```text
RECHAZAR
```

## Caso 6 — leaf aún no válido

Resultado:

```text
RECHAZAR
```

## Caso 7 — SAN incorrecto

Resultado:

```text
RECHAZAR
```

## Caso 8 — falta intermediate

Resultado:

```text
RECHAZAR
```

---

# Paso 18 — Mantener tests Signature-256

Volver a ejecutar y mantener PASS:

```text
Signature-256 válida
Signature-256 inválida
Signature-256 + Signature
Signature-256 inválida + Signature válida => rechazar
sólo Signature válida => SHA-1 fallback
sin firma => rechazar
```

No eliminar anti-downgrade.

---

# Paso 19 — Tests de regresión funcional

Ejecutar todos los tests existentes:

```text
LaunchRequest
PlayRadioIntent
PauseIntent
ResumeIntent
StopIntent
PlaybackStarted
PlaybackStopped
PlaybackFailed
PlaybackController.PlayCommandIssued
PlaybackController.PauseCommandIssued
JSON inválido
```

Todo debe seguir pasando.

---

# Paso 20 — PHP lint

Ejecutar `php -l` sobre TODOS los `.php` de:

```text
config\
public\
src\
tests\
```

Resultado obligatorio:

```text
PASS
```

---

# Paso 21 — Endpoint público

No cambiar:

```text
https://aproposito.ddns.net/radio-babasonica-alexa/public/
```

Comprobar únicamente:

```text
GET -> 405
POST JSON inválido -> respuesta controlada
Content-Type application/json
HTTPS válido
```

No intentar falsificar una request Alexa real contra producción.

---

# Paso 22 — Stream

No modificarlo.

Confirmar al final que sigue siendo:

```text
https://aproposito.ddns.net/live/radiobbs/index.m3u8
```

y opcionalmente ejecutar:

```cmd
C:\ffmpeg\bin\ffprobe.exe -hide_banner -v error -show_streams "https://aproposito.ddns.net/live/radiobbs/index.m3u8"
```

Sólo para confirmar que no hubo impacto colateral.

---

# Paso 23 — Logging

Añadir motivos sanitizados como:

```text
cert_url_invalid
cert_download_failed
cert_pem_invalid
cert_expired
cert_not_yet_valid
cert_san_invalid
cert_chain_invalid
signature_verification_failed
```

NO registrar:

```text
Signature-256 completa
Signature completa
raw request body
accessToken
apiAccessToken
certificados completos
private keys de tests
```

---

# Paso 24 — README

Actualizar `README.md` para reflejar que ahora la seguridad de transporte Alexa incluye:

```text
SignatureCertChainUrl normalization
host/scheme/path/port validation
SSRF protection
TLS validation during download
full X.509 chain validation
trusted CA store
NotBefore / NotAfter
SAN echo-api.amazon.com
Signature-256 / SHA-256
anti-downgrade
timestamp <= 150 s
Skill ID
```

Documentar qué CA trust source utiliza el servidor.

---

# Paso 25 — Reporte FINAL

Generar:

```text
C:\xampp\htdocs\radio-babasonica-alexa\RADIO_BABASONICA_ALEXA_SECURITY_FINAL_RESULTS.md
```

Contenido obligatorio:

## Resumen

```text
Hardening completado: SI / PARCIAL / NO
URL normalization: PASS / FAIL
SSRF protection: PASS / FAIL
Full X.509 chain validation: PASS / FAIL
Trusted CA source identificado: SI / NO
Signature-256: PASS / FAIL
Anti-downgrade: PASS / FAIL
Timestamp: PASS / FAIL
Skill ID validation: PASS / FAIL
Functional tests: PASS / FAIL
PHP lint: PASS / FAIL
Endpoint HTTPS: PASS / FAIL
```

## Trust store detectado

Mostrar:

```text
openssl.cafile:
openssl.capath:
curl.cainfo:
CA bundle usado:
```

No incluir contenido completo del bundle.

## Archivos modificados

Lista completa.

## Archivos creados

Lista completa.

## URL normalization

Explicar algoritmo.

Mostrar tabla completa de tests.

## Certificate chain validation

Explicar cómo se construye:

```text
leaf
intermediates
trusted roots
```

y qué API de PHP/OpenSSL se usa.

## Tests criptográficos

Tabla:

| Prueba | Esperado | Resultado |
|---|---|---|
| cadena válida | aceptar | |
| root no confiable | rechazar | |
| intermediate incorrecto | rechazar | |
| cadena rota | rechazar | |
| leaf expirado | rechazar | |
| leaf no vigente aún | rechazar | |
| SAN incorrecto | rechazar | |
| intermediate faltante | rechazar | |

## Signature-256 tests

Tabla completa.

## Regresión funcional

Tabla completa.

## PHP lint

Resultado.

## Endpoint

Resultado.

## Pendientes reales

Debe indicar explícitamente:

```text
Skill ID real configurado: SI / NO
Alexa Developer Console configurada: NO
Request Alexa real probada: NO
Echo físico probado: NO
```

No considerar estos últimos tres como fallos de esta tarea.

---

# Paso 26 — ZIP COMPLETO obligatorio

Al finalizar crear:

```text
C:\xampp\htdocs\radio-babasonica-alexa\radio-babasonica-alexa-security-final.zip
```

El ZIP debe contener **el proyecto completo**, no una selección parcial.

Debe incluir como mínimo, si existen:

```text
config\
interaction-model\
public\
src\
storage\
tests\
README.md
.gitignore
RADIO_BABASONICA_ALEXA_V01_IMPLEMENTATION_RESULTS.md
RADIO_BABASONICA_ALEXA_SIGNATURE256_RESULTS.md
RADIO_BABASONICA_ALEXA_SECURITY_FINAL_RESULTS.md
```

Excluir:

```text
.git\
logs con información innecesaria
cache runtime de certificados si no se necesita
archivos temporales
private keys generadas por tests
secretos
```

IMPORTANTE:

Si los tests generan una PKI temporal, las **private keys de tests no deben terminar en el ZIP final**, salvo que sean fixtures sintéticos deliberadamente incluidos y no reutilizables. Preferencia: generar y destruir durante ejecución.

---

# Paso 27 — Verificar el ZIP

Después de crearlo:

1. listar su contenido;
2. comprobar que existen:

```text
public\index.php
config\config.php
interaction-model\es-MX.json
src\AlexaRequestVerifier.php
tests\run.php
README.md
RADIO_BABASONICA_ALEXA_SECURITY_FINAL_RESULTS.md
```

3. registrar tamaño del ZIP;
4. registrar que no contiene `.key`, tokens o secretos.

Si falta alguna carpeta real del proyecto:

```text
NO declarar el ZIP como completo
```

Corregirlo.

---

# Criterios finales de aceptación

```text
[ ] SignatureCertChainUrl se normaliza ANTES de validar
[ ] dot segments eliminados
[ ] duplicate slashes eliminados
[ ] fragment eliminado
[ ] HTTPS obligatorio
[ ] host exacto s3.amazonaws.com
[ ] path case-sensitive /echo.api/
[ ] sólo puerto 443
[ ] protección SSRF
[ ] redirects inseguros bloqueados
[ ] cadena PEM completa parseada
[ ] leaf NotBefore validado
[ ] leaf NotAfter validado
[ ] SAN echo-api.amazon.com validado
[ ] cadena criptográfica completa validada
[ ] root termina en CA confiable
[ ] trust store real identificado
[ ] no hay root hardcodeado inventado
[ ] Signature-256 SHA-256 sigue funcionando
[ ] anti-downgrade sigue funcionando
[ ] timestamp <= 150 s sigue funcionando
[ ] Skill ID validation sigue funcionando
[ ] tests adversariales URL pasan
[ ] tests criptográficos pasan
[ ] tests Signature-256 pasan
[ ] tests funcionales pasan
[ ] PHP lint pasa
[ ] endpoint HTTPS sigue igual
[ ] radiobbs no fue modificado
[ ] MediaMTX no fue modificado
[ ] Apache no fue reiniciado
[ ] reporte final generado
[ ] ZIP completo generado
[ ] ZIP auditado sin secretos
```

---

# Entregables

Al terminar deben existir:

```text
C:\xampp\htdocs\radio-babasonica-alexa\RADIO_BABASONICA_ALEXA_SECURITY_FINAL_RESULTS.md

C:\xampp\htdocs\radio-babasonica-alexa\radio-babasonica-alexa-security-final.zip
```

Estos dos archivos se adjuntarán después a ChatGPT para una revisión final.

---

# Fin de esta fase

Codex NO debe crear ni configurar la Skill en Amazon.

Cuando todos los criterios anteriores pasen:

```text
Backend Radio Babasónica
        ↓
SECURITY READY
        ↓
siguiente fase:
Alexa Developer Console
```
