c++ - validar - verificar estado de certificado ssl
¿Cuál es la forma correcta de verificar un certificado SSL en Win32? (3)
Quiero verificar un certificado SSL en Win32 usando C ++. Creo que quiero usar la API Cert * para poder obtener el beneficio del almacén de certificados de Windows. Esto es lo que he encontrado.
- ¿Es correcto?
- ¿Hay una mejor manera de hacer esto?
- ¿Estoy haciendo algo mal?
bool IsValidSSLCertificate( PCCERT_CONTEXT certificate, LPWSTR serverName )
{
LPTSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
CERT_CHAIN_PARA params = { sizeof( params ) };
params.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
params.RequestedUsage.Usage.cUsageIdentifier = _countof( usages );
params.RequestedUsage.Usage.rgpszUsageIdentifier = usages;
PCCERT_CHAIN_CONTEXT chainContext = 0;
if ( !CertGetCertificateChain( NULL,
certificate,
NULL,
NULL,
¶ms,
CERT_CHAIN_REVOCATION_CHECK_CHAIN,
NULL,
&chainContext ) )
{
return false;
}
SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslPolicy = { sizeof( sslPolicy ) };
sslPolicy.dwAuthType = AUTHTYPE_SERVER;
sslPolicy.pwszServerName = serverName;
CERT_CHAIN_POLICY_PARA policy = { sizeof( policy ) };
policy.pvExtraPolicyPara = &sslPolicy;
CERT_CHAIN_POLICY_STATUS status = { sizeof( status ) };
BOOL verified = CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL,
chainContext,
&policy,
&status );
CertFreeCertificateChain( chainContext );
return verified && status.dwError == 0;
}
Creo que la mejor respuesta depende de lo que intentas hacer exactamente.
Le advertiré que SSL se basa en el supuesto de que ambos puntos finales desean una conexión segura. Si alguno de los puntos finales no está interesado en mantener la seguridad, entonces no hay ninguno.
Es un esfuerzo trivial para poner códigos de bytes en su código distribuido que simplemente devuelve verdadero para esta función. Es por eso que las ventanas movieron mucha validación en el kernel. Pero no anticiparon que las personas ejecuten Windows en hardware virtual, lo que hace que eludir el sistema operativo sea tan trivial.
Ahora considere que espera recibir un certificado de alguna fuente, pero fingiendo que esa fuente no pudo recibir la misma información de una fuente confiable. Y luego te lo entrego. Por lo tanto, no puede confiar en los certificados para "probar" que alguien es alguien en particular.
La única protección que se obtiene de los certificados es evitar que los forasteros, no los puntos finales, violen la confidencialidad del mensaje que se transporta.
Cualquier otro uso está destinado a fallar, y eventualmente fallará con resultados potencialmente catastróficos.
Lo siento por el gran post. La sección de comentarios tiene un límite de palabras.
Debe tener en cuenta la sección 6.1 de RFC3280 y la sección 6.1 de RFC5280 . Ambos describen algoritmos para validar las rutas de certificados. A pesar de que Win32 API se encarga de algunas cosas por usted, aún podría ser valioso saber sobre el proceso en general.
Además, aquí hay una referencia bastante confiable (en mi opinión): el código de verificación del certificado Chromium .
En general, creo que su código no es incorrecto. Pero aquí hay algunas cosas que vería / cambiaría si fuera usted:
1. Validación separada del nombre común
Chromium valida el nombre común del certificado por separado de la cadena. Al parecer han notado algunos problemas con ello. Vea los comentarios para su razonamiento:
cert_verify_proc.win.cc:731 // Certificate name validation happens separately, later, using an internal
cert_verify_proc.win.cc:732 // routine that has better support for RFC 6125 name matching.
2. Utilice CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
Chromium también utiliza el indicador CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT en lugar de CERT_CHAIN_REVOCATION_CHECK_CHAIN. De hecho, empecé a estudiar esto antes de encontrar su código, y eso me hizo creer que deberías usar CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.
Aunque los RFC mencionados anteriormente especifican que un ancla de confianza autofirmada no se considera parte de una cadena, la documentación para CertGetCertificateChain ( http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v=vs.85).aspx ) dice que construye una cadena hasta, si es posible, un certificado de raíz confiable. Un certificado raíz de confianza se define (en la misma página) como un certificado autofirmado de confianza.
Esto elimina la posibilidad de que * EXCLUDE_ROOT pueda omitir la comprobación de revocación de un ancla de confianza no raíz (Win32 en realidad requiere que los anclajes de confianza estén autofirmados, aunque no sea requerido por ningún RFC. Aunque esto no está documentado oficialmente).
Ahora, dado que un certificado de CA raíz no se puede revocar a sí mismo (la CRL no se pudo firmar / verificar), me parece que estas dos banderas son idénticas.
Hice algunas búsquedas en Google y me encontré en este foro: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum=windowssecurity . Un miembro de .NET Product Group (supuestamente) afirma que los indicadores en la práctica actúan de la misma manera, si la raíz es autofirmada (en teoría, el indicador ENTIRE_CHAIN verificará el certificado raíz para su revocación si incluyera una extensión de CDP, pero eso no puede suceder).
También recomienda usar el indicador * EXCLUDE_ROOT, porque el otro indicador podría causar una solicitud de red innecesaria, si la CA raíz autofirmada incluye la extensión CDP.
Desafortunadamente:
- No puedo encontrar ninguna explicación documentada oficialmente sobre las diferencias entre las dos banderas.
- Aunque es probable que la discusión vinculada se aplique a los mismos indicadores de la API de Win32 bajo el capó de .NET, no está garantizado.
Para estar completamente seguro de que está bien usar CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, busqué un poco más en Google y encontré el código de verificación del certificado SSL de Chromium al que he vinculado en la parte superior de mi respuesta.
Como bono adicional, el archivo Chromium cert_verify_proc_win.cc contiene las siguientes sugerencias sobre el código de verificación de IE:
618: // IE passes a non-NULL pTime argument that specifies the current system
619: // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the
620: // chain_flags argument.
No estoy seguro de cómo sabrían esto, pero en este momento me sentiría cómodo usando CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT.
3. Diferentes usos de certificados aceptados
Noté que Chromium también especifica 3 usos de certificados en lugar de 1:
szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE
De lo que puedo reunir a través de Google, los navegadores web más antiguos pueden exigir otros usos; de lo contrario, pueden fallar al establecer una conexión segura.
Si Chromium lo considera adecuado para incluir estos usos, seguiría su ejemplo.
Tenga en cuenta que si cambia su código, también debe establecer params.RequestedUsage.dwType en USAGE_MATCH_TYPE_OR en lugar de USAGE_MATCH_TYPE_AND.
-
No puedo pensar en ningún otro comentario en este momento. Pero si fuera tú, verificaría la fuente de Chromium (y quizás también Firefox), solo para asegurarme de que no me haya perdido nada.
Las funciones CertGetCertificateChain
y CertVerifyCertificatePolicy
van juntas. Esta parte es correcta.
Para CertGetCertificateChain
el indicador se puede establecer en cualquiera de los tres siguientes si desea verificar la revocación:
- CERT_CHAIN_REVOCATION_CHECK_END_CERT
- CERT_CHAIN_REVOCATION_CHECK_CHAIN
- CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.
Solo se puede usar una de ellas, estas tres opciones no se pueden ORed
. Junto a una de estas banderas, puedes considerar cómo se debe crear la cadena; utilizando local cache
o simplemente CRL
o OCSP
. Por estas consideraciones lee este enlace .
Error al ejecutar la función o más simplemente si el valor de retorno es 0
, no significa que el certificado no sea válido, sino que no pudo realizar la operación. Para información de error use GetLastError()
. Por lo tanto, su lógica de devolver falso es incorrecta, es más bien un caso de lanzar el error y dejar que el código del cliente decida si intentarlo de nuevo o continuar haciendo otras cosas.
En este enlace hay una sección llamada "clasificar el error", por favor lea eso. Básicamente, debe verificar certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS
certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS
certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS
referencia de certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS
msdn. Así que aquí puedes tener tu lógica de negocios. Por ejemplo, si encuentra el estado de error del valor ( CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION
) que no se pudo realizar la comprobación de la revocación del certificado, tiene la opción de decidir lo que desea (dejar que el certificado siga o aún marcarlo como no válido).
Entonces, antes de llamar a CertVerifyCertificatePolicy
, tiene la opción de descartar o ya marcar un error de validación.
Si elige venir a CertVerifyCertificatePolicy
, el código de cromo es una referencia maravillosa sobre cómo asignar policy_status.dwError a su clase de error / enum.