Как правильно проверить сертификат SSL в Win32? - PullRequest
20 голосов
/ 08 сентября 2011

Я хочу проверить сертификат SSL в Win32, используя C ++. Я думаю, что хочу использовать 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;
}

Ответы [ 3 ]

16 голосов
/ 28 февраля 2014

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

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

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

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

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

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 может пропустить проверку отзыва для привязки доверия без полномочий root (Win32 фактически требует, чтобы привязки доверия были самозаверяющими, даже если это не требуется никакими RFC. Хотя это официально не задокументировано ).

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

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

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

К сожалению:

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

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

В качестве дополнительного бонуса файл 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.

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

3. Различные принятые сертификаты

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

szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE

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

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

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

-

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

1 голос
/ 22 февраля 2014

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

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

Это простая попытка поместить байтовые коды в ваш распределенный код, который просто возвращает true для этой функции.Вот почему Windows перенесла много проверок в ядро.Но они не ожидали, что люди запустят Windows на виртуальном оборудовании, что делает обход ОС почти тривиальным.

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

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

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

0 голосов
/ 27 января 2015

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

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

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN ​​* 10111012 * 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 reference.Так что здесь вы можете иметь свою бизнес-логику.Например, если вы обнаружите состояние ошибки значения (CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION), что проверка отзыва сертификата не может быть выполнена, у вас есть возможность решить, что вы хотите (отпустить сертификат или все же пометить его как недействительный).

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...