Add TLS client events, server probing, and srv cert matching

This allows external programs (e.g., UI) to get more information
about server certificate chain used during TLS handshake. This can
be used both to automatically probe the authentication server to
figure out most likely network configuration and to get information
about reasons for failed authentications.

The follow new control interface events are used for this:
CTRL-EVENT-EAP-PEER-CERT
CTRL-EVENT-EAP-TLS-CERT-ERROR

In addition, there is now an option for matching the server certificate
instead of the full certificate chain for cases where a trusted CA is
not configured or even known. This can be used, e.g., by first probing
the network and learning the server certificate hash based on the new
events and then adding a network configuration with the server
certificate hash after user have accepted it. Future connections will
then be allowed as long as the same server certificate is used.

Authentication server probing can be done, e.g., with following
configuration options:
    eap=TTLS PEAP TLS
    identity=""
    ca_cert="probe://"

Example set of control events for this:
CTRL-EVENT-EAP-STARTED EAP authentication started
CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=21
CTRL-EVENT-EAP-METHOD EAP vendor 0 method 21 (TTLS) selected
CTRL-EVENT-EAP-PEER-CERT depth=0 subject='/C=US/ST=California/L=San Francisco/CN=Server/emailAddress=server@kir.nu' hash=5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a
CTRL-EVENT-EAP-TLS-CERT-ERROR reason=8 depth=0 subject='/C=US/ST=California/L=San Francisco/CN=Server/emailAddress=server@kir.nu' err='Server certificate chain probe'
CTRL-EVENT-EAP-FAILURE EAP authentication failed

Server certificate matching is configured with ca_cert, e.g.:
    ca_cert="hash://server/sha256/5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a"

This functionality is currently available only with OpenSSL. Other
TLS libraries (including internal implementation) may be added in
the future.
This commit is contained in:
Jouni Malinen 2010-02-13 11:14:23 +02:00
parent c7d711609b
commit 00468b4650
7 changed files with 383 additions and 24 deletions

View file

