¿Cómo hacer que CRL y OSCP Checking funcionen en iOS?

9

No puedo hacer que las CRL funcionen en iOS. He creado dos casos de prueba. Tengo un certificado que es válido, emitido por una CA. Tengo otro certificado que es válido, emitido por una CA, pero la CA ha agregado ese certificado a su CRL.

Luego configuro una política de revocación que habilita la comprobación de CRL y requiere que tenga éxito.

func crlValidationTest(trustedCert: SecCertificate, certToVerify: SecCertificate) -> Bool {

    let basicPolicy = SecPolicyCreateBasicX509()

    let crlPolicy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod | kSecRevocationCRLMethod | kSecRevocationRequirePositiveResponse)!

    var trust: SecTrust?

    SecTrustCreateWithCertificates(NSArray(object: certToVerify), NSArray(objects: basicPolicy, crlPolicy), &trust)
    SecTrustSetAnchorCertificates(trust!, NSArray(object: trustedCert))
    SecTrustSetNetworkFetchAllowed(trust!, true)

    var trustResult = SecTrustResultType.invalid

    guard SecTrustEvaluate(trust!, &trustResult) == errSecSuccess else {
        return false
    }

    return trustResult == SecTrustResultType.proceed || trustResult == SecTrustResultType.unspecified
}

Mi expectativa es que el certificado que está en la CRL no será confiable, y el certificado que está limpio será confiable.

Dada la configuración anterior, ambos fallan como no confiables. Si elimino la kSecRevocationRequirePositiveResponsebandera, ambos tienen éxito. He probado todas las diferentes permutaciones de usar solo OSCP o solo CRL, y nada funciona de la manera que esperaría.

Documentación de manzanas para SecPolicyCreateRevocationestados:

Por lo general, no es necesario crear una política de revocación usted mismo, a menos que desee anular el comportamiento predeterminado del sistema, por ejemplo, forzar un método en particular o deshabilitar por completo la verificación de revocación.

Usar solo la SecPolicyCreateBasicX509política permite que ambos tengan éxito (cuando el segundo certificado debe fallar), entonces, ¿es el comportamiento predeterminado de Apple no hacer ninguna verificación de CRL?

Os adjunto CharlesProxy a mi dispositivo, y corrió el código varias veces mientras se escucha a todo el tráfico de la red, y no hay peticiones salientes a ir a la CRL que explica por qué todos fallan cuando el RequirePositiveResponsese comprueba la bandera.

También intenté navegar directamente desde el dispositivo a la CRL usando a URLRequest, y pude obtener los datos de la CRL en el dispositivo sin ningún problema.

¿La comprobación de CRL no es compatible a través de la biblioteca de Apple Security? Si es así, ¿alguien ha descubierto la configuración para que responda correctamente? ¿Qué alternativas se utilizan para hacer la validación de CRL? Supongo que las aplicaciones móviles de alta seguridad que operan en el distrito financiero u otras áreas sensibles no permitirían esta brecha de cobertura.

ACTUALIZACIÓN Para comparar, ejecutécertutil -f -urlfetch -verify MYCERT.cerusando certutil, y adjunté Fiddler a la casilla que ejecuta el comando. Recibo los resultados esperados que iOS no me está dando, y veo una solicitud de salida a la CRL a través de HTTP a través de Fiddler.

He creado una recompensa para generar más interés en esto. Espero que alguien tenga más detalles sobre lo que se está haciendo mal anteriormente, o por qué esto no funciona en iOS.

Unome
fuente

Respuestas:

7

En las plataformas de Apple, los clientes no verifican la Lista de revocación de certificados (CRL) de las CA ni utilizan OCSP de manera predeterminada.

Sin embargo, las plataformas de Apple son compatibles con el engrapado OCSP y, alternativamente, proporcionan un mecanismo que llaman Mejora de la revocación, que de hecho podría conducir a una llamada OCSP, consulte los detalles a continuación.

Grapado OCSP

