|
|
@@ -5132,6 +5132,9 @@ bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
|
|
|
bool get_cert_validity(cert_t cert, time_t ¬_before, time_t ¬_after);
|
|
|
std::string get_cert_serial(cert_t cert);
|
|
|
bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
|
|
|
+// The certificates the peer sent, leaf first, in DER form
|
|
|
+bool get_peer_cert_chain_der(const_session_t session,
|
|
|
+ std::vector<std::vector<unsigned char>> &chain);
|
|
|
const char *get_sni(const_session_t session);
|
|
|
|
|
|
// CA store management
|
|
|
@@ -10698,20 +10701,31 @@ inline bool match_hostname(const std::string &pattern,
|
|
|
// Verify certificate using Windows CertGetCertificateChain API.
|
|
|
// This provides real-time certificate validation with Windows Update
|
|
|
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
|
|
|
-inline bool
|
|
|
-verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
|
|
- const std::string &hostname,
|
|
|
- bool verify_hostname, uint64_t &out_error) {
|
|
|
- if (der_cert.empty()) { return false; }
|
|
|
+// `chain` holds the certificates the server sent, leaf first.
|
|
|
+inline bool verify_cert_with_windows_schannel(
|
|
|
+ const std::vector<std::vector<unsigned char>> &chain,
|
|
|
+ const std::string &hostname, bool verify_hostname, uint64_t &out_error) {
|
|
|
+ if (chain.empty() || chain[0].empty()) { return false; }
|
|
|
|
|
|
out_error = 0;
|
|
|
|
|
|
- // Create Windows certificate context from DER data
|
|
|
- auto cert_context = CertCreateCertificateContext(
|
|
|
- X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
|
|
|
- static_cast<DWORD>(der_cert.size()));
|
|
|
+ // Give CryptoAPI the intermediates the server sent, not just the leaf.
|
|
|
+ // Without them it fetches an issuer from the leaf's AIA URL instead, and
|
|
|
+ // that one may chain to a root Windows does not trust even when the chain
|
|
|
+ // the server sent ends at a trusted root.
|
|
|
+ auto store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr);
|
|
|
+ if (!store) {
|
|
|
+ out_error = GetLastError();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto store_guard = scope_exit([&] { CertCloseStore(store, 0); });
|
|
|
|
|
|
- if (!cert_context) {
|
|
|
+ PCCERT_CONTEXT cert_context = nullptr;
|
|
|
+ if (!CertAddEncodedCertificateToStore(
|
|
|
+ store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, chain[0].data(),
|
|
|
+ static_cast<DWORD>(chain[0].size()), CERT_STORE_ADD_ALWAYS,
|
|
|
+ &cert_context)) {
|
|
|
out_error = GetLastError();
|
|
|
return false;
|
|
|
}
|
|
|
@@ -10719,6 +10733,14 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
|
|
auto cert_guard =
|
|
|
scope_exit([&] { CertFreeCertificateContext(cert_context); });
|
|
|
|
|
|
+ // An intermediate CryptoAPI cannot parse is just one less hint for chain
|
|
|
+ // building, so a failure here is not an error by itself.
|
|
|
+ for (size_t i = 1; i < chain.size(); i++) {
|
|
|
+ CertAddEncodedCertificateToStore(
|
|
|
+ store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, chain[i].data(),
|
|
|
+ static_cast<DWORD>(chain[i].size()), CERT_STORE_ADD_ALWAYS, nullptr);
|
|
|
+ }
|
|
|
+
|
|
|
// Setup chain parameters
|
|
|
CERT_CHAIN_PARA chain_para = {};
|
|
|
chain_para.cbSize = sizeof(chain_para);
|
|
|
@@ -10726,7 +10748,7 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
|
|
// Build certificate chain with revocation checking
|
|
|
PCCERT_CHAIN_CONTEXT chain_context = nullptr;
|
|
|
auto chain_result = CertGetCertificateChain(
|
|
|
- nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
|
|
|
+ nullptr, cert_context, nullptr, store, &chain_para,
|
|
|
CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
|
|
|
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
|
|
|
nullptr, &chain_context);
|
|
|
@@ -10945,11 +10967,17 @@ inline bool setup_client_tls_session(
|
|
|
// This provides real-time certificate validation with Windows Update
|
|
|
// integration, working with both OpenSSL and MbedTLS backends.
|
|
|
if (options.windows_cert_verification) {
|
|
|
- std::vector<unsigned char> der;
|
|
|
- if (get_cert_der(server_cert, der)) {
|
|
|
+ std::vector<std::vector<unsigned char>> chain;
|
|
|
+ if (!get_peer_cert_chain_der(session, chain)) {
|
|
|
+ // Fall back to the leaf alone and let CryptoAPI find the issuers
|
|
|
+ chain.clear();
|
|
|
+ std::vector<unsigned char> der;
|
|
|
+ if (get_cert_der(server_cert, der)) { chain.push_back(std::move(der)); }
|
|
|
+ }
|
|
|
+ if (!chain.empty()) {
|
|
|
uint64_t wincrypt_error = 0;
|
|
|
if (!verify_cert_with_windows_schannel(
|
|
|
- der, host, options.server_hostname_verification,
|
|
|
+ chain, host, options.server_hostname_verification,
|
|
|
wincrypt_error)) {
|
|
|
return fail(Error::SSLServerVerification, 0, wincrypt_error);
|
|
|
}
|
|
|
@@ -19708,6 +19736,34 @@ inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+inline bool
|
|
|
+get_peer_cert_chain_der(const_session_t session,
|
|
|
+ std::vector<std::vector<unsigned char>> &chain) {
|
|
|
+ chain.clear();
|
|
|
+ if (!session) return false;
|
|
|
+ auto ssl = static_cast<SSL *>(const_cast<void *>(session));
|
|
|
+
|
|
|
+ // On the server side, SSL_get_peer_cert_chain() leaves out the peer's own
|
|
|
+ // certificate.
|
|
|
+ if (SSL_is_server(ssl)) {
|
|
|
+ auto leaf = get_peer_cert(session);
|
|
|
+ if (!leaf) return false;
|
|
|
+ std::vector<unsigned char> der;
|
|
|
+ auto ok = get_cert_der(leaf, der);
|
|
|
+ free_cert(leaf);
|
|
|
+ if (!ok) return false;
|
|
|
+ chain.push_back(std::move(der));
|
|
|
+ }
|
|
|
+
|
|
|
+ auto sk = SSL_get_peer_cert_chain(ssl);
|
|
|
+ for (int i = 0; sk && i < sk_X509_num(sk); i++) {
|
|
|
+ std::vector<unsigned char> der;
|
|
|
+ if (!get_cert_der(sk_X509_value(sk, i), der)) return false;
|
|
|
+ chain.push_back(std::move(der));
|
|
|
+ }
|
|
|
+ return !chain.empty();
|
|
|
+}
|
|
|
+
|
|
|
inline const char *get_sni(const_session_t session) {
|
|
|
if (!session) return nullptr;
|
|
|
auto ssl = static_cast<SSL *>(const_cast<void *>(session));
|
|
|
@@ -21099,6 +21155,20 @@ inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+inline bool
|
|
|
+get_peer_cert_chain_der(const_session_t session,
|
|
|
+ std::vector<std::vector<unsigned char>> &chain) {
|
|
|
+ chain.clear();
|
|
|
+ // The peer certificate is the head of the list Mbed TLS parsed the whole
|
|
|
+ // received chain into.
|
|
|
+ for (auto crt = static_cast<const mbedtls_x509_crt *>(get_peer_cert(session));
|
|
|
+ crt; crt = crt->next) {
|
|
|
+ if (!crt->raw.p || crt->raw.len == 0) { break; }
|
|
|
+ chain.emplace_back(crt->raw.p, crt->raw.p + crt->raw.len);
|
|
|
+ }
|
|
|
+ return !chain.empty();
|
|
|
+}
|
|
|
+
|
|
|
inline const char *get_sni(const_session_t session) {
|
|
|
if (!session) return nullptr;
|
|
|
auto msession = static_cast<const impl::MbedTlsSession *>(session);
|
|
|
@@ -22278,6 +22348,30 @@ inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+inline bool
|
|
|
+get_peer_cert_chain_der(const_session_t session,
|
|
|
+ std::vector<std::vector<unsigned char>> &chain) {
|
|
|
+ chain.clear();
|
|
|
+ if (!session) return false;
|
|
|
+#ifdef SESSION_CERTS
|
|
|
+ auto wsession =
|
|
|
+ static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
|
|
|
+ auto peer_chain = wolfSSL_get_peer_chain(wsession->ssl);
|
|
|
+ if (!peer_chain) return false;
|
|
|
+ auto count = wolfSSL_get_chain_count(peer_chain);
|
|
|
+ for (int i = 0; i < count; i++) {
|
|
|
+ auto data = wolfSSL_get_chain_cert(peer_chain, i);
|
|
|
+ auto len = wolfSSL_get_chain_length(peer_chain, i);
|
|
|
+ if (!data || len <= 0) return false;
|
|
|
+ chain.emplace_back(data, data + len);
|
|
|
+ }
|
|
|
+ return !chain.empty();
|
|
|
+#else
|
|
|
+ // wolfSSL keeps the received chain only when built with SESSION_CERTS
|
|
|
+ return false;
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
inline const char *get_sni(const_session_t session) {
|
|
|
if (!session) return nullptr;
|
|
|
auto wsession = static_cast<const impl::WolfSSLSession *>(session);
|