@ -44,6 +44,10 @@ extern "C" {
#define WPA_EVENT_EAP_PROPOSED_METHOD "CTRL-EVENT-EAP-PROPOSED-METHOD "
/** EAP method selected */
#define WPA_EVENT_EAP_METHOD "CTRL-EVENT-EAP-METHOD "
/** EAP peer certificate from TLS */
#define WPA_EVENT_EAP_PEER_CERT "CTRL-EVENT-EAP-PEER-CERT "
/** EAP TLS certificate chain validation error */
#define WPA_EVENT_EAP_TLS_CERT_ERROR "CTRL-EVENT-EAP-TLS-CERT-ERROR "
/** EAP authentication completed successfully */
#define WPA_EVENT_EAP_SUCCESS "CTRL-EVENT-EAP-SUCCESS "
/** EAP authentication failed (EAP-Failure received) */

View file

@ -1,6 +1,6 @@
/*
* SSL/TLS interface definition
* Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2004-2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -28,11 +28,54 @@ struct tls_keys {
size_t inner_secret_len;
};
enum tls_event {
TLS_CERT_CHAIN_FAILURE,
TLS_PEER_CERTIFICATE
};
/*
* Note: These are used as identifier with external programs and as such, the
* values must not be changed.
*/
enum tls_fail_reason {
TLS_FAIL_UNSPECIFIED = 0,
TLS_FAIL_UNTRUSTED = 1,
TLS_FAIL_REVOKED = 2,
TLS_FAIL_NOT_YET_VALID = 3,
TLS_FAIL_EXPIRED = 4,
TLS_FAIL_SUBJECT_MISMATCH = 5,
TLS_FAIL_ALTSUBJECT_MISMATCH = 6,
TLS_FAIL_BAD_CERTIFICATE = 7,
TLS_FAIL_SERVER_CHAIN_PROBE = 8
};
union tls_event_data {
struct {
int depth;
const char *subject;
enum tls_fail_reason reason;
const char *reason_txt;
const struct wpabuf *cert;
} cert_fail;
struct {
int depth;
const char *subject;
const struct wpabuf *cert;
const u8 *hash;
size_t hash_len;
} peer_cert;
};
struct tls_config {
const char *opensc_engine_path;
const char *pkcs11_engine_path;
const char *pkcs11_module_path;
int fips_mode;
void (*event_cb)(void *ctx, enum tls_event ev,
union tls_event_data *data);
void *cb_ctx;
};
#define TLS_CONN_ALLOW_SIGN_RSA_MD5 BIT(0)

View file

@ -1,6 +1,6 @@
/*
* SSL/TLS interface functions for OpenSSL
* Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2004-2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -29,6 +29,7 @@
#endif /* OPENSSL_NO_ENGINE */
#include "common.h"
#include "crypto.h"
#include "tls.h"
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
@ -49,6 +50,15 @@
static int tls_openssl_ref_count = 0;
struct tls_global {
void (*event_cb)(void *ctx, enum tls_event ev,
union tls_event_data *data);
void *cb_ctx;
};
static struct tls_global *tls_global = NULL;
struct tls_connection {
SSL *ssl;
BIO *ssl_in, *ssl_out;
@ -65,6 +75,12 @@ struct tls_connection {
/* SessionTicket received from OpenSSL hello_extension_cb (server) */
u8 *session_ticket;
size_t session_ticket_len;
int ca_cert_verify:1;
int cert_probe:1;
int server_cert_only:1;
u8 srv_cert_hash[32];
};
@ -665,6 +681,14 @@ void * tls_init(const struct tls_config *conf)
SSL_CTX *ssl;
if (tls_openssl_ref_count == 0) {
tls_global = os_zalloc(sizeof(*tls_global));
if (tls_global == NULL)
return NULL;
if (conf) {
tls_global->event_cb = conf->event_cb;
tls_global->cb_ctx = conf->cb_ctx;
}
#ifdef CONFIG_FIPS
#ifdef OPENSSL_FIPS
if (conf && conf->fips_mode) {
@ -750,6 +774,8 @@ void tls_deinit(void *ssl_ctx)
ERR_remove_state(0);
ERR_free_strings();
EVP_cleanup();
os_free(tls_global);
tls_global = NULL;
}
}
@ -1016,6 +1042,124 @@ static int tls_match_altsubject(X509 *cert, const char *match)
}
static enum tls_fail_reason openssl_tls_fail_reason(int err)
{
switch (err) {
case X509_V_ERR_CERT_REVOKED:
return TLS_FAIL_REVOKED;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CRL_NOT_YET_VALID:
return TLS_FAIL_NOT_YET_VALID;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_CRL_HAS_EXPIRED:
return TLS_FAIL_EXPIRED;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
case X509_V_ERR_UNABLE_TO_GET_CRL:
case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
case X509_V_ERR_INVALID_CA:
return TLS_FAIL_UNTRUSTED;
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
case X509_V_ERR_CERT_UNTRUSTED:
case X509_V_ERR_CERT_REJECTED:
return TLS_FAIL_BAD_CERTIFICATE;
default:
return TLS_FAIL_UNSPECIFIED;
}
}
static struct wpabuf * get_x509_cert(X509 *cert)
{
struct wpabuf *buf;
u8 *tmp;
int cert_len = i2d_X509(cert, NULL);
if (cert_len <= 0)
return NULL;
buf = wpabuf_alloc(cert_len);
if (buf == NULL)
return NULL;
tmp = wpabuf_put(buf, cert_len);
i2d_X509(cert, &tmp);
return buf;
}
static void openssl_tls_fail_event(struct tls_connection *conn,
X509 *err_cert, int err, int depth,
const char *subject, const char *err_str,
enum tls_fail_reason reason)
{
union tls_event_data ev;
struct wpabuf *cert = NULL;
if (tls_global->event_cb == NULL)
return;
cert = get_x509_cert(err_cert);
os_memset(&ev, 0, sizeof(ev));
ev.cert_fail.reason = reason != TLS_FAIL_UNSPECIFIED ?
reason : openssl_tls_fail_reason(err);
ev.cert_fail.depth = depth;
ev.cert_fail.subject = subject;
ev.cert_fail.reason_txt = err_str;
ev.cert_fail.cert = cert;
tls_global->event_cb(tls_global->cb_ctx, TLS_CERT_CHAIN_FAILURE, &ev);
wpabuf_free(cert);
}
static void openssl_tls_cert_event(struct tls_connection *conn,
X509 *err_cert, int depth,
const char *subject)
{
struct wpabuf *cert = NULL;
union tls_event_data ev;
#ifdef CONFIG_SHA256
u8 hash[32];
#endif /* CONFIG_SHA256 */
if (tls_global->event_cb == NULL)
return;
os_memset(&ev, 0, sizeof(ev));
if (conn->cert_probe) {
cert = get_x509_cert(err_cert);
ev.peer_cert.cert = cert;
}
#ifdef CONFIG_SHA256
if (cert) {
const u8 *addr[1];
size_t len[1];
addr[0] = wpabuf_head(cert);
len[0] = wpabuf_len(cert);
if (sha256_vector(1, addr, len, hash) == 0) {
ev.peer_cert.hash = hash;
ev.peer_cert.hash_len = sizeof(hash);
}
}
#endif /* CONFIG_SHA256 */
ev.peer_cert.depth = depth;
ev.peer_cert.subject = subject;
tls_global->event_cb(tls_global->cb_ctx, TLS_PEER_CERTIFICATE, &ev);
wpabuf_free(cert);
}
static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
char buf[256];
@ -1024,6 +1168,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
SSL *ssl;
struct tls_connection *conn;
char *match, *altmatch;
const char *err_str;
err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
err = X509_STORE_CTX_get_error(x509_ctx);
@ -1036,25 +1181,76 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
match = conn ? conn->subject_match : NULL;
altmatch = conn ? conn->altsubject_match : NULL;
if (!preverify_ok && !conn->ca_cert_verify)
preverify_ok = 1;
if (!preverify_ok && depth > 0 && conn->server_cert_only)
preverify_ok = 1;
err_str = X509_verify_cert_error_string(err);
#ifdef CONFIG_SHA256
if (preverify_ok && depth == 0 && conn->server_cert_only) {
struct wpabuf *cert;
cert = get_x509_cert(err_cert);
if (!cert) {
wpa_printf(MSG_DEBUG, "OpenSSL: Could not fetch "
"server certificate data");
preverify_ok = 0;
} else {
u8 hash[32];
const u8 *addr[1];
size_t len[1];
addr[0] = wpabuf_head(cert);
len[0] = wpabuf_len(cert);
if (sha256_vector(1, addr, len, hash) < 0 ||
os_memcmp(conn->srv_cert_hash, hash, 32) != 0) {
err_str = "Server certificate mismatch";
err = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
preverify_ok = 0;
}
wpabuf_free(cert);
}
}
#endif /* CONFIG_SHA256 */
if (!preverify_ok) {
wpa_printf(MSG_WARNING, "TLS: Certificate verification failed,"
" error %d (%s) depth %d for '%s'", err,
X509_verify_cert_error_string(err), depth, buf);
} else {
wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - "
"preverify_ok=%d err=%d (%s) depth=%d buf='%s'",
preverify_ok, err,
X509_verify_cert_error_string(err), depth, buf);
if (depth == 0 && match && os_strstr(buf, match) == NULL) {
wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not "
"match with '%s'", buf, match);
preverify_ok = 0;
} else if (depth == 0 && altmatch &&
!tls_match_altsubject(err_cert, altmatch)) {
wpa_printf(MSG_WARNING, "TLS: altSubjectName match "
"'%s' not found", altmatch);
preverify_ok = 0;
}
" error %d (%s) depth %d for '%s'", err, err_str,
depth, buf);
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
err_str, TLS_FAIL_UNSPECIFIED);
return preverify_ok;
}
wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - preverify_ok=%d "
"err=%d (%s) ca_cert_verify=%d depth=%d buf='%s'",
preverify_ok, err, err_str,
conn->ca_cert_verify, depth, buf);
if (depth == 0 && match && os_strstr(buf, match) == NULL) {
wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not "
"match with '%s'", buf, match);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Subject mismatch",
TLS_FAIL_SUBJECT_MISMATCH);
} else if (depth == 0 && altmatch &&
!tls_match_altsubject(err_cert, altmatch)) {
wpa_printf(MSG_WARNING, "TLS: altSubjectName match "
"'%s' not found", altmatch);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"AltSubject mismatch",
TLS_FAIL_ALTSUBJECT_MISMATCH);
} else
openssl_tls_cert_event(conn, err_cert, depth, buf);
if (conn->cert_probe && preverify_ok && depth == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Reject server certificate "
"on probe-only run");
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Server certificate chain probe",
TLS_FAIL_SERVER_CHAIN_PROBE);
}
return preverify_ok;
@ -1112,6 +1308,47 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn,
return -1;
}
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb);
conn->ca_cert_verify = 1;
if (ca_cert && os_strncmp(ca_cert, "probe://", 8) == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Probe for server certificate "
"chain");
conn->cert_probe = 1;
conn->ca_cert_verify = 0;
return 0;
}
if (ca_cert && os_strncmp(ca_cert, "hash://", 7) == 0) {
#ifdef CONFIG_SHA256
const char *pos = ca_cert + 7;
if (os_strncmp(pos, "server/sha256/", 14) != 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Unsupported ca_cert "
"hash value '%s'", ca_cert);
return -1;
}
pos += 14;
if (os_strlen(pos) != 32 * 2) {
wpa_printf(MSG_DEBUG, "OpenSSL: Unexpected SHA256 "
"hash length in ca_cert '%s'", ca_cert);
return -1;
}
if (hexstr2bin(pos, conn->srv_cert_hash, 32) < 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Invalid SHA256 hash "
"value in ca_cert '%s'", ca_cert);
return -1;
}
conn->server_cert_only = 1;
wpa_printf(MSG_DEBUG, "OpenSSL: Checking only server "
"certificate match");
return 0;
#else /* CONFIG_SHA256 */
wpa_printf(MSG_INFO, "No SHA256 included in the build - "
"cannot validate server certificate hash");
return -1;
#endif /* CONFIG_SHA256 */
}
if (ca_cert_blob) {
X509 *cert = d2i_X509(NULL, (OPENSSL_d2i_TYPE) &ca_cert_blob,
ca_cert_blob_len);
@ -1140,7 +1377,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn,
X509_free(cert);
wpa_printf(MSG_DEBUG, "OpenSSL: %s - added ca_cert_blob "
"to certificate store", __func__);
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb);
return 0;
}
@ -1149,7 +1385,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn,
0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Added CA certificates from "
"system certificate store");
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb);
return 0;
}
#endif /* CONFIG_NATIVE_WINDOWS */
@ -1172,7 +1407,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn,
"certificate(s) loaded");
tls_get_errors(ssl_ctx);
}
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb);
#else /* OPENSSL_NO_STDIO */
wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO",
__func__);
@ -1181,7 +1415,7 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn,
} else {
/* No ca_cert configured - do not try to verify server
* certificate */
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
conn->ca_cert_verify = 0;
}
return 0;
@ -1266,10 +1500,12 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
return -1;
if (verify_peer) {
conn->ca_cert_verify = 1;
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
SSL_VERIFY_CLIENT_ONCE, tls_verify_cb);
} else {
conn->ca_cert_verify = 0;
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
}