Primero una explicación de grapado OCSP:

El grapado del Protocolo de estado de certificado en línea (OCSP) , formalmente conocido como la extensión de Solicitud de estado de certificado TLS , es un estándar para verificar el estado de revocación de los certificados digitales X.509. 1 Permite al presentador de un certificado asumir el costo de los recursos involucrados en proporcionar respuestas del Protocolo de estado de certificado en línea (OCSP) al agregar ("grapar") una respuesta OCSP con sello de tiempo firmado por la CA al protocolo de enlace TLS inicial, eliminando la necesidad para que los clientes se comuniquen con la CA, con el objetivo de mejorar tanto la seguridad como el rendimiento.

ver https://en.wikipedia.org/wiki/OCSP_stapling

Diferencias entre grapado OCSP y OCSP

Si un cliente se conecta a un servidor en un flujo OCSP tradicional y recupera el certificado, verifica si el certificado recibido ha sido revocado mediante una solicitud a la CA. Esto tiene algunas desventajas, por ejemplo, se requiere una conexión de red adicional, la información no está encriptada y, por lo tanto, representa un problema de privacidad de datos.

A través del engrapado OCSP, el servidor solicita una información de revocación firmada de la CA y la agrega al protocolo de enlace TLS.

Esto también significa que, al usar el engrapado OCSP, no ve una solicitud OCSP de iOS a un servidor de CA.

Inconvenientes del grapado OCSP

El servidor al que se está conectando debe admitir el engrapado OCSP. Esto tampoco protege contra servidores maliciosos.

Esas son las razones principales por las que Apple está proporcionando una mejora de revocación.

Mejora de la revocación de Apple

Así es como funciona:

  • Apple registra las entradas de los registros de transparencia del certificado
  • con esta información, Apple recopila información sobre las revocaciones de las AC
  • esta información agregada se pone automáticamente a disposición de todos los clientes de Apple de forma regular
  • según esta información, cuando una aplicación de iOS intenta conectarse al servidor con un certificado revocado, realiza una verificación adicional a través de OCSP.

Requisito

El único requisito para que una aplicación lo admita es que el certificado del servidor utilizado se agregue a un registro de transparencia del certificado. Normalmente, una CA ya lo hace, pero debe verificar que el certificado de dominio esté en los registros de transparencia activos para certificados públicos, por ejemplo, utilizando el siguiente enlace: https://transparencyreport.google.com/https/certificates

WWDC 2017, sesión 701

Hay una excelente sesión de WWDC en la que se explican en detalle este tema y los motivos de Apple: WWDC 2017, sesión 701: https://developer.apple.com/videos/play/wwdc2017/701/

Alrededor del minuto 12:10, un ingeniero de Apple explica todo el tema de revocación en detalle. Alrededor de las 15:30, explica que el OCSP normal requeriría el uso de API adicionales.

Prueba de grapado OCSP en iOS

Para una prueba, necesitamos un servidor que admita el engrapado OCSP y utilice un certificado revocado: https://revoked.grc.com (encontró este servidor en la respuesta predeterminada de este servidor: https://serverfault.com/a/645066 )

Luego podemos intentar conectarnos desde iOS con un pequeño programa de prueba que intenta descargar la respuesta HTML y enviarla a la consola.

Según la información de la sesión de WWDC mencionada anteriormente, el intento de conexión debería fallar.

...
let session = URLSession(configuration: .default)
...

func onDownloadAction() {
    let url = URL(string: "https://revoked.grc.com")!
    self.download(from: url) { (result, error) in
        if let result = result {
            print("result: " + result)
        } else {
            print("download failed")
            if let error = error {
                print("error: \(error)")
            }
        }
    }
}


