Подтвердить что ты не робот

Каков правильный способ проверки SSL-сертификата в Win32?

Я хочу проверить сертификат SSL в Win32 с помощью С++. Я думаю, что я хочу использовать Cert * API, чтобы получить преимущество хранилища сертификатов Windows. Это то, что я придумал.

  • Правильно ли это?
  • Есть ли лучший способ сделать это?
  • Я делаю что-то неправильно?
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,
                                   &params,
                                   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;
}
4b9b3361

Ответ 1

Вы должны знать RFC3280 раздел 6.1 и RFC5280 раздел 6.1. Оба описывают алгоритмы для проверки путей сертификатов. Несмотря на то, что Win32 API позаботится о некоторых вещах для вас, все же может быть полезно узнать о процессе в целом.

Кроме того, heres (на мой взгляд) довольно надежная ссылка: Код подтверждения хром-кода.

В целом, я думаю, что ваш код не является неправильным. Но вот несколько вещей, которые я искал/изменил, если бы я был вами:

1. Отдельная проверка общего имени

Chromium проверяет общее имя сертификата отдельно от цепочки. Видимо, они заметили некоторые проблемы с этим. См. Комментарии для их обоснования:

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. Используйте CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT

Chromium также использует флаг CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT вместо CERT_CHAIN_REVOCATION_CHECK_CHAIN. Я действительно начал изучать это, прежде чем нашел свой код, и это подтвердило мою уверенность в том, что вы должны использовать CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.

Несмотря на то, что оба вышеупомянутых RFC указывают, что самоподписанный доверительный якорь не считается частью цепочки, документация для CertGetCertificateChain (http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v=vs.85).aspx) говорит, что он строит цепочку до, если возможно, доверенного корневого сертификата. Доверенный корневой сертификат определяется (на той же странице) как доверенный самозаверяющий сертификат.

Это исключает возможность того, что * EXCLUDE_ROOT может пропустить проверку отзыва для некорневого доверительного привязки (Win32 фактически требует, чтобы привязки доверия были самоподписаны, даже если это не требуется никакими RFC. Хотя это официально не документировано).

Теперь, поскольку корневой сертификат CA не может отменить сам (CRL не может быть подписан/проверен), мне кажется, что эти два флага идентичны.

Я сделал несколько поисковых запросов и наткнулся на это сообщение на форуме: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum=windowssecurity. Член группы продуктов .NET(предположительно) утверждает, что флаги на практике действуют одинаково, если корень является самозаписываемым (теоретически флаг ENTIRE_CHAIN ​​будет проверять корневой сертификат для отзыва, если он включает расширение CDP, но это не может произойти).

Он также рекомендует использовать флаг * EXCLUDE_ROOT, потому что другой флаг может вызвать ненужный сетевой запрос, если самозаверяющий корневой ЦС включает расширение CDP.

К сожалению:

  • Я не могу найти официально задокументированное объяснение различий между двумя флагами.
  • Несмотря на то, что связанное обсуждение относится к тем же флагам API Win32 под капотом .NET, это не гарантируется.

Чтобы быть уверенным, что его использовать CERT_CHAIN_REVOCATION_CHECK_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, я немного искал Google и нашел код проверки сертификата Chromium SSL, с которым я связан, в верхней части моего ответа.

В качестве дополнительного бонуса файл Chromium cert_verify_proc_win.cc содержит следующие подсказки о коде проверки 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.

Не знаю, как они это знают, но в этот момент Id чувствует себя комфортно, используя CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT.

3. Использование разных принятых сертификатов

Я заметил, что Chromium также указывает на использование трех сертификатов вместо 1:

szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE

Из того, что я могу собрать через Google, другие обычаи могут потребоваться более старыми веб-браузерами, иначе они могут не установить безопасное соединение.

Если Хром считает нужным включить эти обычаи, я последую этому примеру.

Обратите внимание, что если вы измените свой код, вы также должны установить params.RequestedUsage.dwType в USAGE_MATCH_TYPE_OR вместо USAGE_MATCH_TYPE_AND.

-

На данный момент я не думаю о каких-либо других комментариях. Но, если бы я был вами, Идентификация источника Chromium сама (и, может быть, и Firefox) - просто чтобы убедиться, что я ничего не пропустил.

Ответ 2

Я думаю, что лучший ответ зависит от того, что именно вы пытаетесь сделать.

Я предупреждаю вас, что SSL основан на предположении, что обе конечные точки хотят иметь безопасное соединение. Если какая-либо конечная точка не заинтересована в сохранении безопасности, то ее нет.

Его тривиальное усилие поместить байт-коды в ваш распределенный код, который просто возвращает true для этой функции. То почему окна двигали много проверки в ядро. Но они не ожидали, что люди будут запускать окна на виртуальном оборудовании, что делает обход OS примерно такой же тривиальной.

Теперь подумайте, что вы ожидаете получить сертификат от какого-то источника, но притворяетесь, что этому источнику не может быть предоставлена ​​такая же информация из надежного источника. А потом передай его тебе. Таким образом, вы не можете полагаться на сертификаты, чтобы "доказать", что кто-либо в частности.

Единственная защита, получаемая от сертификатов, заключается в том, чтобы предотвратить посторонние, а не конечные точки, нарушение конфиденциальности передаваемого сообщения.

Любое другое использование обречено на провал, и оно в конечном итоге потерпит неудачу с потенциально катастрофическими результатами.

Извините за большой пост. В разделе комментариев есть предел слов.

Ответ 3

Функции CertGetCertificateChain и CertVerifyCertificatePolicy идут вместе. Эта часть верна.

Для CertGetCertificateChain флаг может быть установлен на любое из следующих трех, если вы хотите проверить отмену:

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN ​​
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.

Только один из них может использоваться, эти три параметра не могут быть ORed. Помимо одного из этих флагов вы можете рассмотреть, как создать цепочку; используя local cache или просто CRL или OCSP. Для этих соображений прочитайте эту ссылку.

Ошибка при выполнении функции или более просто, если возвращаемое значение равно 0, это не значит, что сертификат недействителен, скорее вы не смогли выполнить операцию. Для получения информации об ошибках используйте GetLastError(). Таким образом, ваша логика возврата false неверна, это скорее случай выброса ошибки и позволяет клиентскому коду решить, следует ли повторять попытку или продолжать делать другие вещи.

В эта ссылка есть раздел, называемый "классифицировать ошибку", пожалуйста, прочтите это. В основном вы должны проверить ссылку certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS msdn. Итак, здесь вы можете иметь свою бизнес-логику. Например, если вы обнаружите статус ошибки для значения (CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION), проверка проверки отзыва не может быть выполнена, у вас есть возможность решить, что вы хотите (дайте сертификату пойти или пометить его как недействительный).

Итак, перед вызовом CertVerifyCertificatePolicy у вас есть возможность сбросить или уже зарегистрировать ошибку проверки.

Если вы решите перейти на CertVerifyCertificatePolicy, хром-код - замечательная ссылка о том, как сопоставить policy_status.dwError с вашим классом ошибок/перечислением.