View file

@ -1,6 +1,6 @@
/*
* EAP peer state machines (RFC 4137)
* Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
* Copyright (c) 2004-2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -1160,6 +1160,60 @@ static void eap_sm_parseEapReq(struct eap_sm *sm, const struct wpabuf *req)
}
static void eap_peer_sm_tls_event(void *ctx, enum tls_event ev,
union tls_event_data *data)
{
struct eap_sm *sm = ctx;
char *hash_hex = NULL;
char *cert_hex = NULL;
switch (ev) {
case TLS_CERT_CHAIN_FAILURE:
wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_TLS_CERT_ERROR
"reason=%d depth=%d subject='%s' err='%s'",
data->cert_fail.reason,
data->cert_fail.depth,
data->cert_fail.subject,
data->cert_fail.reason_txt);
break;
case TLS_PEER_CERTIFICATE:
if (data->peer_cert.hash) {
size_t len = data->peer_cert.hash_len * 2 + 1;
hash_hex = os_malloc(len);
if (hash_hex) {
wpa_snprintf_hex(hash_hex, len,
data->peer_cert.hash,
data->peer_cert.hash_len);
}
}
wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_PEER_CERT
"depth=%d subject='%s'%s%s",
data->peer_cert.depth, data->peer_cert.subject,
hash_hex ? " hash=" : "", hash_hex ? hash_hex : "");
if (data->peer_cert.cert) {
size_t len = wpabuf_len(data->peer_cert.cert) * 2 + 1;
cert_hex = os_malloc(len);
if (cert_hex == NULL)
break;
wpa_snprintf_hex(cert_hex, len,
wpabuf_head(data->peer_cert.cert),
wpabuf_len(data->peer_cert.cert));
wpa_msg_ctrl(sm->msg_ctx, MSG_INFO,
WPA_EVENT_EAP_PEER_CERT
"depth=%d subject='%s' cert=%s",
data->peer_cert.depth,
data->peer_cert.subject,
cert_hex);
}
break;
}
os_free(hash_hex);
os_free(cert_hex);
}
/**
* eap_peer_sm_init - Allocate and initialize EAP peer state machine
* @eapol_ctx: Context data to be used with eapol_cb calls
@ -1197,6 +1251,8 @@ struct eap_sm * eap_peer_sm_init(void *eapol_ctx,
#ifdef CONFIG_FIPS
tlsconf.fips_mode = 1;
#endif /* CONFIG_FIPS */
tlsconf.event_cb = eap_peer_sm_tls_event;
tlsconf.cb_ctx = sm;
sm->ssl_ctx = tls_init(&tlsconf);
if (sm->ssl_ctx == NULL) {
wpa_printf(MSG_WARNING, "SSL: Failed to initialize TLS "

View file

@ -85,6 +85,15 @@ struct eap_peer_config {
* Alternatively, a named configuration blob can be used by setting
* this to blob://blob_name.
*
* Alternatively, this can be used to only perform matching of the
* server certificate (SHA-256 hash of the DER encoded X.509
* certificate). In this case, the possible CA certificates in the
* server certificate chain are ignored and only the server certificate
* is verified. This is configured with the following format:
* hash:://server/sha256/cert_hash_in_hex
* For example: "hash://server/sha256/
* 5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a"
*
* On Windows, trusted CA certificates can be loaded from the system
* certificate store by setting this to cert_store://name, e.g.,
* ca_cert="cert_store://CA" or ca_cert="cert_store://ROOT".

View file

@ -999,6 +999,7 @@ endif
SHA256OBJS = # none by default
ifdef NEED_SHA256
CFLAGS += -DCONFIG_SHA256
SHA256OBJS += ../src/crypto/sha256.o
ifdef CONFIG_INTERNAL_SHA256
SHA256OBJS += ../src/crypto/sha256-internal.o

View file

@ -390,6 +390,16 @@ fast_reauth=1
# a trusted CA certificate should always be configured when using
# EAP-TLS/TTLS/PEAP. Full path should be used since working directory may
# change when wpa_supplicant is run in the background.
#
# Alternatively, this can be used to only perform matching of the server
# certificate (SHA-256 hash of the DER encoded X.509 certificate). In
# this case, the possible CA certificates in the server certificate chain
# are ignored and only the server certificate is verified. This is
# configured with the following format:
# hash:://server/sha256/cert_hash_in_hex
# For example: "hash://server/sha256/
# 5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a"
#
# On Windows, trusted CA certificates can be loaded from the system
# certificate store by setting this to cert_store://<name>, e.g.,
# ca_cert="cert_store://CA" or ca_cert="cert_store://ROOT".