func download(from url: URL, completion: @escaping(String?, Error?)->Void) {
    let dataTask = self.session.dataTask(with: url) { data, response, error in
        guard let data = data else {
            if let error = error {
                completion(nil, error)
                return
            }
            completion(nil, NSError(domain: "DownloadFailure", code: 0, userInfo:nil))
            return
        }

        guard let response = response as? HTTPURLResponse else {
            completion(nil, NSError(domain: "ResponseFailure", code: 0, userInfo:nil))
            return
        }
        print("http status: \(response.statusCode)")
        let res = String(bytes: data, encoding: .utf8)
        completion(res, nil)
    }
    dataTask.resume()
}

Si ejecutamos la rutina anterior en el simulador de iOS, podemos usar Wireshark para verificar si una respuesta OCSP con marca de tiempo firmada por la CA está grapada al apretón de manos TLS.

Con nslookup revoked.grc.comnosotros obtenemos la dirección IP del servidor y podemos filtrar en Wireshark con ip.addr==4.79.142.205.

En la captura de pantalla se puede ver que el certificado tiene el estado revoked.

Wirehark

Echando un vistazo a la consola Xcodes, se puede ver el siguiente resultado:

2019-10-12 21:32:25.734382+0200 OCSPTests[6701:156558] ATS failed system trust
2019-10-12 21:32:25.734526+0200 OCSPTests[6701:156558] Connection 1: system TLS Trust evaluation failed(-9802)
2019-10-12 21:32:25.734701+0200 OCSPTests[6701:156558] Connection 1: TLS Trust encountered error 3:-9802
2019-10-12 21:32:25.734787+0200 OCSPTests[6701:156558] Connection 1: encountered error(3:-9802)
2019-10-12 21:32:25.737672+0200 OCSPTests[6701:156558] Task <12408947-689F-4537-9642-C8F95E86CA62>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9802])
download failed
error: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000037f8510>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
    "<cert(0x7fda78828200) s: revoked.grc.com i: DigiCert SHA2 Secure Server CA>",
    "<cert(0x7fda7882b200) s: DigiCert SHA2 Secure Server CA i: DigiCert Global Root CA>"
), NSUnderlyingError=0x600000be9170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x6000037f8510>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
    "<cert(0x7fda78828200) s: revoked.grc.com i: DigiCert SHA2 Secure Server CA>",
    "<cert(0x7fda7882b200) s: DigiCert SHA2 Secure Server CA i: DigiCert Global Root CA>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://revoked.grc.com/, NSErrorFailingURLStringKey=https://revoked.grc.com/, NSErrorClientCertificateStateKey=0}

iOS cancela el intento de conectarse al servidor con un error de TLS.

Prueba revocada.badssl.com

revoked.badssl.com no admite el engrapado OCSP.

Si echamos un vistazo a los detalles del certificado de https://revoked.badssl.com , podemos descubrir:

Si uno descarga el archivo .crl (2.5MB) y emite un

openssl crl -inform DER -text -in ssca-sha2-g6.crl | grep 0371B58A86F6CE9C3ECB7BF42F9208FC

se puede ver que este certificado se revoca a través de CRL.

Curiosamente, ni Safari ni Chrome ni iOS reconocen este estado revocado. Solo Mozilla Firefox muestra un mensaje de error ( se ha revocado el certificado de igual. Código de error: SEC_ERROR_REVOKED_CERTIFICATE ).

La razón podría ser que el certificado se renovó hace solo unos días y, por lo tanto, aún no ha llegado a todas las listas locales de revocación de navegadores y sistemas operativos.

Stephan Schlecht
fuente
Gran información aquí. Gracias por la atenta respuesta. A medida que continúo investigando este tema, he visto lo mismo que usted, el soporte de CRL está siendo eliminado por los principales navegadores / sistemas operativos y el engrapado OCSP parece ser el nuevo mecanismo de seguridad recomendado. En el video de WWDC, el representante de Apple dice: "Desafortunadamente, nuestras plataformas NO comprueban la revocación por defecto actualmente". Lo que he encontrado a través de mis experimentos es que no solo no es compatible de forma predeterminada, sino que tampoco es compatible (incluso si fuerza la configuración) @Stephan Schlecht
Unome