Post-festum CRL checking introduced

This commit is contained in:
2016-12-05 13:00:39 +01:00
parent 303350dec0
commit d82e22d188
6 changed files with 273 additions and 23 deletions

View File

@@ -62,6 +62,15 @@ void eap::peer_ttls::initialize()
void eap::peer_ttls::shutdown()
{
// Signal all certificate revocation verify threads to abort and wait for them (10sec max).
vector<HANDLE> chks;
chks.reserve(m_crl_checkers.size());
for (auto chk = m_crl_checkers.begin(), chk_end = m_crl_checkers.end(); chk != chk_end; ++chk) {
SetEvent(chk->m_abort);
chks.push_back(chk->m_thread);
}
WaitForMultipleObjects((DWORD)chks.size(), chks.data(), TRUE, 10000);
// Uninitialize EapHost. It was initialized for EapHost based inner authentication methods.
EapHostPeerUninitialize();
}
@@ -379,6 +388,17 @@ void eap::peer_ttls::set_response_attributes(
}
void eap::peer_ttls::spawn_crl_check(_Inout_ winstd::cert_context &&cert)
{
// Create the thread and add it to the list.
m_crl_checkers.push_back(std::move(crl_checker(*this, std::move(cert))));
// Now the thread is in-place, start it.
crl_checker &chk = m_crl_checkers.back();
chk.m_thread = CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(crl_checker::verify), &chk, 0, NULL);
}
const eap::config_method_ttls* eap::peer_ttls::combine_credentials(
_In_ DWORD dwFlags,
_In_ const config_connection &cfg,
@@ -498,3 +518,143 @@ eap::peer_ttls::session::~session()
m_module.free_memory(m_blob_cred);
#endif
}
//////////////////////////////////////////////////////////////////////
// eap::peer_ttls::crl_checker
//////////////////////////////////////////////////////////////////////
eap::peer_ttls::crl_checker::crl_checker(_In_ module &mod, _Inout_ winstd::cert_context &&cert) :
m_module(mod),
m_cert (std::move(cert)),
m_abort (CreateEvent(NULL, TRUE, FALSE, NULL))
{
}
eap::peer_ttls::crl_checker::crl_checker(_Inout_ crl_checker &&other) :
m_module( other.m_module ),
m_thread(std::move(other.m_thread)),
m_abort (std::move(other.m_abort )),
m_cert (std::move(other.m_cert ))
{
}
eap::peer_ttls::crl_checker& eap::peer_ttls::crl_checker::operator=(_Inout_ crl_checker &&other)
{
if (this != std::addressof(other)) {
assert(std::addressof(m_module) == std::addressof(other.m_module)); // Move threads within same module only!
m_thread = std::move(other.m_thread);
m_abort = std::move(other.m_abort );
m_cert = std::move(other.m_cert );
}
return *this;
}
DWORD WINAPI eap::peer_ttls::crl_checker::verify(_In_ crl_checker *obj)
{
// Initialize COM.
com_initializer com_init(NULL);
// Wait for 5sec for the link to become online. (Hopefuly!)
if (WaitForSingleObject(obj->m_abort, 5000) == WAIT_OBJECT_0) {
// Aborted.
return 1;
}
// Prepare a list of certificates forming certificate chain.
list<cert_context> context_data;
for (cert_context c(obj->m_cert); c;) {
context_data.push_back(std::move(c));
DWORD flags = 0;
c = CertGetIssuerCertificateFromStore(obj->m_cert->hCertStore, context_data.back(), NULL, &flags);
if (!c) break;
}
// Create an array of pointers to CERT_CONTEXT required by CertVerifyRevocation().
vector<PCERT_CONTEXT> context;
context.reserve(context_data.size());
for (auto c = context_data.cbegin(), c_end = context_data.cend(); c != c_end; ++c)
context.push_back(const_cast<PCERT_CONTEXT>(c->operator PCCERT_CONTEXT()));
CERT_REVOCATION_STATUS status_rev = { sizeof(CERT_REVOCATION_STATUS) };
for (auto c = context.begin(), c_end = context.end(); c != c_end;) {
// Check for thread abort signal.
if (WaitForSingleObject(obj->m_abort, 0) == WAIT_OBJECT_0)
return 1;
// Perform revocation check.
if (!CertVerifyRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE,
(DWORD)(c_end - c), reinterpret_cast<PVOID*>(&*c),
CERT_VERIFY_REV_CHAIN_FLAG, NULL, &status_rev))
{
PCCERT_CONTEXT cert = *(c + status_rev.dwIndex);
wstring subj;
if (!CertGetNameStringW(cert, CERT_NAME_DNS_TYPE, CERT_NAME_STR_ENABLE_PUNYCODE_FLAG, NULL, subj))
sprintf(subj, L"<error %u>", GetLastError());
switch (status_rev.dwError) {
case CRYPT_E_NO_REVOCATION_CHECK:
// Revocation check could not be performed.
c += status_rev.dwIndex + 1;
if (c == c_end) {
// This "error" is expected for the root CA certificate.
} else {
// This really was an error, as it appeared before the root CA cerficate in the chain.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_SKIPPED, event_data((unsigned int)eap_type_ttls), event_data(subj), event_data::blank);
}
break;
case CRYPT_E_REVOKED:
// One of the certificates in the chain was revoked.
switch (status_rev.dwReason) {
case CRL_REASON_AFFILIATION_CHANGED:
case CRL_REASON_SUPERSEDED:
case CRL_REASON_CESSATION_OF_OPERATION:
case CRL_REASON_CERTIFICATE_HOLD:
// The revocation was of administrative nature. No need to black-list.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED1, event_data((unsigned int)eap_type_ttls), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
break;
default: {
// One of the certificates in the chain was revoked as compromised. Black-list it.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKED, event_data((unsigned int)eap_type_ttls), event_data(subj), event_data(status_rev.dwReason), event_data::blank);
reg_key key;
if (key.create(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\") _T(VENDOR_NAME_STR) _T("\\") _T(PRODUCT_NAME_STR) _T("\\TLSCRL"), NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE)) {
vector<unsigned char> hash;
if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, hash)) {
wstring hash_unicode;
hex_enc enc;
enc.encode(hash_unicode, hash.data(), hash.size());
RegSetValueExW(key, hash_unicode.c_str(), NULL, REG_SZ, reinterpret_cast<LPCBYTE>(subj.c_str()), (DWORD)((subj.length() + 1) * sizeof(wstring::value_type)));
}
}
}}
// Resume checking the rest of the chain.
c += status_rev.dwIndex + 1;
break;
case ERROR_SUCCESS:
// Odd. CertVerifyRevocation() should return TRUE then. Nevertheless, we take this as a "yes".
c = c_end;
break;
default:
// Checking one of the certificates in the chain for revocation failed. Resume checking the rest.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FAILED, event_data((unsigned int)eap_type_ttls), event_data(subj), event_data(status_rev.dwError), event_data::blank);
c += status_rev.dwIndex + 1;
}
} else {
// Revocation check finished.
break;
}
}
// Revocation check succeeded.
obj->m_module.log_event(&EAPMETHOD_TLS_SERVER_CERT_REVOKE_FINISHED, event_data((unsigned int)eap_type_ttls), event_data::blank);
return 0;
}