| /* |
| * SSL/TLS interface functions for OpenSSL |
| * Copyright (c) 2004-2015, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #ifdef CONFIG_TESTING_OPTIONS |
| #include <fcntl.h> |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| |
| #ifndef CONFIG_SMARTCARD |
| #ifndef OPENSSL_NO_ENGINE |
| #ifndef ANDROID |
| #define OPENSSL_NO_ENGINE |
| #endif |
| #endif |
| #endif |
| |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| #include <openssl/opensslv.h> |
| #include <openssl/pkcs12.h> |
| #include <openssl/x509v3.h> |
| #ifndef OPENSSL_NO_ENGINE |
| #include <openssl/engine.h> |
| #endif /* OPENSSL_NO_ENGINE */ |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| #include <openssl/core_names.h> |
| #include <openssl/decoder.h> |
| #include <openssl/param_build.h> |
| #else /* OpenSSL version >= 3.0 */ |
| #ifndef OPENSSL_NO_DSA |
| #include <openssl/dsa.h> |
| #endif |
| #ifndef OPENSSL_NO_DH |
| #include <openssl/dh.h> |
| #endif |
| #endif /* OpenSSL version >= 3.0 */ |
| |
| #include "common.h" |
| #include "utils/list.h" |
| #include "crypto.h" |
| #include "sha1.h" |
| #include "sha256.h" |
| #include "tls.h" |
| #include "tls_openssl.h" |
| |
| #if !defined(CONFIG_FIPS) && \ |
| (defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || \ |
| defined(EAP_SERVER_FAST)) |
| #define OPENSSL_NEED_EAP_FAST_PRF |
| #endif |
| |
| #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || \ |
| defined(EAP_SERVER_FAST) || defined(EAP_TEAP) || \ |
| defined(EAP_SERVER_TEAP) |
| #define EAP_FAST_OR_TEAP |
| #endif |
| |
| |
| #if defined(OPENSSL_IS_BORINGSSL) |
| /* stack_index_t is the return type of OpenSSL's sk_XXX_num() functions. */ |
| typedef size_t stack_index_t; |
| #else |
| typedef int stack_index_t; |
| #endif |
| |
| #ifdef SSL_set_tlsext_status_type |
| #ifndef OPENSSL_NO_TLSEXT |
| #define HAVE_OCSP |
| #include <openssl/ocsp.h> |
| #endif /* OPENSSL_NO_TLSEXT */ |
| #endif /* SSL_set_tlsext_status_type */ |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L && \ |
| !defined(BORINGSSL_API_VERSION) |
| /* |
| * SSL_get_client_random() and SSL_get_server_random() were added in OpenSSL |
| * 1.1.0 and newer BoringSSL revisions. Provide compatibility wrappers for |
| * older versions. |
| */ |
| |
| static size_t SSL_get_client_random(const SSL *ssl, unsigned char *out, |
| size_t outlen) |
| { |
| if (!ssl->s3 || outlen < SSL3_RANDOM_SIZE) |
| return 0; |
| os_memcpy(out, ssl->s3->client_random, SSL3_RANDOM_SIZE); |
| return SSL3_RANDOM_SIZE; |
| } |
| |
| |
| static size_t SSL_get_server_random(const SSL *ssl, unsigned char *out, |
| size_t outlen) |
| { |
| if (!ssl->s3 || outlen < SSL3_RANDOM_SIZE) |
| return 0; |
| os_memcpy(out, ssl->s3->server_random, SSL3_RANDOM_SIZE); |
| return SSL3_RANDOM_SIZE; |
| } |
| |
| |
| #ifdef OPENSSL_NEED_EAP_FAST_PRF |
| static size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, |
| unsigned char *out, size_t outlen) |
| { |
| if (!session || session->master_key_length < 0 || |
| (size_t) session->master_key_length > outlen) |
| return 0; |
| if ((size_t) session->master_key_length < outlen) |
| outlen = session->master_key_length; |
| os_memcpy(out, session->master_key, outlen); |
| return outlen; |
| } |
| #endif /* OPENSSL_NEED_EAP_FAST_PRF */ |
| |
| #endif |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| static const unsigned char * ASN1_STRING_get0_data(const ASN1_STRING *x) |
| { |
| return ASN1_STRING_data((ASN1_STRING *) x); |
| } |
| #endif |
| |
| static int tls_openssl_ref_count = 0; |
| static int tls_ex_idx_session = -1; |
| |
| struct tls_session_data { |
| struct dl_list list; |
| struct wpabuf *buf; |
| }; |
| |
| struct tls_context { |
| void (*event_cb)(void *ctx, enum tls_event ev, |
| union tls_event_data *data); |
| void *cb_ctx; |
| int cert_in_cb; |
| char *ocsp_stapling_response; |
| struct dl_list sessions; /* struct tls_session_data */ |
| }; |
| |
| struct tls_data { |
| SSL_CTX *ssl; |
| unsigned int tls_session_lifetime; |
| int check_crl; |
| int check_crl_strict; |
| char *ca_cert; |
| unsigned int crl_reload_interval; |
| struct os_reltime crl_last_reload; |
| char *check_cert_subject; |
| }; |
| |
| struct tls_connection { |
| struct tls_context *context; |
| struct tls_data *data; |
| SSL_CTX *ssl_ctx; |
| SSL *ssl; |
| BIO *ssl_in, *ssl_out; |
| #if defined(ANDROID) || !defined(OPENSSL_NO_ENGINE) |
| ENGINE *engine; /* functional reference to the engine */ |
| EVP_PKEY *private_key; /* the private key if using engine */ |
| #endif /* OPENSSL_NO_ENGINE */ |
| char *subject_match, *altsubject_match, *suffix_match, *domain_match; |
| char *check_cert_subject; |
| int read_alerts, write_alerts, failed; |
| |
| tls_session_ticket_cb session_ticket_cb; |
| void *session_ticket_cb_ctx; |
| |
| /* SessionTicket received from OpenSSL hello_extension_cb (server) */ |
| u8 *session_ticket; |
| size_t session_ticket_len; |
| |
| unsigned int ca_cert_verify:1; |
| unsigned int cert_probe:1; |
| unsigned int server_cert_only:1; |
| unsigned int invalid_hb_used:1; |
| unsigned int success_data:1; |
| unsigned int client_hello_generated:1; |
| unsigned int server:1; |
| |
| u8 srv_cert_hash[32]; |
| |
| unsigned int flags; |
| |
| X509 *peer_cert; |
| X509 *peer_issuer; |
| X509 *peer_issuer_issuer; |
| char *peer_subject; /* peer subject info for authenticated peer */ |
| |
| unsigned char client_random[SSL3_RANDOM_SIZE]; |
| unsigned char server_random[SSL3_RANDOM_SIZE]; |
| |
| u16 cipher_suite; |
| int server_dh_prime_len; |
| }; |
| |
| static struct tls_context *tls_global = NULL; |
| static tls_get_certificate_cb certificate_callback_global = NULL; |
| static tls_openssl_failure_cb openssl_failure_callback_global = NULL; |
| |
| #ifdef ANDROID |
| #include <openssl/pem.h> |
| |
| #include <log/log.h> |
| #include <log/log_event_list.h> |
| |
| #define CERT_VALIDATION_FAILURE 210033 |
| #define ANDROID_KEYSTORE_PREFIX "keystore://" |
| #define ANDROID_KEYSTORE_PREFIX_LEN os_strlen(ANDROID_KEYSTORE_PREFIX) |
| #define ANDROID_KEYSTORE_ENCODED_PREFIX "keystores://" |
| #define ANDROID_KEYSTORE_ENCODED_PREFIX_LEN os_strlen(ANDROID_KEYSTORE_ENCODED_PREFIX) |
| |
| static void log_cert_validation_failure(const char *reason) |
| { |
| android_log_context ctx = create_android_logger(CERT_VALIDATION_FAILURE); |
| android_log_write_string8(ctx, reason); |
| android_log_write_list(ctx, LOG_ID_SECURITY); |
| android_log_destroy(&ctx); |
| } |
| |
| |
| static BIO* BIO_from_keystore(const char *alias, struct tls_connection *conn) |
| { |
| BIO *bio = NULL; |
| uint8_t *value = NULL; |
| |
| void *cb_ctx = NULL; |
| if (conn != NULL && conn->context != NULL) { |
| cb_ctx = conn->context->cb_ctx; |
| } |
| |
| if (cb_ctx != NULL && certificate_callback_global != NULL) { |
| wpa_printf(MSG_INFO, "Retrieving certificate using callback"); |
| int length = (*certificate_callback_global)(cb_ctx, alias, &value); |
| if (length != -1 && (bio = BIO_new(BIO_s_mem())) != NULL) |
| BIO_write(bio, value, length); |
| free(value); |
| } |
| return bio; |
| } |
| |
| static int tls_add_ca_from_keystore(X509_STORE *ctx, const char *alias, struct tls_connection *conn) |
| { |
| BIO *bio = BIO_from_keystore(alias, conn); |
| STACK_OF(X509_INFO) *stack = NULL; |
| stack_index_t i; |
| int ret = 0; |
| |
| if (!bio) { |
| wpa_printf(MSG_ERROR, "OpenSSL: Failed to parse certificate: %s", |
| alias); |
| return -1; |
| } |
| |
| // Keystore returns X.509 certificates in PEM encoding |
| stack = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); |
| BIO_free(bio); |
| |
| if (!stack) { |
| wpa_printf(MSG_ERROR, "OpenSSL: Failed to parse certificate: %s", |
| alias); |
| return -1; |
| } |
| |
| for (i = 0; i < sk_X509_INFO_num(stack); ++i) { |
| X509_INFO *info = sk_X509_INFO_value(stack, i); |
| |
| if (info->x509) |
| if (!X509_STORE_add_cert(ctx, info->x509)) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Failed to add Root CA certificate"); |
| ret = -1; |
| break; |
| } |
| if (info->crl) |
| X509_STORE_add_crl(ctx, info->crl); |
| } |
| |
| sk_X509_INFO_pop_free(stack, X509_INFO_free); |
| return ret; |
| } |
| |
| |
| static int tls_add_ca_from_keystore_encoded(X509_STORE *ctx, |
| const char *encoded_alias, |
| struct tls_connection *conn) |
| { |
| int rc = -1; |
| int len = os_strlen(encoded_alias); |
| unsigned char *decoded_alias; |
| |
| if (len & 1) { |
| wpa_printf(MSG_WARNING, "Invalid hex-encoded alias: %s", |
| encoded_alias); |
| return rc; |
| } |
| |
| decoded_alias = os_malloc(len / 2 + 1); |
| if (decoded_alias) { |
| if (!hexstr2bin(encoded_alias, decoded_alias, len / 2)) { |
| decoded_alias[len / 2] = '\0'; |
| rc = tls_add_ca_from_keystore( |
| ctx, (const char *) decoded_alias, conn); |
| } |
| os_free(decoded_alias); |
| } |
| |
| return rc; |
| } |
| |
| #endif /* ANDROID */ |
| |
| |
| static struct tls_context * tls_context_new(const struct tls_config *conf) |
| { |
| struct tls_context *context = os_zalloc(sizeof(*context)); |
| if (context == NULL) |
| return NULL; |
| dl_list_init(&context->sessions); |
| if (conf) { |
| context->event_cb = conf->event_cb; |
| context->cb_ctx = conf->cb_ctx; |
| context->cert_in_cb = conf->cert_in_cb; |
| } |
| return context; |
| } |
| |
| |
| #ifdef CONFIG_NO_STDOUT_DEBUG |
| |
| static void _tls_show_errors(void) |
| { |
| unsigned long err; |
| |
| while ((err = ERR_get_error())) { |
| /* Just ignore the errors, since stdout is disabled */ |
| } |
| } |
| #define tls_show_errors(l, f, t) _tls_show_errors() |
| |
| #else /* CONFIG_NO_STDOUT_DEBUG */ |
| |
| static void tls_show_errors(int level, const char *func, const char *txt) |
| { |
| unsigned long err; |
| |
| wpa_printf(level, "OpenSSL: %s - %s %s", |
| func, txt, ERR_error_string(ERR_get_error(), NULL)); |
| |
| while ((err = ERR_get_error())) { |
| wpa_printf(MSG_INFO, "OpenSSL: pending error: %s", |
| ERR_error_string(err, NULL)); |
| } |
| } |
| |
| #endif /* CONFIG_NO_STDOUT_DEBUG */ |
| |
| |
| static X509_STORE * tls_crl_cert_reload(const char *ca_cert, int check_crl) |
| { |
| int flags; |
| X509_STORE *store; |
| |
| store = X509_STORE_new(); |
| if (!store) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: %s - failed to allocate new certificate store", |
| __func__); |
| return NULL; |
| } |
| |
| if (ca_cert && X509_STORE_load_locations(store, ca_cert, NULL) != 1) { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to load root certificates"); |
| X509_STORE_free(store); |
| return NULL; |
| } |
| |
| flags = check_crl ? X509_V_FLAG_CRL_CHECK : 0; |
| if (check_crl == 2) |
| flags |= X509_V_FLAG_CRL_CHECK_ALL; |
| |
| X509_STORE_set_flags(store, flags); |
| |
| return store; |
| } |
| |
| |
| #ifdef CONFIG_NATIVE_WINDOWS |
| |
| /* Windows CryptoAPI and access to certificate stores */ |
| #include <wincrypt.h> |
| |
| #ifdef __MINGW32_VERSION |
| /* |
| * MinGW does not yet include all the needed definitions for CryptoAPI, so |
| * define here whatever extra is needed. |
| */ |
| #define CERT_SYSTEM_STORE_CURRENT_USER (1 << 16) |
| #define CERT_STORE_READONLY_FLAG 0x00008000 |
| #define CERT_STORE_OPEN_EXISTING_FLAG 0x00004000 |
| |
| #endif /* __MINGW32_VERSION */ |
| |
| |
| struct cryptoapi_rsa_data { |
| const CERT_CONTEXT *cert; |
| HCRYPTPROV crypt_prov; |
| DWORD key_spec; |
| BOOL free_crypt_prov; |
| }; |
| |
| |
| static void cryptoapi_error(const char *msg) |
| { |
| wpa_printf(MSG_INFO, "CryptoAPI: %s; err=%u", |
| msg, (unsigned int) GetLastError()); |
| } |
| |
| |
| static int cryptoapi_rsa_pub_enc(int flen, const unsigned char *from, |
| unsigned char *to, RSA *rsa, int padding) |
| { |
| wpa_printf(MSG_DEBUG, "%s - not implemented", __func__); |
| return 0; |
| } |
| |
| |
| static int cryptoapi_rsa_pub_dec(int flen, const unsigned char *from, |
| unsigned char *to, RSA *rsa, int padding) |
| { |
| wpa_printf(MSG_DEBUG, "%s - not implemented", __func__); |
| return 0; |
| } |
| |
| |
| static int cryptoapi_rsa_priv_enc(int flen, const unsigned char *from, |
| unsigned char *to, RSA *rsa, int padding) |
| { |
| struct cryptoapi_rsa_data *priv = |
| (struct cryptoapi_rsa_data *) rsa->meth->app_data; |
| HCRYPTHASH hash; |
| DWORD hash_size, len, i; |
| unsigned char *buf = NULL; |
| int ret = 0; |
| |
| if (priv == NULL) { |
| RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, |
| ERR_R_PASSED_NULL_PARAMETER); |
| return 0; |
| } |
| |
| if (padding != RSA_PKCS1_PADDING) { |
| RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, |
| RSA_R_UNKNOWN_PADDING_TYPE); |
| return 0; |
| } |
| |
| if (flen != 16 /* MD5 */ + 20 /* SHA-1 */) { |
| wpa_printf(MSG_INFO, "%s - only MD5-SHA1 hash supported", |
| __func__); |
| RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, |
| RSA_R_INVALID_MESSAGE_LENGTH); |
| return 0; |
| } |
| |
| if (!CryptCreateHash(priv->crypt_prov, CALG_SSL3_SHAMD5, 0, 0, &hash)) |
| { |
| cryptoapi_error("CryptCreateHash failed"); |
| return 0; |
| } |
| |
| len = sizeof(hash_size); |
| if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &hash_size, &len, |
| 0)) { |
| cryptoapi_error("CryptGetHashParam failed"); |
| goto err; |
| } |
| |
| if ((int) hash_size != flen) { |
| wpa_printf(MSG_INFO, "CryptoAPI: Invalid hash size (%u != %d)", |
| (unsigned) hash_size, flen); |
| RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, |
| RSA_R_INVALID_MESSAGE_LENGTH); |
| goto err; |
| } |
| if (!CryptSetHashParam(hash, HP_HASHVAL, (BYTE * ) from, 0)) { |
| cryptoapi_error("CryptSetHashParam failed"); |
| goto err; |
| } |
| |
| len = RSA_size(rsa); |
| buf = os_malloc(len); |
| if (buf == NULL) { |
| RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_MALLOC_FAILURE); |
| goto err; |
| } |
| |
| if (!CryptSignHash(hash, priv->key_spec, NULL, 0, buf, &len)) { |
| cryptoapi_error("CryptSignHash failed"); |
| goto err; |
| } |
| |
| for (i = 0; i < len; i++) |
| to[i] = buf[len - i - 1]; |
| ret = len; |
| |
| err: |
| os_free(buf); |
| CryptDestroyHash(hash); |
| |
| return ret; |
| } |
| |
| |
| static int cryptoapi_rsa_priv_dec(int flen, const unsigned char *from, |
| unsigned char *to, RSA *rsa, int padding) |
| { |
| wpa_printf(MSG_DEBUG, "%s - not implemented", __func__); |
| return 0; |
| } |
| |
| |
| static void cryptoapi_free_data(struct cryptoapi_rsa_data *priv) |
| { |
| if (priv == NULL) |
| return; |
| if (priv->crypt_prov && priv->free_crypt_prov) |
| CryptReleaseContext(priv->crypt_prov, 0); |
| if (priv->cert) |
| CertFreeCertificateContext(priv->cert); |
| os_free(priv); |
| } |
| |
| |
| static int cryptoapi_finish(RSA *rsa) |
| { |
| cryptoapi_free_data((struct cryptoapi_rsa_data *) rsa->meth->app_data); |
| os_free((void *) rsa->meth); |
| rsa->meth = NULL; |
| return 1; |
| } |
| |
| |
| static const CERT_CONTEXT * cryptoapi_find_cert(const char *name, DWORD store) |
| { |
| HCERTSTORE cs; |
| const CERT_CONTEXT *ret = NULL; |
| |
| cs = CertOpenStore((LPCSTR) CERT_STORE_PROV_SYSTEM, 0, 0, |
| store | CERT_STORE_OPEN_EXISTING_FLAG | |
| CERT_STORE_READONLY_FLAG, L"MY"); |
| if (cs == NULL) { |
| cryptoapi_error("Failed to open 'My system store'"); |
| return NULL; |
| } |
| |
| if (strncmp(name, "cert://", 7) == 0) { |
| unsigned short wbuf[255]; |
| MultiByteToWideChar(CP_ACP, 0, name + 7, -1, wbuf, 255); |
| ret = CertFindCertificateInStore(cs, X509_ASN_ENCODING | |
| PKCS_7_ASN_ENCODING, |
| 0, CERT_FIND_SUBJECT_STR, |
| wbuf, NULL); |
| } else if (strncmp(name, "hash://", 7) == 0) { |
| CRYPT_HASH_BLOB blob; |
| int len; |
| const char *hash = name + 7; |
| unsigned char *buf; |
| |
| len = os_strlen(hash) / 2; |
| buf = os_malloc(len); |
| if (buf && hexstr2bin(hash, buf, len) == 0) { |
| blob.cbData = len; |
| blob.pbData = buf; |
| ret = CertFindCertificateInStore(cs, |
| X509_ASN_ENCODING | |
| PKCS_7_ASN_ENCODING, |
| 0, CERT_FIND_HASH, |
| &blob, NULL); |
| } |
| os_free(buf); |
| } |
| |
| CertCloseStore(cs, 0); |
| |
| return ret; |
| } |
| |
| |
| static int tls_cryptoapi_cert(SSL *ssl, const char *name) |
| { |
| X509 *cert = NULL; |
| RSA *rsa = NULL, *pub_rsa; |
| struct cryptoapi_rsa_data *priv; |
| RSA_METHOD *rsa_meth; |
| |
| if (name == NULL || |
| (strncmp(name, "cert://", 7) != 0 && |
| strncmp(name, "hash://", 7) != 0)) |
| return -1; |
| |
| priv = os_zalloc(sizeof(*priv)); |
| rsa_meth = os_zalloc(sizeof(*rsa_meth)); |
| if (priv == NULL || rsa_meth == NULL) { |
| wpa_printf(MSG_WARNING, "CryptoAPI: Failed to allocate memory " |
| "for CryptoAPI RSA method"); |
| os_free(priv); |
| os_free(rsa_meth); |
| return -1; |
| } |
| |
| priv->cert = cryptoapi_find_cert(name, CERT_SYSTEM_STORE_CURRENT_USER); |
| if (priv->cert == NULL) { |
| priv->cert = cryptoapi_find_cert( |
| name, CERT_SYSTEM_STORE_LOCAL_MACHINE); |
| } |
| if (priv->cert == NULL) { |
| wpa_printf(MSG_INFO, "CryptoAPI: Could not find certificate " |
| "'%s'", name); |
| goto err; |
| } |
| |
| cert = d2i_X509(NULL, |
| (const unsigned char **) &priv->cert->pbCertEncoded, |
| priv->cert->cbCertEncoded); |
| if (cert == NULL) { |
| wpa_printf(MSG_INFO, "CryptoAPI: Could not process X509 DER " |
| "encoding"); |
| goto err; |
| } |
| |
| if (!CryptAcquireCertificatePrivateKey(priv->cert, |
| CRYPT_ACQUIRE_COMPARE_KEY_FLAG, |
| NULL, &priv->crypt_prov, |
| &priv->key_spec, |
| &priv->free_crypt_prov)) { |
| cryptoapi_error("Failed to acquire a private key for the " |
| "certificate"); |
| goto err; |
| } |
| |
| rsa_meth->name = "Microsoft CryptoAPI RSA Method"; |
| rsa_meth->rsa_pub_enc = cryptoapi_rsa_pub_enc; |
| rsa_meth->rsa_pub_dec = cryptoapi_rsa_pub_dec; |
| rsa_meth->rsa_priv_enc = cryptoapi_rsa_priv_enc; |
| rsa_meth->rsa_priv_dec = cryptoapi_rsa_priv_dec; |
| rsa_meth->finish = cryptoapi_finish; |
| rsa_meth->flags = RSA_METHOD_FLAG_NO_CHECK; |
| rsa_meth->app_data = (char *) priv; |
| |
| rsa = RSA_new(); |
| if (rsa == NULL) { |
| SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, |
| ERR_R_MALLOC_FAILURE); |
| goto err; |
| } |
| |
| if (!SSL_use_certificate(ssl, cert)) { |
| RSA_free(rsa); |
| rsa = NULL; |
| goto err; |
| } |
| pub_rsa = cert->cert_info->key->pkey->pkey.rsa; |
| X509_free(cert); |
| cert = NULL; |
| |
| rsa->n = BN_dup(pub_rsa->n); |
| rsa->e = BN_dup(pub_rsa->e); |
| if (!RSA_set_method(rsa, rsa_meth)) |
| goto err; |
| |
| if (!SSL_use_RSAPrivateKey(ssl, rsa)) |
| goto err; |
| RSA_free(rsa); |
| |
| return 0; |
| |
| err: |
| if (cert) |
| X509_free(cert); |
| if (rsa) |
| RSA_free(rsa); |
| else { |
| os_free(rsa_meth); |
| cryptoapi_free_data(priv); |
| } |
| return -1; |
| } |
| |
| |
| static int tls_cryptoapi_ca_cert(SSL_CTX *ssl_ctx, SSL *ssl, const char *name) |
| { |
| HCERTSTORE cs; |
| PCCERT_CONTEXT ctx = NULL; |
| X509 *cert; |
| char buf[128]; |
| const char *store; |
| #ifdef UNICODE |
| WCHAR *wstore; |
| #endif /* UNICODE */ |
| |
| if (name == NULL || strncmp(name, "cert_store://", 13) != 0) |
| return -1; |
| |
| store = name + 13; |
| #ifdef UNICODE |
| wstore = os_malloc((os_strlen(store) + 1) * sizeof(WCHAR)); |
| if (wstore == NULL) |
| return -1; |
| wsprintf(wstore, L"%S", store); |
| cs = CertOpenSystemStore(0, wstore); |
| os_free(wstore); |
| #else /* UNICODE */ |
| cs = CertOpenSystemStore(0, store); |
| #endif /* UNICODE */ |
| if (cs == NULL) { |
| wpa_printf(MSG_DEBUG, "%s: failed to open system cert store " |
| "'%s': error=%d", __func__, store, |
| (int) GetLastError()); |
| return -1; |
| } |
| |
| while ((ctx = CertEnumCertificatesInStore(cs, ctx))) { |
| cert = d2i_X509(NULL, |
| (const unsigned char **) &ctx->pbCertEncoded, |
| ctx->cbCertEncoded); |
| if (cert == NULL) { |
| wpa_printf(MSG_INFO, "CryptoAPI: Could not process " |
| "X509 DER encoding for CA cert"); |
| continue; |
| } |
| |
| X509_NAME_oneline(X509_get_subject_name(cert), buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "OpenSSL: Loaded CA certificate for " |
| "system certificate store: subject='%s'", buf); |
| |
| if (!X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx), |
| cert)) { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to add ca_cert to OpenSSL " |
| "certificate store"); |
| } |
| |
| X509_free(cert); |
| } |
| |
| if (!CertCloseStore(cs, 0)) { |
| wpa_printf(MSG_DEBUG, "%s: failed to close system cert store " |
| "'%s': error=%d", __func__, name + 13, |
| (int) GetLastError()); |
| } |
| |
| return 0; |
| } |
| |
| |
| #else /* CONFIG_NATIVE_WINDOWS */ |
| |
| static int tls_cryptoapi_cert(SSL *ssl, const char *name) |
| { |
| return -1; |
| } |
| |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| |
| static void ssl_info_cb(const SSL *ssl, int where, int ret) |
| { |
| const char *str; |
| int w; |
| |
| wpa_printf(MSG_DEBUG, "SSL: (where=0x%x ret=0x%x)", where, ret); |
| w = where & ~SSL_ST_MASK; |
| if (w & SSL_ST_CONNECT) |
| str = "SSL_connect"; |
| else if (w & SSL_ST_ACCEPT) |
| str = "SSL_accept"; |
| else |
| str = "undefined"; |
| |
| if (where & SSL_CB_LOOP) { |
| wpa_printf(MSG_DEBUG, "SSL: %s:%s", |
| str, SSL_state_string_long(ssl)); |
| } else if (where & SSL_CB_ALERT) { |
| struct tls_connection *conn = SSL_get_app_data((SSL *) ssl); |
| wpa_printf(MSG_INFO, "SSL: SSL3 alert: %s:%s:%s", |
| where & SSL_CB_READ ? |
| "read (remote end reported an error)" : |
| "write (local SSL3 detected an error)", |
| SSL_alert_type_string_long(ret), |
| SSL_alert_desc_string_long(ret)); |
| if ((ret >> 8) == SSL3_AL_FATAL) { |
| if (where & SSL_CB_READ) |
| conn->read_alerts++; |
| else |
| conn->write_alerts++; |
| } |
| if (conn->context->event_cb != NULL) { |
| union tls_event_data ev; |
| struct tls_context *context = conn->context; |
| os_memset(&ev, 0, sizeof(ev)); |
| ev.alert.is_local = !(where & SSL_CB_READ); |
| ev.alert.type = SSL_alert_type_string_long(ret); |
| ev.alert.description = SSL_alert_desc_string_long(ret); |
| context->event_cb(context->cb_ctx, TLS_ALERT, &ev); |
| } |
| } else if (where & SSL_CB_EXIT && ret <= 0) { |
| wpa_printf(MSG_DEBUG, "SSL: %s:%s in %s", |
| str, ret == 0 ? "failed" : "error", |
| SSL_state_string_long(ssl)); |
| } |
| } |
| |
| |
| #ifndef OPENSSL_NO_ENGINE |
| /** |
| * tls_engine_load_dynamic_generic - load any openssl engine |
| * @pre: an array of commands and values that load an engine initialized |
| * in the engine specific function |
| * @post: an array of commands and values that initialize an already loaded |
| * engine (or %NULL if not required) |
| * @id: the engine id of the engine to load (only required if post is not %NULL |
| * |
| * This function is a generic function that loads any openssl engine. |
| * |
| * Returns: 0 on success, -1 on failure |
| */ |
| static int tls_engine_load_dynamic_generic(const char *pre[], |
| const char *post[], const char *id) |
| { |
| ENGINE *engine; |
| const char *dynamic_id = "dynamic"; |
| |
| engine = ENGINE_by_id(id); |
| if (engine) { |
| wpa_printf(MSG_DEBUG, "ENGINE: engine '%s' is already " |
| "available", id); |
| /* |
| * If it was auto-loaded by ENGINE_by_id() we might still |
| * need to tell it which PKCS#11 module to use in legacy |
| * (non-p11-kit) environments. Do so now; even if it was |
| * properly initialised before, setting it again will be |
| * harmless. |
| */ |
| goto found; |
| } |
| ERR_clear_error(); |
| |
| engine = ENGINE_by_id(dynamic_id); |
| if (engine == NULL) { |
| wpa_printf(MSG_INFO, "ENGINE: Can't find engine %s [%s]", |
| dynamic_id, |
| ERR_error_string(ERR_get_error(), NULL)); |
| return -1; |
| } |
| |
| /* Perform the pre commands. This will load the engine. */ |
| while (pre && pre[0]) { |
| wpa_printf(MSG_DEBUG, "ENGINE: '%s' '%s'", pre[0], pre[1]); |
| if (ENGINE_ctrl_cmd_string(engine, pre[0], pre[1], 0) == 0) { |
| wpa_printf(MSG_INFO, "ENGINE: ctrl cmd_string failed: " |
| "%s %s [%s]", pre[0], pre[1], |
| ERR_error_string(ERR_get_error(), NULL)); |
| ENGINE_free(engine); |
| return -1; |
| } |
| pre += 2; |
| } |
| |
| /* |
| * Free the reference to the "dynamic" engine. The loaded engine can |
| * now be looked up using ENGINE_by_id(). |
| */ |
| ENGINE_free(engine); |
| |
| engine = ENGINE_by_id(id); |
| if (engine == NULL) { |
| wpa_printf(MSG_INFO, "ENGINE: Can't find engine %s [%s]", |
| id, ERR_error_string(ERR_get_error(), NULL)); |
| return -1; |
| } |
| found: |
| while (post && post[0]) { |
| wpa_printf(MSG_DEBUG, "ENGINE: '%s' '%s'", post[0], post[1]); |
| if (ENGINE_ctrl_cmd_string(engine, post[0], post[1], 0) == 0) { |
| wpa_printf(MSG_DEBUG, "ENGINE: ctrl cmd_string failed:" |
| " %s %s [%s]", post[0], post[1], |
| ERR_error_string(ERR_get_error(), NULL)); |
| ENGINE_remove(engine); |
| ENGINE_free(engine); |
| return -1; |
| } |
| post += 2; |
| } |
| ENGINE_free(engine); |
| |
| return 0; |
| } |
| |
| |
| /** |
| * tls_engine_load_dynamic_pkcs11 - load the pkcs11 engine provided by opensc |
| * @pkcs11_so_path: pksc11_so_path from the configuration |
| * @pcks11_module_path: pkcs11_module_path from the configuration |
| */ |
| static int tls_engine_load_dynamic_pkcs11(const char *pkcs11_so_path, |
| const char *pkcs11_module_path) |
| { |
| char *engine_id = "pkcs11"; |
| const char *pre_cmd[] = { |
| "SO_PATH", NULL /* pkcs11_so_path */, |
| "ID", NULL /* engine_id */, |
| "LIST_ADD", "1", |
| /* "NO_VCHECK", "1", */ |
| "LOAD", NULL, |
| NULL, NULL |
| }; |
| const char *post_cmd[] = { |
| "MODULE_PATH", NULL /* pkcs11_module_path */, |
| NULL, NULL |
| }; |
| |
| if (!pkcs11_so_path) |
| return 0; |
| |
| pre_cmd[1] = pkcs11_so_path; |
| pre_cmd[3] = engine_id; |
| if (pkcs11_module_path) |
| post_cmd[1] = pkcs11_module_path; |
| else |
| post_cmd[0] = NULL; |
| |
| wpa_printf(MSG_DEBUG, "ENGINE: Loading pkcs11 Engine from %s", |
| pkcs11_so_path); |
| |
| return tls_engine_load_dynamic_generic(pre_cmd, post_cmd, engine_id); |
| } |
| |
| |
| /** |
| * tls_engine_load_dynamic_opensc - load the opensc engine provided by opensc |
| * @opensc_so_path: opensc_so_path from the configuration |
| */ |
| static int tls_engine_load_dynamic_opensc(const char *opensc_so_path) |
| { |
| char *engine_id = "opensc"; |
| const char *pre_cmd[] = { |
| "SO_PATH", NULL /* opensc_so_path */, |
| "ID", NULL /* engine_id */, |
| "LIST_ADD", "1", |
| "LOAD", NULL, |
| NULL, NULL |
| }; |
| |
| if (!opensc_so_path) |
| return 0; |
| |
| pre_cmd[1] = opensc_so_path; |
| pre_cmd[3] = engine_id; |
| |
| wpa_printf(MSG_DEBUG, "ENGINE: Loading OpenSC Engine from %s", |
| opensc_so_path); |
| |
| return tls_engine_load_dynamic_generic(pre_cmd, NULL, engine_id); |
| } |
| #endif /* OPENSSL_NO_ENGINE */ |
| |
| |
| static struct tls_session_data * get_session_data(struct tls_context *context, |
| const struct wpabuf *buf) |
| { |
| struct tls_session_data *data; |
| |
| dl_list_for_each(data, &context->sessions, struct tls_session_data, |
| list) { |
| if (data->buf == buf) |
| return data; |
| } |
| |
| return NULL; |
| } |
| |
| |
| static void remove_session_cb(SSL_CTX *ctx, SSL_SESSION *sess) |
| { |
| struct wpabuf *buf; |
| struct tls_context *context; |
| struct tls_session_data *found; |
| |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Remove session %p (tls_ex_idx_session=%d)", sess, |
| tls_ex_idx_session); |
| |
| if (tls_ex_idx_session < 0) |
| return; |
| buf = SSL_SESSION_get_ex_data(sess, tls_ex_idx_session); |
| if (!buf) |
| return; |
| |
| context = SSL_CTX_get_app_data(ctx); |
| SSL_SESSION_set_ex_data(sess, tls_ex_idx_session, NULL); |
| found = get_session_data(context, buf); |
| if (!found) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Do not free application session data %p (sess %p)", |
| buf, sess); |
| return; |
| } |
| |
| dl_list_del(&found->list); |
| os_free(found); |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Free application session data %p (sess %p)", |
| buf, sess); |
| wpabuf_free(buf); |
| } |
| |
| |
| void * tls_init(const struct tls_config *conf) |
| { |
| struct tls_data *data; |
| SSL_CTX *ssl; |
| struct tls_context *context; |
| const char *ciphers; |
| |
| if (tls_openssl_ref_count == 0) { |
| void openssl_load_legacy_provider(void); |
| |
| openssl_load_legacy_provider(); |
| |
| tls_global = context = tls_context_new(conf); |
| if (context == NULL) |
| return NULL; |
| #ifdef CONFIG_FIPS |
| #ifdef OPENSSL_FIPS |
| if (conf && conf->fips_mode) { |
| static int fips_enabled = 0; |
| |
| if (!fips_enabled && !FIPS_mode_set(1)) { |
| wpa_printf(MSG_ERROR, "Failed to enable FIPS " |
| "mode"); |
| ERR_load_crypto_strings(); |
| ERR_print_errors_fp(stderr); |
| os_free(tls_global); |
| tls_global = NULL; |
| return NULL; |
| } else { |
| wpa_printf(MSG_INFO, "Running in FIPS mode"); |
| fips_enabled = 1; |
| } |
| } |
| #else /* OPENSSL_FIPS */ |
| if (conf && conf->fips_mode) { |
| wpa_printf(MSG_ERROR, "FIPS mode requested, but not " |
| "supported"); |
| os_free(tls_global); |
| tls_global = NULL; |
| return NULL; |
| } |
| #endif /* OPENSSL_FIPS */ |
| #endif /* CONFIG_FIPS */ |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| SSL_load_error_strings(); |
| SSL_library_init(); |
| #ifndef OPENSSL_NO_SHA256 |
| EVP_add_digest(EVP_sha256()); |
| #endif /* OPENSSL_NO_SHA256 */ |
| /* TODO: if /dev/urandom is available, PRNG is seeded |
| * automatically. If this is not the case, random data should |
| * be added here. */ |
| |
| #ifdef PKCS12_FUNCS |
| #ifndef OPENSSL_NO_RC2 |
| /* |
| * 40-bit RC2 is commonly used in PKCS#12 files, so enable it. |
| * This is enabled by PKCS12_PBE_add() in OpenSSL 0.9.8 |
| * versions, but it looks like OpenSSL 1.0.0 does not do that |
| * anymore. |
| */ |
| EVP_add_cipher(EVP_rc2_40_cbc()); |
| #endif /* OPENSSL_NO_RC2 */ |
| PKCS12_PBE_add(); |
| #endif /* PKCS12_FUNCS */ |
| #endif /* < 1.1.0 */ |
| } else { |
| context = tls_context_new(conf); |
| if (context == NULL) |
| return NULL; |
| } |
| tls_openssl_ref_count++; |
| |
| data = os_zalloc(sizeof(*data)); |
| if (data) |
| ssl = SSL_CTX_new(SSLv23_method()); |
| else |
| ssl = NULL; |
| if (ssl == NULL) { |
| tls_openssl_ref_count--; |
| if (context != tls_global) |
| os_free(context); |
| if (tls_openssl_ref_count == 0) { |
| os_free(tls_global); |
| tls_global = NULL; |
| } |
| os_free(data); |
| return NULL; |
| } |
| data->ssl = ssl; |
| if (conf) { |
| data->tls_session_lifetime = conf->tls_session_lifetime; |
| data->crl_reload_interval = conf->crl_reload_interval; |
| } |
| |
| SSL_CTX_set_options(ssl, SSL_OP_NO_SSLv2); |
| SSL_CTX_set_options(ssl, SSL_OP_NO_SSLv3); |
| |
| SSL_CTX_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
| |
| #ifdef SSL_MODE_NO_AUTO_CHAIN |
| /* Number of deployed use cases assume the default OpenSSL behavior of |
| * auto chaining the local certificate is in use. BoringSSL removed this |
| * functionality by default, so we need to restore it here to avoid |
| * breaking existing use cases. */ |
| SSL_CTX_clear_mode(ssl, SSL_MODE_NO_AUTO_CHAIN); |
| #endif /* SSL_MODE_NO_AUTO_CHAIN */ |
| |
| SSL_CTX_set_info_callback(ssl, ssl_info_cb); |
| SSL_CTX_set_app_data(ssl, context); |
| if (data->tls_session_lifetime > 0) { |
| SSL_CTX_set_quiet_shutdown(ssl, 1); |
| /* |
| * Set default context here. In practice, this will be replaced |
| * by the per-EAP method context in tls_connection_set_verify(). |
| */ |
| SSL_CTX_set_session_id_context(ssl, (u8 *) "hostapd", 7); |
| SSL_CTX_set_session_cache_mode(ssl, SSL_SESS_CACHE_SERVER); |
| SSL_CTX_set_timeout(ssl, data->tls_session_lifetime); |
| SSL_CTX_sess_set_remove_cb(ssl, remove_session_cb); |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ |
| !defined(LIBRESSL_VERSION_NUMBER) && \ |
| !defined(OPENSSL_IS_BORINGSSL) |
| /* One session ticket is sufficient for EAP-TLS */ |
| SSL_CTX_set_num_tickets(ssl, 1); |
| #endif |
| } else { |
| SSL_CTX_set_session_cache_mode(ssl, SSL_SESS_CACHE_OFF); |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && \ |
| !defined(LIBRESSL_VERSION_NUMBER) && \ |
| !defined(OPENSSL_IS_BORINGSSL) |
| SSL_CTX_set_num_tickets(ssl, 0); |
| #endif |
| } |
| |
| if (tls_ex_idx_session < 0) { |
| tls_ex_idx_session = SSL_SESSION_get_ex_new_index( |
| 0, NULL, NULL, NULL, NULL); |
| if (tls_ex_idx_session < 0) { |
| tls_deinit(data); |
| return NULL; |
| } |
| } |
| |
| #ifndef OPENSSL_NO_ENGINE |
| wpa_printf(MSG_DEBUG, "ENGINE: Loading builtin engines"); |
| ENGINE_load_builtin_engines(); |
| |
| if (conf && |
| (conf->opensc_engine_path || conf->pkcs11_engine_path || |
| conf->pkcs11_module_path)) { |
| if (tls_engine_load_dynamic_opensc(conf->opensc_engine_path) || |
| tls_engine_load_dynamic_pkcs11(conf->pkcs11_engine_path, |
| conf->pkcs11_module_path)) { |
| tls_deinit(data); |
| return NULL; |
| } |
| } |
| #endif /* OPENSSL_NO_ENGINE */ |
| |
| if (conf && conf->openssl_ciphers) |
| ciphers = conf->openssl_ciphers; |
| else |
| ciphers = TLS_DEFAULT_CIPHERS; |
| if (SSL_CTX_set_cipher_list(ssl, ciphers) != 1) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Failed to set cipher string '%s'", |
| ciphers); |
| tls_deinit(data); |
| return NULL; |
| } |
| |
| return data; |
| } |
| |
| |
| void tls_deinit(void *ssl_ctx) |
| { |
| struct tls_data *data = ssl_ctx; |
| SSL_CTX *ssl = data->ssl; |
| struct tls_context *context = SSL_CTX_get_app_data(ssl); |
| struct tls_session_data *sess_data; |
| |
| if (data->tls_session_lifetime > 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Flush sessions"); |
| SSL_CTX_flush_sessions(ssl, 0); |
| wpa_printf(MSG_DEBUG, "OpenSSL: Flush sessions - done"); |
| } |
| while ((sess_data = dl_list_first(&context->sessions, |
| struct tls_session_data, list))) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Freeing not-flushed session data %p", |
| sess_data->buf); |
| wpabuf_free(sess_data->buf); |
| dl_list_del(&sess_data->list); |
| os_free(sess_data); |
| } |
| if (context != tls_global) |
| os_free(context); |
| os_free(data->ca_cert); |
| SSL_CTX_free(ssl); |
| |
| tls_openssl_ref_count--; |
| if (tls_openssl_ref_count == 0) { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| #ifndef OPENSSL_NO_ENGINE |
| ENGINE_cleanup(); |
| #endif /* OPENSSL_NO_ENGINE */ |
| CRYPTO_cleanup_all_ex_data(); |
| ERR_remove_thread_state(NULL); |
| ERR_free_strings(); |
| EVP_cleanup(); |
| #endif /* < 1.1.0 */ |
| os_free(tls_global->ocsp_stapling_response); |
| tls_global->ocsp_stapling_response = NULL; |
| os_free(tls_global); |
| tls_global = NULL; |
| } |
| |
| os_free(data->check_cert_subject); |
| os_free(data); |
| } |
| |
| |
| #ifndef OPENSSL_NO_ENGINE |
| |
| /* Cryptoki return values */ |
| #define CKR_PIN_INCORRECT 0x000000a0 |
| #define CKR_PIN_INVALID 0x000000a1 |
| #define CKR_PIN_LEN_RANGE 0x000000a2 |
| |
| /* libp11 */ |
| #define ERR_LIB_PKCS11 ERR_LIB_USER |
| |
| static int tls_is_pin_error(unsigned int err) |
| { |
| return ERR_GET_LIB(err) == ERR_LIB_PKCS11 && |
| (ERR_GET_REASON(err) == CKR_PIN_INCORRECT || |
| ERR_GET_REASON(err) == CKR_PIN_INVALID || |
| ERR_GET_REASON(err) == CKR_PIN_LEN_RANGE); |
| } |
| |
| #endif /* OPENSSL_NO_ENGINE */ |
| |
| |
| #ifdef ANDROID |
| /* EVP_PKEY_from_keystore comes from system/security/keystore-engine. */ |
| EVP_PKEY * EVP_PKEY_from_keystore(const char *key_id); |
| #endif /* ANDROID */ |
| |
| static int tls_engine_init(struct tls_connection *conn, const char *engine_id, |
| const char *pin, const char *key_id, |
| const char *cert_id, const char *ca_cert_id) |
| { |
| #if defined(ANDROID) && defined(OPENSSL_IS_BORINGSSL) |
| #if !defined(OPENSSL_NO_ENGINE) |
| #error "This code depends on OPENSSL_NO_ENGINE being defined by BoringSSL." |
| #endif |
| if (!key_id) |
| return TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| conn->engine = NULL; |
| conn->private_key = EVP_PKEY_from_keystore(key_id); |
| if (!conn->private_key) { |
| wpa_printf(MSG_ERROR, |
| "ENGINE: cannot load private key with id '%s' [%s]", |
| key_id, |
| ERR_error_string(ERR_get_error(), NULL)); |
| return TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| } |
| #endif /* ANDROID && OPENSSL_IS_BORINGSSL */ |
| |
| #ifndef OPENSSL_NO_ENGINE |
| int ret = -1; |
| if (engine_id == NULL) { |
| wpa_printf(MSG_ERROR, "ENGINE: Engine ID not set"); |
| return -1; |
| } |
| |
| ERR_clear_error(); |
| #ifdef ANDROID |
| ENGINE_load_dynamic(); |
| #endif |
| conn->engine = ENGINE_by_id(engine_id); |
| if (!conn->engine) { |
| wpa_printf(MSG_ERROR, "ENGINE: engine %s not available [%s]", |
| engine_id, ERR_error_string(ERR_get_error(), NULL)); |
| goto err; |
| } |
| if (ENGINE_init(conn->engine) != 1) { |
| wpa_printf(MSG_ERROR, "ENGINE: engine init failed " |
| "(engine: %s) [%s]", engine_id, |
| ERR_error_string(ERR_get_error(), NULL)); |
| goto err; |
| } |
| wpa_printf(MSG_DEBUG, "ENGINE: engine initialized"); |
| |
| #ifndef ANDROID |
| if (pin && ENGINE_ctrl_cmd_string(conn->engine, "PIN", pin, 0) == 0) { |
| wpa_printf(MSG_ERROR, "ENGINE: cannot set pin [%s]", |
| ERR_error_string(ERR_get_error(), NULL)); |
| goto err; |
| } |
| #endif |
| if (key_id) { |
| /* |
| * Ensure that the ENGINE does not attempt to use the OpenSSL |
| * UI system to obtain a PIN, if we didn't provide one. |
| */ |
| struct { |
| const void *password; |
| const char *prompt_info; |
| } key_cb = { "", NULL }; |
| |
| /* load private key first in-case PIN is required for cert */ |
| conn->private_key = ENGINE_load_private_key(conn->engine, |
| key_id, NULL, |
| &key_cb); |
| if (!conn->private_key) { |
| unsigned long err = ERR_get_error(); |
| |
| wpa_printf(MSG_ERROR, |
| "ENGINE: cannot load private key with id '%s' [%s]", |
| key_id, |
| ERR_error_string(err, NULL)); |
| if (tls_is_pin_error(err)) |
| ret = TLS_SET_PARAMS_ENGINE_PRV_BAD_PIN; |
| else |
| ret = TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| goto err; |
| } |
| } |
| |
| /* handle a certificate and/or CA certificate */ |
| if (cert_id || ca_cert_id) { |
| const char *cmd_name = "LOAD_CERT_CTRL"; |
| |
| /* test if the engine supports a LOAD_CERT_CTRL */ |
| if (!ENGINE_ctrl(conn->engine, ENGINE_CTRL_GET_CMD_FROM_NAME, |
| 0, (void *)cmd_name, NULL)) { |
| wpa_printf(MSG_ERROR, "ENGINE: engine does not support" |
| " loading certificates"); |
| ret = TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| goto err; |
| } |
| } |
| |
| return 0; |
| |
| err: |
| if (conn->engine) { |
| ENGINE_free(conn->engine); |
| conn->engine = NULL; |
| } |
| |
| if (conn->private_key) { |
| EVP_PKEY_free(conn->private_key); |
| conn->private_key = NULL; |
| } |
| |
| return ret; |
| #else /* OPENSSL_NO_ENGINE */ |
| return 0; |
| #endif /* OPENSSL_NO_ENGINE */ |
| } |
| |
| |
| static void tls_engine_deinit(struct tls_connection *conn) |
| { |
| #if defined(ANDROID) || !defined(OPENSSL_NO_ENGINE) |
| wpa_printf(MSG_DEBUG, "ENGINE: engine deinit"); |
| if (conn->private_key) { |
| EVP_PKEY_free(conn->private_key); |
| conn->private_key = NULL; |
| } |
| if (conn->engine) { |
| #if !defined(OPENSSL_IS_BORINGSSL) |
| ENGINE_finish(conn->engine); |
| #endif /* !OPENSSL_IS_BORINGSSL */ |
| conn->engine = NULL; |
| } |
| #endif /* ANDROID || !OPENSSL_NO_ENGINE */ |
| } |
| |
| |
| int tls_get_errors(void *ssl_ctx) |
| { |
| int count = 0; |
| unsigned long err; |
| |
| while ((err = ERR_get_error())) { |
| wpa_printf(MSG_INFO, "TLS - SSL error: %s", |
| ERR_error_string(err, NULL)); |
| count++; |
| } |
| |
| return count; |
| } |
| |
| |
| static const char * openssl_content_type(int content_type) |
| { |
| switch (content_type) { |
| case 20: |
| return "change cipher spec"; |
| case 21: |
| return "alert"; |
| case 22: |
| return "handshake"; |
| case 23: |
| return "application data"; |
| case 24: |
| return "heartbeat"; |
| case 256: |
| return "TLS header info"; /* pseudo content type */ |
| case 257: |
| return "inner content type"; /* pseudo content type */ |
| default: |
| return "?"; |
| } |
| } |
| |
| |
| static const char * openssl_handshake_type(int content_type, const u8 *buf, |
| size_t len) |
| { |
| if (content_type == 257 && buf && len == 1) |
| return openssl_content_type(buf[0]); |
| if (content_type != 22 || !buf || len == 0) |
| return ""; |
| switch (buf[0]) { |
| case 0: |
| return "hello request"; |
| case 1: |
| return "client hello"; |
| case 2: |
| return "server hello"; |
| case 3: |
| return "hello verify request"; |
| case 4: |
| return "new session ticket"; |
| case 5: |
| return "end of early data"; |
| case 6: |
| return "hello retry request"; |
| case 8: |
| return "encrypted extensions"; |
| case 11: |
| return "certificate"; |
| case 12: |
| return "server key exchange"; |
| case 13: |
| return "certificate request"; |
| case 14: |
| return "server hello done"; |
| case 15: |
| return "certificate verify"; |
| case 16: |
| return "client key exchange"; |
| case 20: |
| return "finished"; |
| case 21: |
| return "certificate url"; |
| case 22: |
| return "certificate status"; |
| case 23: |
| return "supplemental data"; |
| case 24: |
| return "key update"; |
| case 254: |
| return "message hash"; |
| default: |
| return "?"; |
| } |
| } |
| |
| |
| #ifdef CONFIG_SUITEB |
| |
| static void check_server_hello(struct tls_connection *conn, |
| const u8 *pos, const u8 *end) |
| { |
| size_t payload_len, id_len; |
| |
| /* |
| * Parse ServerHello to get the selected cipher suite since OpenSSL does |
| * not make it cleanly available during handshake and we need to know |
| * whether DHE was selected. |
| */ |
| |
| if (end - pos < 3) |
| return; |
| payload_len = WPA_GET_BE24(pos); |
| pos += 3; |
| |
| if ((size_t) (end - pos) < payload_len) |
| return; |
| end = pos + payload_len; |
| |
| /* Skip Version and Random */ |
| if (end - pos < 2 + SSL3_RANDOM_SIZE) |
| return; |
| pos += 2 + SSL3_RANDOM_SIZE; |
| |
| /* Skip Session ID */ |
| if (end - pos < 1) |
| return; |
| id_len = *pos++; |
| if ((size_t) (end - pos) < id_len) |
| return; |
| pos += id_len; |
| |
| if (end - pos < 2) |
| return; |
| conn->cipher_suite = WPA_GET_BE16(pos); |
| wpa_printf(MSG_DEBUG, "OpenSSL: Server selected cipher suite 0x%x", |
| conn->cipher_suite); |
| } |
| |
| |
| static void check_server_key_exchange(SSL *ssl, struct tls_connection *conn, |
| const u8 *pos, const u8 *end) |
| { |
| size_t payload_len; |
| u16 dh_len; |
| BIGNUM *p; |
| int bits; |
| |
| if (!(conn->flags & TLS_CONN_SUITEB)) |
| return; |
| |
| /* DHE is enabled only with DHE-RSA-AES256-GCM-SHA384 */ |
| if (conn->cipher_suite != 0x9f) |
| return; |
| |
| if (end - pos < 3) |
| return; |
| payload_len = WPA_GET_BE24(pos); |
| pos += 3; |
| |
| if ((size_t) (end - pos) < payload_len) |
| return; |
| end = pos + payload_len; |
| |
| if (end - pos < 2) |
| return; |
| dh_len = WPA_GET_BE16(pos); |
| pos += 2; |
| |
| if ((size_t) (end - pos) < dh_len) |
| return; |
| p = BN_bin2bn(pos, dh_len, NULL); |
| if (!p) |
| return; |
| |
| bits = BN_num_bits(p); |
| BN_free(p); |
| |
| conn->server_dh_prime_len = bits; |
| wpa_printf(MSG_DEBUG, "OpenSSL: Server DH prime length: %d bits", |
| conn->server_dh_prime_len); |
| } |
| |
| #endif /* CONFIG_SUITEB */ |
| |
| |
| static void tls_msg_cb(int write_p, int version, int content_type, |
| const void *buf, size_t len, SSL *ssl, void *arg) |
| { |
| struct tls_connection *conn = arg; |
| const u8 *pos = buf; |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| if ((SSL_version(ssl) == TLS1_VERSION || |
| SSL_version(ssl) == TLS1_1_VERSION) && |
| SSL_get_security_level(ssl) > 0) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Drop security level to 0 to allow TLS 1.0/1.1 use of MD5-SHA1 signature algorithm"); |
| SSL_set_security_level(ssl, 0); |
| } |
| #endif /* OpenSSL version >= 3.0 */ |
| if (write_p == 2) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: session ver=0x%x content_type=%d", |
| version, content_type); |
| wpa_hexdump_key(MSG_MSGDUMP, "OpenSSL: Data", buf, len); |
| return; |
| } |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s ver=0x%x content_type=%d (%s/%s)", |
| write_p ? "TX" : "RX", version, content_type, |
| openssl_content_type(content_type), |
| openssl_handshake_type(content_type, buf, len)); |
| wpa_hexdump_key(MSG_MSGDUMP, "OpenSSL: Message", buf, len); |
| if (content_type == 24 && len >= 3 && pos[0] == 1) { |
| size_t payload_len = WPA_GET_BE16(pos + 1); |
| if (payload_len + 3 > len) { |
| wpa_printf(MSG_ERROR, "OpenSSL: Heartbeat attack detected"); |
| conn->invalid_hb_used = 1; |
| } |
| } |
| |
| #ifdef CONFIG_SUITEB |
| /* |
| * Need to parse these handshake messages to be able to check DH prime |
| * length since OpenSSL does not expose the new cipher suite and DH |
| * parameters during handshake (e.g., for cert_cb() callback). |
| */ |
| if (content_type == 22 && pos && len > 0 && pos[0] == 2) |
| check_server_hello(conn, pos + 1, pos + len); |
| if (content_type == 22 && pos && len > 0 && pos[0] == 12) |
| check_server_key_exchange(ssl, conn, pos + 1, pos + len); |
| #endif /* CONFIG_SUITEB */ |
| } |
| |
| |
| #ifdef CONFIG_TESTING_OPTIONS |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) |
| /* |
| * By setting the environment variable SSLKEYLOGFILE to a filename keying |
| * material will be exported that you may use with Wireshark to decode any |
| * TLS flows. Please see the following for more details: |
| * |
| * https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption |
| * |
| * Example logging sessions are (you should delete the file on each run): |
| * |
| * rm -f /tmp/sslkey.log |
| * env SSLKEYLOGFILE=/tmp/sslkey.log hostapd ... |
| * |
| * rm -f /tmp/sslkey.log |
| * env SSLKEYLOGFILE=/tmp/sslkey.log wpa_supplicant ... |
| * |
| * rm -f /tmp/sslkey.log |
| * env SSLKEYLOGFILE=/tmp/sslkey.log eapol_test ... |
| */ |
| static void tls_keylog_cb(const SSL *ssl, const char *line) |
| { |
| int fd; |
| const char *filename; |
| struct iovec iov[2]; |
| |
| filename = getenv("SSLKEYLOGFILE"); |
| if (!filename) |
| return; |
| |
| fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); |
| if (fd < 0) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Failed to open keylog file %s: %s", |
| filename, strerror(errno)); |
| return; |
| } |
| |
| /* Assume less than _POSIX_PIPE_BUF (512) where writes are guaranteed |
| * to be atomic for O_APPEND. */ |
| iov[0].iov_base = (void *) line; |
| iov[0].iov_len = os_strlen(line); |
| iov[1].iov_base = "\n"; |
| iov[1].iov_len = 1; |
| |
| if (writev(fd, iov, ARRAY_SIZE(iov)) < 01) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Failed to write to keylog file %s: %s", |
| filename, strerror(errno)); |
| } |
| |
| close(fd); |
| } |
| #endif |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| |
| |
| struct tls_connection * tls_connection_init(void *ssl_ctx) |
| { |
| struct tls_data *data = ssl_ctx; |
| SSL_CTX *ssl = data->ssl; |
| struct tls_connection *conn; |
| long options; |
| X509_STORE *new_cert_store; |
| struct os_reltime now; |
| struct tls_context *context = SSL_CTX_get_app_data(ssl); |
| |
| /* Replace X509 store if it is time to update CRL. */ |
| if (data->crl_reload_interval > 0 && os_get_reltime(&now) == 0 && |
| os_reltime_expired(&now, &data->crl_last_reload, |
| data->crl_reload_interval)) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Flushing X509 store with ca_cert file"); |
| new_cert_store = tls_crl_cert_reload(data->ca_cert, |
| data->check_crl); |
| if (!new_cert_store) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Error replacing X509 store with ca_cert file"); |
| } else { |
| /* Replace old store */ |
| SSL_CTX_set_cert_store(ssl, new_cert_store); |
| data->crl_last_reload = now; |
| } |
| } |
| |
| conn = os_zalloc(sizeof(*conn)); |
| if (conn == NULL) |
| return NULL; |
| conn->data = data; |
| conn->ssl_ctx = ssl; |
| conn->ssl = SSL_new(ssl); |
| if (conn->ssl == NULL) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to initialize new SSL connection"); |
| os_free(conn); |
| return NULL; |
| } |
| |
| conn->context = context; |
| SSL_set_app_data(conn->ssl, conn); |
| SSL_set_msg_callback(conn->ssl, tls_msg_cb); |
| SSL_set_msg_callback_arg(conn->ssl, conn); |
| options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | |
| SSL_OP_SINGLE_DH_USE; |
| #ifdef SSL_OP_NO_COMPRESSION |
| options |= SSL_OP_NO_COMPRESSION; |
| #endif /* SSL_OP_NO_COMPRESSION */ |
| SSL_set_options(conn->ssl, options); |
| #ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT |
| /* Hopefully there is no need for middlebox compatibility mechanisms |
| * when going through EAP authentication. */ |
| SSL_clear_options(conn->ssl, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); |
| #endif |
| |
| #ifdef CONFIG_TESTING_OPTIONS |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) |
| /* Set the keylog file if the admin requested it. */ |
| if (getenv("SSLKEYLOGFILE")) |
| SSL_CTX_set_keylog_callback(conn->ssl_ctx, tls_keylog_cb); |
| #endif |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| |
| conn->ssl_in = BIO_new(BIO_s_mem()); |
| if (!conn->ssl_in) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to create a new BIO for ssl_in"); |
| SSL_free(conn->ssl); |
| os_free(conn); |
| return NULL; |
| } |
| |
| conn->ssl_out = BIO_new(BIO_s_mem()); |
| if (!conn->ssl_out) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to create a new BIO for ssl_out"); |
| SSL_free(conn->ssl); |
| BIO_free(conn->ssl_in); |
| os_free(conn); |
| return NULL; |
| } |
| |
| SSL_set_bio(conn->ssl, conn->ssl_in, conn->ssl_out); |
| |
| return conn; |
| } |
| |
| |
| void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) |
| { |
| if (conn == NULL) |
| return; |
| if (conn->success_data) { |
| /* |
| * Make sure ssl_clear_bad_session() does not remove this |
| * session. |
| */ |
| SSL_set_quiet_shutdown(conn->ssl, 1); |
| SSL_shutdown(conn->ssl); |
| } |
| SSL_free(conn->ssl); |
| tls_engine_deinit(conn); |
| os_free(conn->subject_match); |
| os_free(conn->altsubject_match); |
| os_free(conn->suffix_match); |
| os_free(conn->domain_match); |
| os_free(conn->check_cert_subject); |
| os_free(conn->session_ticket); |
| os_free(conn->peer_subject); |
| os_free(conn); |
| } |
| |
| |
| int tls_connection_established(void *ssl_ctx, struct tls_connection *conn) |
| { |
| return conn ? SSL_is_init_finished(conn->ssl) : 0; |
| } |
| |
| |
| char * tls_connection_peer_serial_num(void *tls_ctx, |
| struct tls_connection *conn) |
| { |
| ASN1_INTEGER *ser; |
| char *serial_num; |
| size_t len; |
| |
| if (!conn->peer_cert) |
| return NULL; |
| |
| ser = X509_get_serialNumber(conn->peer_cert); |
| if (!ser) |
| return NULL; |
| |
| len = ASN1_STRING_length(ser) * 2 + 1; |
| serial_num = os_malloc(len); |
| if (!serial_num) |
| return NULL; |
| wpa_snprintf_hex_uppercase(serial_num, len, |
| ASN1_STRING_get0_data(ser), |
| ASN1_STRING_length(ser)); |
| return serial_num; |
| } |
| |
| |
| int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn) |
| { |
| if (conn == NULL) |
| return -1; |
| |
| /* Shutdown previous TLS connection without notifying the peer |
| * because the connection was already terminated in practice |
| * and "close notify" shutdown alert would confuse AS. */ |
| SSL_set_quiet_shutdown(conn->ssl, 1); |
| SSL_shutdown(conn->ssl); |
| return SSL_clear(conn->ssl) == 1 ? 0 : -1; |
| } |
| |
| |
| static int tls_match_altsubject_component(X509 *cert, int type, |
| const char *value, size_t len) |
| { |
| GENERAL_NAME *gen; |
| void *ext; |
| int found = 0; |
| stack_index_t i; |
| |
| ext = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); |
| |
| for (i = 0; ext && i < sk_GENERAL_NAME_num(ext); i++) { |
| gen = sk_GENERAL_NAME_value(ext, i); |
| if (gen->type != type) |
| continue; |
| if (os_strlen((char *) gen->d.ia5->data) == len && |
| os_memcmp(value, gen->d.ia5->data, len) == 0) |
| found++; |
| } |
| |
| sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free); |
| |
| return found; |
| } |
| |
| |
| static int tls_match_altsubject(X509 *cert, const char *match) |
| { |
| int type; |
| const char *pos, *end; |
| size_t len; |
| |
| pos = match; |
| do { |
| if (os_strncmp(pos, "EMAIL:", 6) == 0) { |
| type = GEN_EMAIL; |
| pos += 6; |
| } else if (os_strncmp(pos, "DNS:", 4) == 0) { |
| type = GEN_DNS; |
| pos += 4; |
| } else if (os_strncmp(pos, "URI:", 4) == 0) { |
| type = GEN_URI; |
| pos += 4; |
| } else { |
| wpa_printf(MSG_INFO, "TLS: Invalid altSubjectName " |
| "match '%s'", pos); |
| return 0; |
| } |
| end = os_strchr(pos, ';'); |
| while (end) { |
| if (os_strncmp(end + 1, "EMAIL:", 6) == 0 || |
| os_strncmp(end + 1, "DNS:", 4) == 0 || |
| os_strncmp(end + 1, "URI:", 4) == 0) |
| break; |
| end = os_strchr(end + 1, ';'); |
| } |
| if (end) |
| len = end - pos; |
| else |
| len = os_strlen(pos); |
| if (tls_match_altsubject_component(cert, type, pos, len) > 0) |
| return 1; |
| pos = end + 1; |
| } while (end); |
| |
| return 0; |
| } |
| |
| |
| #ifndef CONFIG_NATIVE_WINDOWS |
| static int domain_suffix_match(const u8 *val, size_t len, const char *match, |
| size_t match_len, int full) |
| { |
| size_t i; |
| |
| /* Check for embedded nuls that could mess up suffix matching */ |
| for (i = 0; i < len; i++) { |
| if (val[i] == '\0') { |
| wpa_printf(MSG_DEBUG, "TLS: Embedded null in a string - reject"); |
| return 0; |
| } |
| } |
| |
| if (match_len > len || (full && match_len != len)) |
| return 0; |
| |
| if (os_strncasecmp((const char *) val + len - match_len, match, |
| match_len) != 0) |
| return 0; /* no match */ |
| |
| if (match_len == len) |
| return 1; /* exact match */ |
| |
| if (val[len - match_len - 1] == '.') |
| return 1; /* full label match completes suffix match */ |
| |
| wpa_printf(MSG_DEBUG, "TLS: Reject due to incomplete label match"); |
| return 0; |
| } |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| |
| struct tls_dn_field_order_cnt { |
| u8 cn; |
| u8 c; |
| u8 l; |
| u8 st; |
| u8 o; |
| u8 ou; |
| u8 email; |
| }; |
| |
| |
| static int get_dn_field_index(const struct tls_dn_field_order_cnt *dn_cnt, |
| int nid) |
| { |
| switch (nid) { |
| case NID_commonName: |
| return dn_cnt->cn; |
| case NID_countryName: |
| return dn_cnt->c; |
| case NID_localityName: |
| return dn_cnt->l; |
| case NID_stateOrProvinceName: |
| return dn_cnt->st; |
| case NID_organizationName: |
| return dn_cnt->o; |
| case NID_organizationalUnitName: |
| return dn_cnt->ou; |
| case NID_pkcs9_emailAddress: |
| return dn_cnt->email; |
| default: |
| wpa_printf(MSG_ERROR, |
| "TLS: Unknown NID '%d' in check_cert_subject", |
| nid); |
| return -1; |
| } |
| } |
| |
| |
| /** |
| * match_dn_field - Match configuration DN field against Certificate DN field |
| * @cert: Certificate |
| * @nid: NID of DN field |
| * @field: Field name |
| * @value DN field value which is passed from configuration |
| * e.g., if configuration have C=US and this argument will point to US. |
| * @dn_cnt: DN matching context |
| * Returns: 1 on success and 0 on failure |
| */ |
| static int match_dn_field(const X509 *cert, int nid, const char *field, |
| const char *value, |
| const struct tls_dn_field_order_cnt *dn_cnt) |
| { |
| int i, ret = 0, len, config_dn_field_index, match_index = 0; |
| X509_NAME *name; |
| |
| len = os_strlen(value); |
| name = X509_get_subject_name((X509 *) cert); |
| |
| /* Assign incremented cnt for every field of DN to check DN field in |
| * right order */ |
| config_dn_field_index = get_dn_field_index(dn_cnt, nid); |
| if (config_dn_field_index < 0) |
| return 0; |
| |
| /* Fetch value based on NID */ |
| for (i = -1; (i = X509_NAME_get_index_by_NID(name, nid, i)) > -1;) { |
| X509_NAME_ENTRY *e; |
| ASN1_STRING *cn; |
| |
| e = X509_NAME_get_entry(name, i); |
| if (!e) |
| continue; |
| |
| cn = X509_NAME_ENTRY_get_data(e); |
| if (!cn) |
| continue; |
| |
| match_index++; |
| |
| /* check for more than one DN field with same name */ |
| if (match_index != config_dn_field_index) |
| continue; |
| |
| /* Check wildcard at the right end side */ |
| /* E.g., if OU=develop* mentioned in configuration, allow 'OU' |
| * of the subject in the client certificate to start with |
| * 'develop' */ |
| if (len > 0 && value[len - 1] == '*') { |
| /* Compare actual certificate DN field value with |
| * configuration DN field value up to the specified |
| * length. */ |
| ret = ASN1_STRING_length(cn) >= len - 1 && |
| os_memcmp(ASN1_STRING_get0_data(cn), value, |
| len - 1) == 0; |
| } else { |
| /* Compare actual certificate DN field value with |
| * configuration DN field value */ |
| ret = ASN1_STRING_length(cn) == len && |
| os_memcmp(ASN1_STRING_get0_data(cn), value, |
| len) == 0; |
| } |
| if (!ret) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Failed to match %s '%s' with certificate DN field value '%s'", |
| field, value, ASN1_STRING_get0_data(cn)); |
| } |
| break; |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * get_value_from_field - Get value from DN field |
| * @cert: Certificate |
| * @field_str: DN field string which is passed from configuration file (e.g., |
| * C=US) |
| * @dn_cnt: DN matching context |
| * Returns: 1 on success and 0 on failure |
| */ |
| static int get_value_from_field(const X509 *cert, char *field_str, |
| struct tls_dn_field_order_cnt *dn_cnt) |
| { |
| int nid; |
| char *context = NULL, *name, *value; |
| |
| if (os_strcmp(field_str, "*") == 0) |
| return 1; /* wildcard matches everything */ |
| |
| name = str_token(field_str, "=", &context); |
| if (!name) |
| return 0; |
| |
| /* Compare all configured DN fields and assign nid based on that to |
| * fetch correct value from certificate subject */ |
| if (os_strcmp(name, "CN") == 0) { |
| nid = NID_commonName; |
| dn_cnt->cn++; |
| } else if(os_strcmp(name, "C") == 0) { |
| nid = NID_countryName; |
| dn_cnt->c++; |
| } else if (os_strcmp(name, "L") == 0) { |
| nid = NID_localityName; |
| dn_cnt->l++; |
| } else if (os_strcmp(name, "ST") == 0) { |
| nid = NID_stateOrProvinceName; |
| dn_cnt->st++; |
| } else if (os_strcmp(name, "O") == 0) { |
| nid = NID_organizationName; |
| dn_cnt->o++; |
| } else if (os_strcmp(name, "OU") == 0) { |
| nid = NID_organizationalUnitName; |
| dn_cnt->ou++; |
| } else if (os_strcmp(name, "emailAddress") == 0) { |
| nid = NID_pkcs9_emailAddress; |
| dn_cnt->email++; |
| } else { |
| wpa_printf(MSG_ERROR, |
| "TLS: Unknown field '%s' in check_cert_subject", name); |
| return 0; |
| } |
| |
| value = str_token(field_str, "=", &context); |
| if (!value) { |
| wpa_printf(MSG_ERROR, |
| "TLS: Distinguished Name field '%s' value is not defined in check_cert_subject", |
| name); |
| return 0; |
| } |
| |
| return match_dn_field(cert, nid, name, value, dn_cnt); |
| } |
| |
| |
| /** |
| * tls_match_dn_field - Match subject DN field with check_cert_subject |
| * @cert: Certificate |
| * @match: check_cert_subject string |
| * Returns: Return 1 on success and 0 on failure |
| */ |
| static int tls_match_dn_field(X509 *cert, const char *match) |
| { |
| const char *token, *last = NULL; |
| char field[256]; |
| struct tls_dn_field_order_cnt dn_cnt; |
| |
| os_memset(&dn_cnt, 0, sizeof(dn_cnt)); |
| |
| /* Maximum length of each DN field is 255 characters */ |
| |
| /* Process each '/' delimited field */ |
| while ((token = cstr_token(match, "/", &last))) { |
| if (last - token >= (int) sizeof(field)) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Too long DN matching field value in '%s'", |
| match); |
| return 0; |
| } |
| os_memcpy(field, token, last - token); |
| field[last - token] = '\0'; |
| |
| if (!get_value_from_field(cert, field, &dn_cnt)) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: No match for DN '%s'", |
| field); |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| #ifndef CONFIG_NATIVE_WINDOWS |
| static int tls_match_suffix_helper(X509 *cert, const char *match, |
| size_t match_len, int full) |
| { |
| GENERAL_NAME *gen; |
| void *ext; |
| int i; |
| stack_index_t j; |
| int dns_name = 0; |
| X509_NAME *name; |
| |
| wpa_printf(MSG_DEBUG, "TLS: Match domain against %s%s", |
| full ? "": "suffix ", match); |
| |
| ext = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); |
| |
| for (j = 0; ext && j < sk_GENERAL_NAME_num(ext); j++) { |
| gen = sk_GENERAL_NAME_value(ext, j); |
| if (gen->type != GEN_DNS) |
| continue; |
| dns_name++; |
| wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate dNSName", |
| gen->d.dNSName->data, |
| gen->d.dNSName->length); |
| if (domain_suffix_match(gen->d.dNSName->data, |
| gen->d.dNSName->length, |
| match, match_len, full) == 1) { |
| wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", |
| full ? "Match" : "Suffix match"); |
| sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free); |
| return 1; |
| } |
| } |
| sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free); |
| |
| if (dns_name) { |
| wpa_printf(MSG_DEBUG, "TLS: None of the dNSName(s) matched"); |
| return 0; |
| } |
| |
| name = X509_get_subject_name(cert); |
| i = -1; |
| for (;;) { |
| X509_NAME_ENTRY *e; |
| ASN1_STRING *cn; |
| |
| i = X509_NAME_get_index_by_NID(name, NID_commonName, i); |
| if (i == -1) |
| break; |
| e = X509_NAME_get_entry(name, i); |
| if (e == NULL) |
| continue; |
| cn = X509_NAME_ENTRY_get_data(e); |
| if (cn == NULL) |
| continue; |
| wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", |
| cn->data, cn->length); |
| if (domain_suffix_match(cn->data, cn->length, |
| match, match_len, full) == 1) { |
| wpa_printf(MSG_DEBUG, "TLS: %s in commonName found", |
| full ? "Match" : "Suffix match"); |
| return 1; |
| } |
| } |
| |
| wpa_printf(MSG_DEBUG, "TLS: No CommonName %smatch found", |
| full ? "": "suffix "); |
| return 0; |
| } |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| |
| static int tls_match_suffix(X509 *cert, const char *match, int full) |
| { |
| #ifdef CONFIG_NATIVE_WINDOWS |
| /* wincrypt.h has conflicting X509_NAME definition */ |
| return -1; |
| #else /* CONFIG_NATIVE_WINDOWS */ |
| const char *token, *last = NULL; |
| |
| /* Process each match alternative separately until a match is found */ |
| while ((token = cstr_token(match, ";", &last))) { |
| if (tls_match_suffix_helper(cert, token, last - token, full)) |
| return 1; |
| } |
| |
| return 0; |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| } |
| |
| |
| 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; |
| struct tls_context *context = conn->context; |
| |
| #ifdef ANDROID |
| log_cert_validation_failure(err_str); |
| #endif |
| |
| if (context->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; |
| context->event_cb(context->cb_ctx, TLS_CERT_CHAIN_FAILURE, &ev); |
| wpabuf_free(cert); |
| } |
| |
| |
| static int openssl_cert_tod(X509 *cert) |
| { |
| CERTIFICATEPOLICIES *ext; |
| stack_index_t i; |
| char buf[100]; |
| int res; |
| int tod = 0; |
| |
| ext = X509_get_ext_d2i(cert, NID_certificate_policies, NULL, NULL); |
| if (!ext) |
| return 0; |
| |
| for (i = 0; i < sk_POLICYINFO_num(ext); i++) { |
| POLICYINFO *policy; |
| |
| policy = sk_POLICYINFO_value(ext, i); |
| res = OBJ_obj2txt(buf, sizeof(buf), policy->policyid, 0); |
| if (res < 0 || (size_t) res >= sizeof(buf)) |
| continue; |
| wpa_printf(MSG_DEBUG, "OpenSSL: Certificate Policy %s", buf); |
| if (os_strcmp(buf, "1.3.6.1.4.1.40808.1.3.1") == 0) |
| tod = 1; /* TOD-STRICT */ |
| else if (os_strcmp(buf, "1.3.6.1.4.1.40808.1.3.2") == 0 && !tod) |
| tod = 2; /* TOD-TOFU */ |
| } |
| sk_POLICYINFO_pop_free(ext, POLICYINFO_free); |
| |
| return tod; |
| } |
| |
| |
| 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; |
| struct tls_context *context = conn->context; |
| char *altsubject[TLS_MAX_ALT_SUBJECT]; |
| int alt, num_altsubject = 0; |
| GENERAL_NAME *gen; |
| void *ext; |
| stack_index_t i; |
| ASN1_INTEGER *ser; |
| char serial_num[128]; |
| #ifdef CONFIG_SHA256 |
| u8 hash[32]; |
| #endif /* CONFIG_SHA256 */ |
| |
| if (context->event_cb == NULL) |
| return; |
| |
| os_memset(&ev, 0, sizeof(ev)); |
| if (conn->cert_probe || (conn->flags & TLS_CONN_EXT_CERT_CHECK) || |
| context->cert_in_cb) { |
| 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; |
| |
| ser = X509_get_serialNumber(err_cert); |
| if (ser) { |
| wpa_snprintf_hex_uppercase(serial_num, sizeof(serial_num), |
| ASN1_STRING_get0_data(ser), |
| ASN1_STRING_length(ser)); |
| ev.peer_cert.serial_num = serial_num; |
| } |
| |
| ext = X509_get_ext_d2i(err_cert, NID_subject_alt_name, NULL, NULL); |
| for (i = 0; ext && i < sk_GENERAL_NAME_num(ext); i++) { |
| char *pos; |
| |
| if (num_altsubject == TLS_MAX_ALT_SUBJECT) |
| break; |
| gen = sk_GENERAL_NAME_value(ext, i); |
| if (gen->type != GEN_EMAIL && |
| gen->type != GEN_DNS && |
| gen->type != GEN_URI) |
| continue; |
| |
| pos = os_malloc(10 + gen->d.ia5->length + 1); |
| if (pos == NULL) |
| break; |
| altsubject[num_altsubject++] = pos; |
| |
| switch (gen->type) { |
| case GEN_EMAIL: |
| os_memcpy(pos, "EMAIL:", 6); |
| pos += 6; |
| break; |
| case GEN_DNS: |
| os_memcpy(pos, "DNS:", 4); |
| pos += 4; |
| break; |
| case GEN_URI: |
| os_memcpy(pos, "URI:", 4); |
| pos += 4; |
| break; |
| } |
| |
| os_memcpy(pos, gen->d.ia5->data, gen->d.ia5->length); |
| pos += gen->d.ia5->length; |
| *pos = '\0'; |
| } |
| sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free); |
| |
| for (alt = 0; alt < num_altsubject; alt++) |
| ev.peer_cert.altsubject[alt] = altsubject[alt]; |
| ev.peer_cert.num_altsubject = num_altsubject; |
| |
| ev.peer_cert.tod = openssl_cert_tod(err_cert); |
| |
| context->event_cb(context->cb_ctx, TLS_PEER_CERTIFICATE, &ev); |
| wpabuf_free(cert); |
| for (alt = 0; alt < num_altsubject; alt++) |
| os_free(altsubject[alt]); |
| } |
| |
| |
| static void debug_print_cert(X509 *cert, const char *title) |
| { |
| #ifndef CONFIG_NO_STDOUT_DEBUG |
| BIO *out; |
| size_t rlen; |
| char *txt; |
| int res; |
| |
| if (wpa_debug_level > MSG_DEBUG) |
| return; |
| |
| out = BIO_new(BIO_s_mem()); |
| if (!out) |
| return; |
| |
| X509_print(out, cert); |
| rlen = BIO_ctrl_pending(out); |
| txt = os_malloc(rlen + 1); |
| if (txt) { |
| res = BIO_read(out, txt, rlen); |
| if (res > 0) { |
| txt[res] = '\0'; |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s\n%s", title, txt); |
| } |
| os_free(txt); |
| } |
| |
| BIO_free(out); |
| #endif /* CONFIG_NO_STDOUT_DEBUG */ |
| } |
| |
| |
| static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) |
| { |
| char buf[256]; |
| X509 *err_cert; |
| int err, depth; |
| SSL *ssl; |
| struct tls_connection *conn; |
| struct tls_context *context; |
| char *match, *altmatch, *suffix_match, *domain_match; |
| const char *check_cert_subject; |
| const char *err_str; |
| |
| err_cert = X509_STORE_CTX_get_current_cert(x509_ctx); |
| if (!err_cert) |
| return 0; |
| |
| err = X509_STORE_CTX_get_error(x509_ctx); |
| depth = X509_STORE_CTX_get_error_depth(x509_ctx); |
| ssl = X509_STORE_CTX_get_ex_data(x509_ctx, |
| SSL_get_ex_data_X509_STORE_CTX_idx()); |
| os_snprintf(buf, sizeof(buf), "Peer certificate - depth %d", depth); |
| debug_print_cert(err_cert, buf); |
| X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf)); |
| |
| conn = SSL_get_app_data(ssl); |
| if (conn == NULL) |
| return 0; |
| |
| if (depth == 0) |
| conn->peer_cert = err_cert; |
| else if (depth == 1) |
| conn->peer_issuer = err_cert; |
| else if (depth == 2) |
| conn->peer_issuer_issuer = err_cert; |
| |
| context = conn->context; |
| match = conn->subject_match; |
| altmatch = conn->altsubject_match; |
| suffix_match = conn->suffix_match; |
| domain_match = conn->domain_match; |
| |
| if (!preverify_ok && !conn->ca_cert_verify) |
| preverify_ok = 1; |
| if (!preverify_ok && depth > 0 && conn->server_cert_only) |
| preverify_ok = 1; |
| if (!preverify_ok && (conn->flags & TLS_CONN_DISABLE_TIME_CHECKS) && |
| (err == X509_V_ERR_CERT_HAS_EXPIRED || |
| err == X509_V_ERR_CERT_NOT_YET_VALID)) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Ignore certificate validity " |
| "time mismatch"); |
| preverify_ok = 1; |
| } |
| if (!preverify_ok && !conn->data->check_crl_strict && |
| (err == X509_V_ERR_CRL_HAS_EXPIRED || |
| err == X509_V_ERR_CRL_NOT_YET_VALID)) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Ignore certificate validity CRL time mismatch"); |
| preverify_ok = 1; |
| } |
| |
| err_str = X509_verify_cert_error_string(err); |
| |
| #ifdef CONFIG_SHA256 |
| /* |
| * Do not require preverify_ok so we can explicity allow otherwise |
| * invalid pinned server certificates. |
| */ |
| if (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; |
| } else if (!preverify_ok) { |
| /* |
| * Certificate matches pinned certificate, allow |
| * regardless of other problems. |
| */ |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Ignore validation issues for a pinned server certificate"); |
| preverify_ok = 1; |
| } |
| wpabuf_free(cert); |
| } |
| } |
| #endif /* CONFIG_SHA256 */ |
| |
| if (!preverify_ok) { |
| /* Send cert events for the peer certificate chain so that |
| * the upper layers get information about it even if |
| * validation of a CA certificate fails. */ |
| STACK_OF(X509) *chain; |
| int num_of_certs; |
| |
| chain = X509_STORE_CTX_get1_chain(x509_ctx); |
| num_of_certs = sk_X509_num(chain); |
| if (chain && num_of_certs > 0) { |
| char buf2[256]; |
| X509 *cert; |
| int cur_depth; |
| |
| for (cur_depth = num_of_certs - 1; cur_depth >= 0; cur_depth--) { |
| cert = sk_X509_value(chain, cur_depth); |
| X509_NAME_oneline(X509_get_subject_name(cert), |
| buf2, sizeof(buf2)); |
| |
| openssl_tls_cert_event(conn, cert, cur_depth, buf2); |
| } |
| } |
| if (chain) |
| sk_X509_pop_free(chain, X509_free); |
| |
| char *format_str = "TLS: Certificate verification failed," |
| " error %d (%s) depth %d for '%s'"; |
| int msg_len = snprintf(NULL, 0, format_str, err, err_str, depth, buf) + 1; |
| char *msg = os_malloc(msg_len); |
| snprintf(msg, msg_len, format_str, err, err_str, depth, buf); |
| |
| wpa_printf(MSG_WARNING, "%s", msg); |
| if (conn != NULL && conn->context != NULL |
| && openssl_failure_callback_global != NULL) { |
| (*openssl_failure_callback_global)(conn->context->cb_ctx, msg); |
| } |
| os_free(msg); |
| |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| err_str, TLS_FAIL_UNSPECIFIED); |
| return preverify_ok; |
| } |
| |
| openssl_tls_cert_event(conn, err_cert, depth, buf); |
| |
| 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); |
| check_cert_subject = conn->check_cert_subject; |
| if (!check_cert_subject) |
| check_cert_subject = conn->data->check_cert_subject; |
| if (check_cert_subject) { |
| if (depth == 0 && |
| !tls_match_dn_field(err_cert, check_cert_subject)) { |
| preverify_ok = 0; |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| "Distinguished Name", |
| TLS_FAIL_DN_MISMATCH); |
| } |
| } |
| 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 if (depth == 0 && suffix_match && |
| !tls_match_suffix(err_cert, suffix_match, 0)) { |
| wpa_printf(MSG_WARNING, "TLS: Domain suffix match '%s' not found", |
| suffix_match); |
| preverify_ok = 0; |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| "Domain suffix mismatch", |
| TLS_FAIL_DOMAIN_SUFFIX_MISMATCH); |
| } else if (depth == 0 && domain_match && |
| !tls_match_suffix(err_cert, domain_match, 1)) { |
| wpa_printf(MSG_WARNING, "TLS: Domain match '%s' not found", |
| domain_match); |
| preverify_ok = 0; |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| "Domain mismatch", |
| TLS_FAIL_DOMAIN_MISMATCH); |
| } |
| |
| 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); |
| } |
| |
| #ifdef CONFIG_SUITEB |
| if (conn->flags & TLS_CONN_SUITEB) { |
| EVP_PKEY *pk; |
| int len = -1; |
| |
| pk = X509_get_pubkey(err_cert); |
| if (pk) { |
| len = EVP_PKEY_bits(pk); |
| EVP_PKEY_free(pk); |
| } |
| |
| if (len >= 0) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: RSA modulus size: %d bits", len); |
| if (len < 3072) { |
| preverify_ok = 0; |
| openssl_tls_fail_event( |
| conn, err_cert, err, |
| depth, buf, |
| "Insufficient RSA modulus size", |
| TLS_FAIL_INSUFFICIENT_KEY_LEN); |
| } |
| } |
| } |
| #endif /* CONFIG_SUITEB */ |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| if (depth == 0 && (conn->flags & TLS_CONN_REQUEST_OCSP) && |
| preverify_ok) { |
| enum ocsp_result res; |
| |
| res = check_ocsp_resp(conn->ssl_ctx, conn->ssl, err_cert, |
| conn->peer_issuer, |
| conn->peer_issuer_issuer); |
| if (res == OCSP_REVOKED) { |
| preverify_ok = 0; |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| "certificate revoked", |
| TLS_FAIL_REVOKED); |
| if (err == X509_V_OK) |
| X509_STORE_CTX_set_error( |
| x509_ctx, X509_V_ERR_CERT_REVOKED); |
| } else if (res != OCSP_GOOD && |
| (conn->flags & TLS_CONN_REQUIRE_OCSP)) { |
| preverify_ok = 0; |
| openssl_tls_fail_event(conn, err_cert, err, depth, buf, |
| "bad certificate status response", |
| TLS_FAIL_UNSPECIFIED); |
| } |
| } |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| |
| if (depth == 0 && preverify_ok && context->event_cb != NULL) |
| context->event_cb(context->cb_ctx, |
| TLS_CERT_CHAIN_SUCCESS, NULL); |
| |
| if (depth == 0 && preverify_ok) { |
| os_free(conn->peer_subject); |
| conn->peer_subject = os_strdup(buf); |
| } |
| |
| return preverify_ok; |
| } |
| |
| |
| #ifndef OPENSSL_NO_STDIO |
| static int tls_load_ca_der(struct tls_data *data, const char *ca_cert) |
| { |
| SSL_CTX *ssl_ctx = data->ssl; |
| X509_LOOKUP *lookup; |
| int ret = 0; |
| |
| lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(ssl_ctx), |
| X509_LOOKUP_file()); |
| if (lookup == NULL) { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed add lookup for X509 store"); |
| return -1; |
| } |
| |
| if (!X509_LOOKUP_load_file(lookup, ca_cert, X509_FILETYPE_ASN1)) { |
| unsigned long err = ERR_peek_error(); |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed load CA in DER format"); |
| if (ERR_GET_LIB(err) == ERR_LIB_X509 && |
| ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - ignoring " |
| "cert already in hash table error", |
| __func__); |
| } else |
| ret = -1; |
| } |
| |
| return ret; |
| } |
| #endif /* OPENSSL_NO_STDIO */ |
| |
| |
| static int tls_connection_ca_cert(struct tls_data *data, |
| struct tls_connection *conn, |
| const char *ca_cert, const u8 *ca_cert_blob, |
| size_t ca_cert_blob_len, const char *ca_path) |
| { |
| SSL_CTX *ssl_ctx = data->ssl; |
| X509_STORE *store; |
| |
| /* |
| * Remove previously configured trusted CA certificates before adding |
| * new ones. |
| */ |
| store = X509_STORE_new(); |
| if (store == NULL) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - failed to allocate new " |
| "certificate store", __func__); |
| return -1; |
| } |
| SSL_CTX_set_cert_store(ssl_ctx, store); |
| |
| 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, |
| (const unsigned char **) &ca_cert_blob, |
| ca_cert_blob_len); |
| if (cert == NULL) { |
| BIO *bio = BIO_new_mem_buf(ca_cert_blob, |
| ca_cert_blob_len); |
| |
| if (bio) { |
| cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| BIO_free(bio); |
| } |
| |
| if (!cert) { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to parse ca_cert_blob"); |
| return -1; |
| } |
| |
| while (ERR_get_error()) { |
| /* Ignore errors from DER conversion. */ |
| } |
| } |
| |
| if (!X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx), |
| cert)) { |
| unsigned long err = ERR_peek_error(); |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to add ca_cert_blob to " |
| "certificate store"); |
| if (ERR_GET_LIB(err) == ERR_LIB_X509 && |
| ERR_GET_REASON(err) == |
| X509_R_CERT_ALREADY_IN_HASH_TABLE) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - ignoring " |
| "cert already in hash table error", |
| __func__); |
| } else { |
| X509_free(cert); |
| return -1; |
| } |
| } |
| X509_free(cert); |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - added ca_cert_blob " |
| "to certificate store", __func__); |
| return 0; |
| } |
| |
| #ifdef ANDROID |
| /* Single alias */ |
| if (ca_cert && os_strncmp(ANDROID_KEYSTORE_PREFIX, ca_cert, |
| ANDROID_KEYSTORE_PREFIX_LEN) == 0) { |
| if (tls_add_ca_from_keystore(SSL_CTX_get_cert_store(ssl_ctx), |
| &ca_cert[ANDROID_KEYSTORE_PREFIX_LEN], conn) < 0) |
| return -1; |
| SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); |
| return 0; |
| } |
| |
| /* Multiple aliases separated by space */ |
| if (ca_cert && os_strncmp(ANDROID_KEYSTORE_ENCODED_PREFIX, ca_cert, |
| ANDROID_KEYSTORE_ENCODED_PREFIX_LEN) == 0) { |
| char *aliases = os_strdup( |
| &ca_cert[ANDROID_KEYSTORE_ENCODED_PREFIX_LEN]); |
| const char *delim = " "; |
| int rc = 0; |
| char *savedptr; |
| char *alias; |
| |
| if (!aliases) |
| return -1; |
| alias = strtok_r(aliases, delim, &savedptr); |
| for (; alias; alias = strtok_r(NULL, delim, &savedptr)) { |
| if (tls_add_ca_from_keystore_encoded( |
| SSL_CTX_get_cert_store(ssl_ctx), alias, conn)) { |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: Failed to add ca_cert %s from keystore", |
| alias); |
| rc = -1; |
| break; |
| } |
| } |
| os_free(aliases); |
| if (rc) |
| return rc; |
| |
| SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); |
| return 0; |
| } |
| #endif /* ANDROID */ |
| |
| #ifdef CONFIG_NATIVE_WINDOWS |
| if (ca_cert && tls_cryptoapi_ca_cert(ssl_ctx, conn->ssl, ca_cert) == |
| 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Added CA certificates from " |
| "system certificate store"); |
| return 0; |
| } |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| if (ca_cert || ca_path) { |
| #ifndef OPENSSL_NO_STDIO |
| if (SSL_CTX_load_verify_locations(ssl_ctx, ca_cert, ca_path) != |
| 1) { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to load root certificates"); |
| if (ca_cert && |
| tls_load_ca_der(data, ca_cert) == 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - loaded " |
| "DER format CA certificate", |
| __func__); |
| } else |
| return -1; |
| } else { |
| wpa_printf(MSG_DEBUG, "TLS: Trusted root " |
| "certificate(s) loaded"); |
| tls_get_errors(data); |
| } |
| #else /* OPENSSL_NO_STDIO */ |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO", |
| __func__); |
| return -1; |
| #endif /* OPENSSL_NO_STDIO */ |
| } else { |
| /* No ca_cert configured - do not try to verify server |
| * certificate */ |
| conn->ca_cert_verify = 0; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int tls_global_ca_cert(struct tls_data *data, const char *ca_cert) |
| { |
| SSL_CTX *ssl_ctx = data->ssl; |
| |
| if (ca_cert) { |
| if (SSL_CTX_load_verify_locations(ssl_ctx, ca_cert, NULL) != 1) |
| { |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to load root certificates"); |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "TLS: Trusted root " |
| "certificate(s) loaded"); |
| |
| #ifndef OPENSSL_NO_STDIO |
| /* Add the same CAs to the client certificate requests */ |
| SSL_CTX_set_client_CA_list(ssl_ctx, |
| SSL_load_client_CA_file(ca_cert)); |
| #endif /* OPENSSL_NO_STDIO */ |
| |
| os_free(data->ca_cert); |
| data->ca_cert = os_strdup(ca_cert); |
| } |
| |
| return 0; |
| } |
| |
| |
| int tls_global_set_verify(void *ssl_ctx, int check_crl, int strict) |
| { |
| int flags; |
| |
| if (check_crl) { |
| struct tls_data *data = ssl_ctx; |
| X509_STORE *cs = SSL_CTX_get_cert_store(data->ssl); |
| if (cs == NULL) { |
| tls_show_errors(MSG_INFO, __func__, "Failed to get " |
| "certificate store when enabling " |
| "check_crl"); |
| return -1; |
| } |
| flags = X509_V_FLAG_CRL_CHECK; |
| if (check_crl == 2) |
| flags |= X509_V_FLAG_CRL_CHECK_ALL; |
| X509_STORE_set_flags(cs, flags); |
| |
| data->check_crl = check_crl; |
| data->check_crl_strict = strict; |
| os_get_reltime(&data->crl_last_reload); |
| } |
| return 0; |
| } |
| |
| |
| static int tls_connection_set_subject_match(struct tls_connection *conn, |
| const char *subject_match, |
| const char *altsubject_match, |
| const char *suffix_match, |
| const char *domain_match, |
| const char *check_cert_subject) |
| { |
| os_free(conn->subject_match); |
| conn->subject_match = NULL; |
| if (subject_match) { |
| conn->subject_match = os_strdup(subject_match); |
| if (conn->subject_match == NULL) |
| return -1; |
| } |
| |
| os_free(conn->altsubject_match); |
| conn->altsubject_match = NULL; |
| if (altsubject_match) { |
| conn->altsubject_match = os_strdup(altsubject_match); |
| if (conn->altsubject_match == NULL) |
| return -1; |
| } |
| |
| os_free(conn->suffix_match); |
| conn->suffix_match = NULL; |
| if (suffix_match) { |
| conn->suffix_match = os_strdup(suffix_match); |
| if (conn->suffix_match == NULL) |
| return -1; |
| } |
| |
| os_free(conn->domain_match); |
| conn->domain_match = NULL; |
| if (domain_match) { |
| conn->domain_match = os_strdup(domain_match); |
| if (conn->domain_match == NULL) |
| return -1; |
| } |
| |
| os_free(conn->check_cert_subject); |
| conn->check_cert_subject = NULL; |
| if (check_cert_subject) { |
| conn->check_cert_subject = os_strdup(check_cert_subject); |
| if (!conn->check_cert_subject) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| #ifdef CONFIG_SUITEB |
| static int suiteb_cert_cb(SSL *ssl, void *arg) |
| { |
| struct tls_connection *conn = arg; |
| |
| /* |
| * This cert_cb() is not really the best location for doing a |
| * constraint check for the ServerKeyExchange message, but this seems to |
| * be the only place where the current OpenSSL sequence can be |
| * terminated cleanly with an TLS alert going out to the server. |
| */ |
| |
| if (!(conn->flags & TLS_CONN_SUITEB)) |
| return 1; |
| |
| /* DHE is enabled only with DHE-RSA-AES256-GCM-SHA384 */ |
| if (conn->cipher_suite != 0x9f) |
| return 1; |
| |
| if (conn->server_dh_prime_len >= 3072) |
| return 1; |
| |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Server DH prime length (%d bits) not sufficient for Suite B RSA - reject handshake", |
| conn->server_dh_prime_len); |
| return 0; |
| } |
| #endif /* CONFIG_SUITEB */ |
| |
| |
| static int tls_set_conn_flags(struct tls_connection *conn, unsigned int flags, |
| const char *openssl_ciphers) |
| { |
| SSL *ssl = conn->ssl; |
| |
| #ifdef SSL_OP_NO_TICKET |
| if (flags & TLS_CONN_DISABLE_SESSION_TICKET) |
| SSL_set_options(ssl, SSL_OP_NO_TICKET); |
| else |
| SSL_clear_options(ssl, SSL_OP_NO_TICKET); |
| #endif /* SSL_OP_NO_TICKET */ |
| |
| #ifdef SSL_OP_LEGACY_SERVER_CONNECT |
| if (flags & TLS_CONN_ALLOW_UNSAFE_RENEGOTIATION) |
| SSL_set_options(ssl, SSL_OP_LEGACY_SERVER_CONNECT); |
| #endif /* SSL_OP_LEGACY_SERVER_CONNECT */ |
| |
| #ifdef SSL_OP_NO_TLSv1 |
| if (flags & TLS_CONN_DISABLE_TLSv1_0) |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1); |
| else |
| SSL_clear_options(ssl, SSL_OP_NO_TLSv1); |
| #endif /* SSL_OP_NO_TLSv1 */ |
| #ifdef SSL_OP_NO_TLSv1_1 |
| if (flags & TLS_CONN_DISABLE_TLSv1_1) |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1_1); |
| else |
| SSL_clear_options(ssl, SSL_OP_NO_TLSv1_1); |
| #endif /* SSL_OP_NO_TLSv1_1 */ |
| #ifdef SSL_OP_NO_TLSv1_2 |
| if (flags & TLS_CONN_DISABLE_TLSv1_2) |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1_2); |
| else |
| SSL_clear_options(ssl, SSL_OP_NO_TLSv1_2); |
| #endif /* SSL_OP_NO_TLSv1_2 */ |
| #ifdef SSL_OP_NO_TLSv1_3 |
| if (flags & TLS_CONN_DISABLE_TLSv1_3) |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1_3); |
| else |
| SSL_clear_options(ssl, SSL_OP_NO_TLSv1_3); |
| #endif /* SSL_OP_NO_TLSv1_3 */ |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L |
| if (flags & (TLS_CONN_ENABLE_TLSv1_0 | |
| TLS_CONN_ENABLE_TLSv1_1 | |
| TLS_CONN_ENABLE_TLSv1_2)) { |
| int version = 0; |
| |
| /* Explicit request to enable TLS versions even if needing to |
| * override systemwide policies. */ |
| if (flags & TLS_CONN_ENABLE_TLSv1_0) |
| version = TLS1_VERSION; |
| else if (flags & TLS_CONN_ENABLE_TLSv1_1) |
| version = TLS1_1_VERSION; |
| else if (flags & TLS_CONN_ENABLE_TLSv1_2) |
| version = TLS1_2_VERSION; |
| if (!version) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Invalid TLS version configuration"); |
| return -1; |
| } |
| |
| if (SSL_set_min_proto_version(ssl, version) != 1) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Failed to set minimum TLS version"); |
| return -1; |
| } |
| } |
| #endif /* >= 1.1.0 */ |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ |
| !defined(LIBRESSL_VERSION_NUMBER) && \ |
| !defined(OPENSSL_IS_BORINGSSL) |
| { |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| int need_level = 0; |
| #else |
| int need_level = 1; |
| #endif |
| |
| if ((flags & |
| (TLS_CONN_ENABLE_TLSv1_0 | TLS_CONN_ENABLE_TLSv1_1)) && |
| SSL_get_security_level(ssl) > need_level) { |
| /* |
| * Need to drop to security level 1 (or 0 with OpenSSL |
| * 3.0) to allow TLS versions older than 1.2 to be used |
| * when explicitly enabled in configuration. |
| */ |
| SSL_set_security_level(conn->ssl, need_level); |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_SUITEB |
| #ifdef OPENSSL_IS_BORINGSSL |
| /* Start with defaults from BoringSSL */ |
| SSL_set_verify_algorithm_prefs(conn->ssl, NULL, 0); |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| if (flags & TLS_CONN_SUITEB_NO_ECDH) { |
| const char *ciphers = "DHE-RSA-AES256-GCM-SHA384"; |
| |
| if (openssl_ciphers) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Override ciphers for Suite B (no ECDH): %s", |
| openssl_ciphers); |
| ciphers = openssl_ciphers; |
| } |
| if (SSL_set_cipher_list(ssl, ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B ciphers"); |
| return -1; |
| } |
| } else if (flags & TLS_CONN_SUITEB) { |
| #if OPENSSL_VERSION_NUMBER < 0x30000000L |
| EC_KEY *ecdh; |
| #endif |
| const char *ciphers = |
| "ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384"; |
| int nid[1] = { NID_secp384r1 }; |
| |
| if (openssl_ciphers) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Override ciphers for Suite B: %s", |
| openssl_ciphers); |
| ciphers = openssl_ciphers; |
| } |
| if (SSL_set_cipher_list(ssl, ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B ciphers"); |
| return -1; |
| } |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| if (SSL_set1_groups(ssl, nid, 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B groups"); |
| return -1; |
| } |
| |
| #else |
| if (SSL_set1_curves(ssl, nid, 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B curves"); |
| return -1; |
| } |
| |
| ecdh = EC_KEY_new_by_curve_name(NID_secp384r1); |
| if (!ecdh || SSL_set_tmp_ecdh(ssl, ecdh) != 1) { |
| EC_KEY_free(ecdh); |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set ECDH parameter"); |
| return -1; |
| } |
| EC_KEY_free(ecdh); |
| #endif |
| } |
| if ((flags & (TLS_CONN_SUITEB | TLS_CONN_SUITEB_NO_ECDH)) |
| #ifdef EAP_TLSV1_3 |
| && (flags & TLS_CONN_DISABLE_TLSv1_3) |
| #endif |
| ) { |
| #ifdef OPENSSL_IS_BORINGSSL |
| uint16_t sigalgs[1] = { SSL_SIGN_RSA_PKCS1_SHA384 }; |
| |
| if (SSL_set_verify_algorithm_prefs(conn->ssl, sigalgs, |
| 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B sigalgs"); |
| return -1; |
| } |
| #else /* OPENSSL_IS_BORINGSSL */ |
| /* ECDSA+SHA384 if need to add EC support here */ |
| if (SSL_set1_sigalgs_list(ssl, "RSA+SHA384") != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B sigalgs"); |
| return -1; |
| } |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1); |
| SSL_set_options(ssl, SSL_OP_NO_TLSv1_1); |
| SSL_set_cert_cb(ssl, suiteb_cert_cb, conn); |
| } |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| if (openssl_ciphers && os_strcmp(openssl_ciphers, "SUITEB192") == 0) { |
| uint16_t sigalgs[1] = { SSL_SIGN_ECDSA_SECP384R1_SHA384 }; |
| int nid[1] = { NID_secp384r1 }; |
| |
| if (SSL_set1_curves(ssl, nid, 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B curves"); |
| return -1; |
| } |
| |
| if (SSL_set_verify_algorithm_prefs(conn->ssl, sigalgs, |
| 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set Suite B sigalgs"); |
| return -1; |
| } |
| } |
| #else /* OPENSSL_IS_BORINGSSL */ |
| if (!(flags & (TLS_CONN_SUITEB | TLS_CONN_SUITEB_NO_ECDH)) && |
| openssl_ciphers && SSL_set_cipher_list(ssl, openssl_ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set openssl_ciphers '%s'", |
| openssl_ciphers); |
| return -1; |
| } |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| #else /* CONFIG_SUITEB */ |
| if (openssl_ciphers && SSL_set_cipher_list(ssl, openssl_ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set openssl_ciphers '%s'", |
| openssl_ciphers); |
| return -1; |
| } |
| #endif /* CONFIG_SUITEB */ |
| |
| if (flags & TLS_CONN_TEAP_ANON_DH) { |
| #ifndef TEAP_DH_ANON_CS |
| #define TEAP_DH_ANON_CS \ |
| "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:" \ |
| "ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:" \ |
| "ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:" \ |
| "DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:" \ |
| "DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:" \ |
| "DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:" \ |
| "ADH-AES256-GCM-SHA384:ADH-AES128-GCM-SHA256:" \ |
| "ADH-AES256-SHA256:ADH-AES128-SHA256:ADH-AES256-SHA:ADH-AES128-SHA" |
| #endif |
| static const char *cs = TEAP_DH_ANON_CS; |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ |
| !defined(LIBRESSL_VERSION_NUMBER) && \ |
| !defined(OPENSSL_IS_BORINGSSL) |
| /* |
| * Need to drop to security level 0 to allow anonymous |
| * cipher suites for EAP-TEAP. |
| */ |
| SSL_set_security_level(conn->ssl, 0); |
| #endif |
| |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Enable cipher suites for anonymous EAP-TEAP provisioning: %s", |
| cs); |
| if (SSL_set_cipher_list(conn->ssl, cs) != 1) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Cipher suite configuration failed"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn, |
| int verify_peer, unsigned int flags, |
| const u8 *session_ctx, size_t session_ctx_len) |
| { |
| static int counter = 0; |
| struct tls_data *data = ssl_ctx; |
| |
| if (conn == NULL) |
| return -1; |
| |
| if (verify_peer == 2) { |
| conn->ca_cert_verify = 1; |
| SSL_set_verify(conn->ssl, SSL_VERIFY_PEER | |
| SSL_VERIFY_CLIENT_ONCE, tls_verify_cb); |
| } else 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); |
| } |
| |
| if (tls_set_conn_flags(conn, flags, NULL) < 0) |
| return -1; |
| conn->flags = flags; |
| |
| SSL_set_accept_state(conn->ssl); |
| |
| if (data->tls_session_lifetime == 0) { |
| /* |
| * Set session id context to a unique value to make sure |
| * session resumption cannot be used either through session |
| * caching or TLS ticket extension. |
| */ |
| counter++; |
| SSL_set_session_id_context(conn->ssl, |
| (const unsigned char *) &counter, |
| sizeof(counter)); |
| } else if (session_ctx) { |
| SSL_set_session_id_context(conn->ssl, session_ctx, |
| session_ctx_len); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int tls_connection_client_cert(struct tls_connection *conn, |
| const char *client_cert, |
| const u8 *client_cert_blob, |
| size_t client_cert_blob_len) |
| { |
| if (client_cert == NULL && client_cert_blob == NULL) |
| return 0; |
| |
| #ifdef PKCS12_FUNCS |
| #ifdef LIBRESSL_VERSION_NUMBER |
| /* |
| * Clear previously set extra chain certificates, if any, from PKCS#12 |
| * processing in tls_parse_pkcs12() to allow LibreSSL to build a new |
| * chain properly. |
| */ |
| SSL_CTX_clear_extra_chain_certs(conn->ssl_ctx); |
| #endif /* LIBRESSL_VERSION_NUMBER */ |
| #endif /* PKCS12_FUNCS */ |
| |
| if (client_cert_blob && |
| SSL_use_certificate_ASN1(conn->ssl, (u8 *) client_cert_blob, |
| client_cert_blob_len) == 1) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_ASN1 --> " |
| "OK"); |
| return 0; |
| } else if (client_cert_blob) { |
| #if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20901000L |
| tls_show_errors(MSG_DEBUG, __func__, |
| "SSL_use_certificate_ASN1 failed"); |
| #else |
| BIO *bio; |
| X509 *x509; |
| |
| tls_show_errors(MSG_DEBUG, __func__, |
| "SSL_use_certificate_ASN1 failed"); |
| bio = BIO_new(BIO_s_mem()); |
| if (!bio) |
| return -1; |
| BIO_write(bio, client_cert_blob, client_cert_blob_len); |
| x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| if (!x509 || SSL_use_certificate(conn->ssl, x509) != 1) { |
| X509_free(x509); |
| BIO_free(bio); |
| return -1; |
| } |
| X509_free(x509); |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Found PEM encoded certificate from blob"); |
| while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL))) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Added an additional certificate into the chain"); |
| SSL_add0_chain_cert(conn->ssl, x509); |
| } |
| BIO_free(bio); |
| return 0; |
| #endif |
| } |
| |
| if (client_cert == NULL) |
| return -1; |
| |
| #ifdef ANDROID |
| if (os_strncmp(ANDROID_KEYSTORE_PREFIX, client_cert, |
| ANDROID_KEYSTORE_PREFIX_LEN) == 0) { |
| BIO *bio = BIO_from_keystore(&client_cert[ANDROID_KEYSTORE_PREFIX_LEN], conn); |
| X509 *x509 = NULL; |
| if (!bio) { |
| return -1; |
| } |
| // Keystore returns X.509 certificates in PEM encoding |
| x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| if (!x509 || SSL_use_certificate(conn->ssl, x509) != 1) { |
| X509_free(x509); |
| BIO_free(bio); |
| wpa_printf(MSG_ERROR, "OpenSSL: Unknown certificate encoding"); |
| return -1; |
| } |
| X509_free(x509); |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Found PEM encoded certificate from keystore: %s", |
| client_cert); |
| |
| // Read additional certificates into the chain |
| while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL))) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Added an additional certificate into the chain"); |
| // Takes ownership of x509, no need to free it here |
| SSL_add0_chain_cert(conn->ssl, x509); |
| } |
| BIO_free(bio); |
| return 0; |
| } |
| #endif /* ANDROID */ |
| |
| #ifndef OPENSSL_NO_STDIO |
| if (SSL_use_certificate_file(conn->ssl, client_cert, |
| SSL_FILETYPE_ASN1) == 1) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_file (DER)" |
| " --> OK"); |
| return 0; |
| } |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ |
| !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) |
| if (SSL_use_certificate_chain_file(conn->ssl, client_cert) == 1) { |
| ERR_clear_error(); |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_chain_file" |
| " --> OK"); |
| return 0; |
| } |
| #else |
| if (SSL_use_certificate_file(conn->ssl, client_cert, |
| SSL_FILETYPE_PEM) == 1) { |
| ERR_clear_error(); |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_file (PEM)" |
| " --> OK"); |
| return 0; |
| } |
| #endif |
| |
| tls_show_errors(MSG_DEBUG, __func__, |
| "SSL_use_certificate_file failed"); |
| #else /* OPENSSL_NO_STDIO */ |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO", __func__); |
| #endif /* OPENSSL_NO_STDIO */ |
| |
| return -1; |
| } |
| |
| |
| static int tls_global_client_cert(struct tls_data *data, |
| const char *client_cert) |
| { |
| #ifndef OPENSSL_NO_STDIO |
| SSL_CTX *ssl_ctx = data->ssl; |
| |
| if (client_cert == NULL) |
| return 0; |
| |
| if (SSL_CTX_use_certificate_file(ssl_ctx, client_cert, |
| SSL_FILETYPE_ASN1) != 1 && |
| SSL_CTX_use_certificate_chain_file(ssl_ctx, client_cert) != 1 && |
| SSL_CTX_use_certificate_file(ssl_ctx, client_cert, |
| SSL_FILETYPE_PEM) != 1) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to load client certificate"); |
| return -1; |
| } |
| return 0; |
| #else /* OPENSSL_NO_STDIO */ |
| if (client_cert == NULL) |
| return 0; |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO", __func__); |
| return -1; |
| #endif /* OPENSSL_NO_STDIO */ |
| } |
| |
| |
| #ifdef PKCS12_FUNCS |
| static int tls_parse_pkcs12(struct tls_data *data, SSL *ssl, PKCS12 *p12, |
| const char *passwd) |
| { |
| EVP_PKEY *pkey; |
| X509 *cert; |
| STACK_OF(X509) *certs; |
| int res = 0; |
| char buf[256]; |
| |
| pkey = NULL; |
| cert = NULL; |
| certs = NULL; |
| if (!passwd) |
| passwd = ""; |
| if (!PKCS12_parse(p12, passwd, &pkey, &cert, &certs)) { |
| tls_show_errors(MSG_DEBUG, __func__, |
| "Failed to parse PKCS12 file"); |
| PKCS12_free(p12); |
| return -1; |
| } |
| wpa_printf(MSG_DEBUG, "TLS: Successfully parsed PKCS12 data"); |
| |
| if (cert) { |
| X509_NAME_oneline(X509_get_subject_name(cert), buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "TLS: Got certificate from PKCS12: " |
| "subject='%s'", buf); |
| if (ssl) { |
| if (SSL_use_certificate(ssl, cert) != 1) |
| res = -1; |
| } else { |
| if (SSL_CTX_use_certificate(data->ssl, cert) != 1) |
| res = -1; |
| } |
| X509_free(cert); |
| } |
| |
| if (pkey) { |
| wpa_printf(MSG_DEBUG, "TLS: Got private key from PKCS12"); |
| if (ssl) { |
| if (SSL_use_PrivateKey(ssl, pkey) != 1) |
| res = -1; |
| } else { |
| if (SSL_CTX_use_PrivateKey(data->ssl, pkey) != 1) |
| res = -1; |
| } |
| EVP_PKEY_free(pkey); |
| } |
| |
| if (certs) { |
| #ifndef LIBRESSL_VERSION_NUMBER |
| if (ssl) |
| SSL_clear_chain_certs(ssl); |
| else |
| SSL_CTX_clear_chain_certs(data->ssl); |
| while ((cert = sk_X509_pop(certs)) != NULL) { |
| X509_NAME_oneline(X509_get_subject_name(cert), buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "TLS: additional certificate" |
| " from PKCS12: subject='%s'", buf); |
| if ((ssl && SSL_add1_chain_cert(ssl, cert) != 1) || |
| (!ssl && SSL_CTX_add1_chain_cert(data->ssl, |
| cert) != 1)) { |
| tls_show_errors(MSG_DEBUG, __func__, |
| "Failed to add additional certificate"); |
| res = -1; |
| X509_free(cert); |
| break; |
| } |
| X509_free(cert); |
| } |
| if (!res) { |
| /* Try to continue anyway */ |
| } |
| sk_X509_pop_free(certs, X509_free); |
| #ifndef OPENSSL_IS_BORINGSSL |
| if (ssl) |
| res = SSL_build_cert_chain( |
| ssl, |
| SSL_BUILD_CHAIN_FLAG_CHECK | |
| SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR); |
| else |
| res = SSL_CTX_build_cert_chain( |
| data->ssl, |
| SSL_BUILD_CHAIN_FLAG_CHECK | |
| SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR); |
| if (!res) { |
| tls_show_errors(MSG_DEBUG, __func__, |
| "Failed to build certificate chain"); |
| } else if (res == 2) { |
| wpa_printf(MSG_DEBUG, |
| "TLS: Ignore certificate chain verification error when building chain with PKCS#12 extra certificates"); |
| } |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| /* |
| * Try to continue regardless of result since it is possible for |
| * the extra certificates not to be required. |
| */ |
| res = 0; |
| #else /* LIBRESSL_VERSION_NUMBER */ |
| SSL_CTX_clear_extra_chain_certs(data->ssl); |
| while ((cert = sk_X509_pop(certs)) != NULL) { |
| X509_NAME_oneline(X509_get_subject_name(cert), buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "TLS: additional certificate" |
| " from PKCS12: subject='%s'", buf); |
| /* |
| * There is no SSL equivalent for the chain cert - so |
| * always add it to the context... |
| */ |
| if (SSL_CTX_add_extra_chain_cert(data->ssl, cert) != 1) |
| { |
| X509_free(cert); |
| res = -1; |
| break; |
| } |
| } |
| sk_X509_pop_free(certs, X509_free); |
| #endif /* LIBRSESSL_VERSION_NUMBER */ |
| } |
| |
| PKCS12_free(p12); |
| |
| if (res < 0) |
| tls_get_errors(data); |
| |
| return res; |
| } |
| #endif /* PKCS12_FUNCS */ |
| |
| |
| static int tls_read_pkcs12(struct tls_data *data, SSL *ssl, |
| const char *private_key, const char *passwd) |
| { |
| #ifdef PKCS12_FUNCS |
| FILE *f; |
| PKCS12 *p12; |
| |
| f = fopen(private_key, "rb"); |
| if (f == NULL) |
| return -1; |
| |
| p12 = d2i_PKCS12_fp(f, NULL); |
| fclose(f); |
| |
| if (p12 == NULL) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to use PKCS#12 file"); |
| return -1; |
| } |
| |
| return tls_parse_pkcs12(data, ssl, p12, passwd); |
| |
| #else /* PKCS12_FUNCS */ |
| wpa_printf(MSG_INFO, "TLS: PKCS12 support disabled - cannot read " |
| "p12/pfx files"); |
| return -1; |
| #endif /* PKCS12_FUNCS */ |
| } |
| |
| |
| static int tls_read_pkcs12_blob(struct tls_data *data, SSL *ssl, |
| const u8 *blob, size_t len, const char *passwd) |
| { |
| #ifdef PKCS12_FUNCS |
| PKCS12 *p12; |
| |
| p12 = d2i_PKCS12(NULL, (const unsigned char **) &blob, len); |
| if (p12 == NULL) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to use PKCS#12 blob"); |
| return -1; |
| } |
| |
| return tls_parse_pkcs12(data, ssl, p12, passwd); |
| |
| #else /* PKCS12_FUNCS */ |
| wpa_printf(MSG_INFO, "TLS: PKCS12 support disabled - cannot parse " |
| "p12/pfx blobs"); |
| return -1; |
| #endif /* PKCS12_FUNCS */ |
| } |
| |
| |
| #ifndef OPENSSL_NO_ENGINE |
| static int tls_engine_get_cert(struct tls_connection *conn, |
| const char *cert_id, |
| X509 **cert) |
| { |
| /* this runs after the private key is loaded so no PIN is required */ |
| struct { |
| const char *cert_id; |
| X509 *cert; |
| } params; |
| params.cert_id = cert_id; |
| params.cert = NULL; |
| |
| if (!ENGINE_ctrl_cmd(conn->engine, "LOAD_CERT_CTRL", |
| 0, ¶ms, NULL, 1)) { |
| unsigned long err = ERR_get_error(); |
| |
| wpa_printf(MSG_ERROR, "ENGINE: cannot load client cert with id" |
| " '%s' [%s]", cert_id, |
| ERR_error_string(err, NULL)); |
| if (tls_is_pin_error(err)) |
| return TLS_SET_PARAMS_ENGINE_PRV_BAD_PIN; |
| return TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| } |
| if (!params.cert) { |
| wpa_printf(MSG_ERROR, "ENGINE: did not properly cert with id" |
| " '%s'", cert_id); |
| return TLS_SET_PARAMS_ENGINE_PRV_INIT_FAILED; |
| } |
| *cert = params.cert; |
| return 0; |
| } |
| #endif /* OPENSSL_NO_ENGINE */ |
| |
| |
| static int tls_connection_engine_client_cert(struct tls_connection *conn, |
| const char *cert_id) |
| { |
| #ifndef OPENSSL_NO_ENGINE |
| X509 *cert; |
| |
| if (tls_engine_get_cert(conn, cert_id, &cert)) |
| return -1; |
| |
| if (!SSL_use_certificate(conn->ssl, cert)) { |
| tls_show_errors(MSG_ERROR, __func__, |
| "SSL_use_certificate failed"); |
| X509_free(cert); |
| return -1; |
| } |
| X509_free(cert); |
| wpa_printf(MSG_DEBUG, "ENGINE: SSL_use_certificate --> " |
| "OK"); |
| return 0; |
| |
| #else /* OPENSSL_NO_ENGINE */ |
| return -1; |
| #endif /* OPENSSL_NO_ENGINE */ |
| } |
| |
| |
| static int tls_connection_engine_ca_cert(struct tls_data *data, |
| struct tls_connection *conn, |
| const char *ca_cert_id) |
| { |
| #ifndef OPENSSL_NO_ENGINE |
| X509 *cert; |
| SSL_CTX *ssl_ctx = data->ssl; |
| X509_STORE *store; |
| |
| if (tls_engine_get_cert(conn, ca_cert_id, &cert)) |
| return -1; |
| |
| /* start off the same as tls_connection_ca_cert */ |
| store = X509_STORE_new(); |
| if (store == NULL) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - failed to allocate new " |
| "certificate store", __func__); |
| X509_free(cert); |
| return -1; |
| } |
| SSL_CTX_set_cert_store(ssl_ctx, store); |
| if (!X509_STORE_add_cert(store, cert)) { |
| unsigned long err = ERR_peek_error(); |
| tls_show_errors(MSG_WARNING, __func__, |
| "Failed to add CA certificate from engine " |
| "to certificate store"); |
| if (ERR_GET_LIB(err) == ERR_LIB_X509 && |
| ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - ignoring cert" |
| " already in hash table error", |
| __func__); |
| } else { |
| X509_free(cert); |
| return -1; |
| } |
| } |
| X509_free(cert); |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - added CA certificate from engine " |
| "to certificate store", __func__); |
| SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); |
| conn->ca_cert_verify = 1; |
| |
| return 0; |
| |
| #else /* OPENSSL_NO_ENGINE */ |
| return -1; |
| #endif /* OPENSSL_NO_ENGINE */ |
| } |
| |
| |
| static int tls_connection_engine_private_key(struct tls_connection *conn) |
| { |
| #if defined(ANDROID) || !defined(OPENSSL_NO_ENGINE) |
| if (SSL_use_PrivateKey(conn->ssl, conn->private_key) != 1) { |
| tls_show_errors(MSG_ERROR, __func__, |
| "ENGINE: cannot use private key for TLS"); |
| return -1; |
| } |
| if (!SSL_check_private_key(conn->ssl)) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Private key failed verification"); |
| return -1; |
| } |
| return 0; |
| #else /* OPENSSL_NO_ENGINE */ |
| wpa_printf(MSG_ERROR, "SSL: Configuration uses engine, but " |
| "engine support was not compiled in"); |
| return -1; |
| #endif /* OPENSSL_NO_ENGINE */ |
| } |
| |
| |
| #ifndef OPENSSL_NO_STDIO |
| static int tls_passwd_cb(char *buf, int size, int rwflag, void *password) |
| { |
| if (!password) |
| return 0; |
| os_strlcpy(buf, (const char *) password, size); |
| return os_strlen(buf); |
| } |
| #endif /* OPENSSL_NO_STDIO */ |
| |
| |
| static int tls_use_private_key_file(struct tls_data *data, SSL *ssl, |
| const char *private_key, |
| const char *private_key_passwd) |
| { |
| #ifndef OPENSSL_NO_STDIO |
| BIO *bio; |
| EVP_PKEY *pkey; |
| int ret; |
| |
| /* First try ASN.1 (DER). */ |
| bio = BIO_new_file(private_key, "r"); |
| if (!bio) |
| return -1; |
| pkey = d2i_PrivateKey_bio(bio, NULL); |
| BIO_free(bio); |
| |
| if (pkey) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s (DER) --> loaded", __func__); |
| } else { |
| /* Try PEM with the provided password. */ |
| bio = BIO_new_file(private_key, "r"); |
| if (!bio) |
| return -1; |
| pkey = PEM_read_bio_PrivateKey(bio, NULL, tls_passwd_cb, |
| (void *) private_key_passwd); |
| BIO_free(bio); |
| if (!pkey) |
| return -1; |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s (PEM) --> loaded", __func__); |
| /* Clear errors from the previous failed load. */ |
| ERR_clear_error(); |
| } |
| |
| if (ssl) |
| ret = SSL_use_PrivateKey(ssl, pkey); |
| else |
| ret = SSL_CTX_use_PrivateKey(data->ssl, pkey); |
| |
| EVP_PKEY_free(pkey); |
| return ret == 1 ? 0 : -1; |
| #else /* OPENSSL_NO_STDIO */ |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO", __func__); |
| return -1; |
| #endif /* OPENSSL_NO_STDIO */ |
| } |
| |
| |
| static int tls_connection_private_key(struct tls_data *data, |
| struct tls_connection *conn, |
| const char *private_key, |
| const char *private_key_passwd, |
| const u8 *private_key_blob, |
| size_t private_key_blob_len) |
| { |
| BIO *bio; |
| int ok; |
| |
| if (private_key == NULL && private_key_blob == NULL) |
| return 0; |
| |
| ok = 0; |
| while (private_key_blob) { |
| if (SSL_use_PrivateKey_ASN1(EVP_PKEY_RSA, conn->ssl, |
| (u8 *) private_key_blob, |
| private_key_blob_len) == 1) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_PrivateKey_" |
| "ASN1(EVP_PKEY_RSA) --> OK"); |
| ok = 1; |
| break; |
| } |
| |
| if (SSL_use_PrivateKey_ASN1(EVP_PKEY_DSA, conn->ssl, |
| (u8 *) private_key_blob, |
| private_key_blob_len) == 1) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_PrivateKey_" |
| "ASN1(EVP_PKEY_DSA) --> OK"); |
| ok = 1; |
| break; |
| } |
| |
| #ifndef OPENSSL_NO_EC |
| if (SSL_use_PrivateKey_ASN1(EVP_PKEY_EC, conn->ssl, |
| (u8 *) private_key_blob, |
| private_key_blob_len) == 1) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: SSL_use_PrivateKey_ASN1(EVP_PKEY_EC) --> OK"); |
| ok = 1; |
| break; |
| } |
| #endif /* OPENSSL_NO_EC */ |
| |
| #if OPENSSL_VERSION_NUMBER < 0x30000000L |
| if (SSL_use_RSAPrivateKey_ASN1(conn->ssl, |
| (u8 *) private_key_blob, |
| private_key_blob_len) == 1) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: " |
| "SSL_use_RSAPrivateKey_ASN1 --> OK"); |
| ok = 1; |
| break; |
| } |
| #endif |
| |
| bio = BIO_new_mem_buf((u8 *) private_key_blob, |
| private_key_blob_len); |
| if (bio) { |
| EVP_PKEY *pkey; |
| |
| pkey = PEM_read_bio_PrivateKey( |
| bio, NULL, tls_passwd_cb, |
| (void *) private_key_passwd); |
| if (pkey) { |
| if (SSL_use_PrivateKey(conn->ssl, pkey) == 1) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: SSL_use_PrivateKey --> OK"); |
| ok = 1; |
| EVP_PKEY_free(pkey); |
| BIO_free(bio); |
| break; |
| } |
| EVP_PKEY_free(pkey); |
| } |
| BIO_free(bio); |
| } |
| |
| if (tls_read_pkcs12_blob(data, conn->ssl, private_key_blob, |
| private_key_blob_len, |
| private_key_passwd) == 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: PKCS#12 as blob --> " |
| "OK"); |
| ok = 1; |
| break; |
| } |
| |
| break; |
| } |
| |
| while (!ok && private_key) { |
| if (tls_use_private_key_file(data, conn->ssl, private_key, |
| private_key_passwd) == 0) { |
| ok = 1; |
| break; |
| } |
| |
| if (tls_read_pkcs12(data, conn->ssl, private_key, |
| private_key_passwd) == 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Reading PKCS#12 file " |
| "--> OK"); |
| ok = 1; |
| break; |
| } |
| |
| if (tls_cryptoapi_cert(conn->ssl, private_key) == 0) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Using CryptoAPI to " |
| "access certificate store --> OK"); |
| ok = 1; |
| break; |
| } |
| |
| break; |
| } |
| |
| if (!ok) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to load private key"); |
| return -1; |
| } |
| ERR_clear_error(); |
| |
| if (!SSL_check_private_key(conn->ssl)) { |
| tls_show_errors(MSG_INFO, __func__, "Private key failed " |
| "verification"); |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "SSL: Private key loaded successfully"); |
| return 0; |
| } |
| |
| |
| static int tls_global_private_key(struct tls_data *data, |
| const char *private_key, |
| const char *private_key_passwd) |
| { |
| SSL_CTX *ssl_ctx = data->ssl; |
| |
| if (private_key == NULL) |
| return 0; |
| |
| if (tls_use_private_key_file(data, NULL, private_key, |
| private_key_passwd) && |
| tls_read_pkcs12(data, NULL, private_key, private_key_passwd)) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to load private key"); |
| ERR_clear_error(); |
| return -1; |
| } |
| ERR_clear_error(); |
| |
| if (!SSL_CTX_check_private_key(ssl_ctx)) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Private key failed verification"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| #ifndef OPENSSL_NO_DH |
| #ifndef OPENSSL_NO_DSA |
| /* This is needed to replace the deprecated DSA_dup_DH() function */ |
| static EVP_PKEY * openssl_dsa_to_dh(EVP_PKEY *dsa) |
| { |
| OSSL_PARAM_BLD *bld = NULL; |
| OSSL_PARAM *params = NULL; |
| BIGNUM *p = NULL, *q = NULL, *g = NULL; |
| EVP_PKEY_CTX *ctx = NULL; |
| EVP_PKEY *pkey = NULL; |
| |
| if (!EVP_PKEY_get_bn_param(dsa, OSSL_PKEY_PARAM_FFC_P, &p) || |
| !EVP_PKEY_get_bn_param(dsa, OSSL_PKEY_PARAM_FFC_Q, &q) || |
| !EVP_PKEY_get_bn_param(dsa, OSSL_PKEY_PARAM_FFC_G, &g) || |
| !(bld = OSSL_PARAM_BLD_new()) || |
| !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) || |
| !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, q) || |
| !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) || |
| !(params = OSSL_PARAM_BLD_to_param(bld)) || |
| !(ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL)) || |
| EVP_PKEY_fromdata_init(ctx) != 1 || |
| EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEY_PARAMETERS, |
| params) != 1) |
| wpa_printf(MSG_INFO, |
| "TLS: Failed to convert DSA parameters to DH parameters"); |
| |
| EVP_PKEY_CTX_free(ctx); |
| OSSL_PARAM_free(params); |
| OSSL_PARAM_BLD_free(bld); |
| BN_free(p); |
| BN_free(q); |
| BN_free(g); |
| return pkey; |
| } |
| #endif /* !OPENSSL_NO_DSA */ |
| #endif /* OPENSSL_NO_DH */ |
| #endif /* OpenSSL version >= 3.0 */ |
| |
| static int tls_global_dh(struct tls_data *data, const char *dh_file) |
| { |
| #ifdef OPENSSL_NO_DH |
| if (dh_file == NULL) |
| return 0; |
| wpa_printf(MSG_ERROR, "TLS: openssl does not include DH support, but " |
| "dh_file specified"); |
| return -1; |
| #else /* OPENSSL_NO_DH */ |
| #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
| SSL_CTX *ssl_ctx = data->ssl; |
| BIO *bio; |
| OSSL_DECODER_CTX *ctx = NULL; |
| EVP_PKEY *pkey = NULL, *tmpkey = NULL; |
| bool dsa = false; |
| |
| if (!ssl_ctx) |
| return -1; |
| if (!dh_file) { |
| SSL_CTX_set_dh_auto(ssl_ctx, 1); |
| return 0; |
| } |
| |
| bio = BIO_new_file(dh_file, "r"); |
| if (!bio) { |
| wpa_printf(MSG_INFO, "TLS: Failed to open DH file '%s': %s", |
| dh_file, ERR_error_string(ERR_get_error(), NULL)); |
| return -1; |
| } |
| ctx = OSSL_DECODER_CTX_new_for_pkey( |
| &tmpkey, "PEM", NULL, NULL, |
| OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL); |
| if (!ctx || |
| OSSL_DECODER_from_bio(ctx, bio) != 1) { |
| wpa_printf(MSG_INFO, |
| "TLS: Failed to decode domain parameters from '%s': %s", |
| dh_file, ERR_error_string(ERR_get_error(), NULL)); |
| BIO_free(bio); |
| OSSL_DECODER_CTX_free(ctx); |
| return -1; |
| } |
| OSSL_DECODER_CTX_free(ctx); |
| BIO_free(bio); |
| |
| if (!tmpkey) { |
| wpa_printf(MSG_INFO, "TLS: Failed to load domain parameters"); |
| return -1; |
| } |
| |
| #ifndef OPENSSL_NO_DSA |
| if (EVP_PKEY_is_a(tmpkey, "DSA")) { |
| pkey = openssl_dsa_to_dh(tmpkey); |
| EVP_PKEY_free(tmpkey); |
| if (!pkey) |
| return -1; |
| dsa = true; |
| } |
| #endif /* !OPENSSL_NO_DSA */ |
| if (!dsa) { |
| if (EVP_PKEY_is_a(tmpkey, "DH") || |
| EVP_PKEY_is_a(tmpkey, "DHX")) { |
| } else { |
| wpa_printf(MSG_INFO, |
| "TLS: No DH parameters found in %s", |
| dh_file); |
| EVP_PKEY_free(tmpkey); |
| return -1; |
| } |
| pkey = tmpkey; |
| tmpkey = NULL; |
| } |
| |
| if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, pkey) != 1) { |
| wpa_printf(MSG_INFO, |
| "TLS: Failed to set DH params from '%s': %s", |
| dh_file, ERR_error_string(ERR_get_error(), NULL)); |
| EVP_PKEY_free(pkey); |
| return -1; |
| } |
| return 0; |
| #else /* OpenSSL version >= 3.0 */ |
| SSL_CTX *ssl_ctx = data->ssl; |
| DH *dh; |
| BIO *bio; |
| |
| if (!ssl_ctx) |
| return -1; |
| if (!dh_file) { |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) |
| SSL_CTX_set_dh_auto(ssl_ctx, 1); |
| #endif |
| return 0; |
| } |
| |
| bio = BIO_new_file(dh_file, "r"); |
| if (bio == NULL) { |
| wpa_printf(MSG_INFO, "TLS: Failed to open DH file '%s': %s", |
| dh_file, ERR_error_string(ERR_get_error(), NULL)); |
| return -1; |
| } |
| dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); |
| BIO_free(bio); |
| #ifndef OPENSSL_NO_DSA |
| while (dh == NULL) { |
| DSA *dsa; |
| wpa_printf(MSG_DEBUG, "TLS: Failed to parse DH file '%s': %s -" |
| " trying to parse as DSA params", dh_file, |
| ERR_error_string(ERR_get_error(), NULL)); |
| bio = BIO_new_file(dh_file, "r"); |
| if (bio == NULL) |
| break; |
| dsa = PEM_read_bio_DSAparams(bio, NULL, NULL, NULL); |
| BIO_free(bio); |
| if (!dsa) { |
| wpa_printf(MSG_DEBUG, "TLS: Failed to parse DSA file " |
| "'%s': %s", dh_file, |
| ERR_error_string(ERR_get_error(), NULL)); |
| break; |
| } |
| |
| wpa_printf(MSG_DEBUG, "TLS: DH file in DSA param format"); |
| dh = DSA_dup_DH(dsa); |
| DSA_free(dsa); |
| if (dh == NULL) { |
| wpa_printf(MSG_INFO, "TLS: Failed to convert DSA " |
| "params into DH params"); |
| break; |
| } |
| break; |
| } |
| #endif /* !OPENSSL_NO_DSA */ |
| if (dh == NULL) { |
| wpa_printf(MSG_INFO, "TLS: Failed to read/parse DH/DSA file " |
| "'%s'", dh_file); |
| return -1; |
| } |
| |
| if (SSL_CTX_set_tmp_dh(ssl_ctx, dh) != 1) { |
| wpa_printf(MSG_INFO, "TLS: Failed to set DH params from '%s': " |
| "%s", dh_file, |
| ERR_error_string(ERR_get_error(), NULL)); |
| DH_free(dh); |
| return -1; |
| } |
| DH_free(dh); |
| return 0; |
| #endif /* OpenSSL version >= 3.0 */ |
| #endif /* OPENSSL_NO_DH */ |
| } |
| |
| |
| int tls_connection_get_random(void *ssl_ctx, struct tls_connection *conn, |
| struct tls_random *keys) |
| { |
| SSL *ssl; |
| |
| if (conn == NULL || keys == NULL) |
| return -1; |
| ssl = conn->ssl; |
| if (ssl == NULL) |
| return -1; |
| |
| os_memset(keys, 0, sizeof(*keys)); |
| keys->client_random = conn->client_random; |
| keys->client_random_len = SSL_get_client_random( |
| ssl, conn->client_random, sizeof(conn->client_random)); |
| keys->server_random = conn->server_random; |
| keys->server_random_len = SSL_get_server_random( |
| ssl, conn->server_random, sizeof(conn->server_random)); |
| |
| return 0; |
| } |
| |
| |
| #ifdef OPENSSL_NEED_EAP_FAST_PRF |
| static int openssl_get_keyblock_size(SSL *ssl) |
| { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| const EVP_CIPHER *c; |
| const EVP_MD *h; |
| int md_size; |
| |
| if (ssl->enc_read_ctx == NULL || ssl->enc_read_ctx->cipher == NULL || |
| ssl->read_hash == NULL) |
| return -1; |
| |
| c = ssl->enc_read_ctx->cipher; |
| h = EVP_MD_CTX_md(ssl->read_hash); |
| if (h) |
| md_size = EVP_MD_size(h); |
| else if (ssl->s3) |
| md_size = ssl->s3->tmp.new_mac_secret_size; |
| else |
| return -1; |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: keyblock size: key_len=%d MD_size=%d " |
| "IV_len=%d", EVP_CIPHER_key_length(c), md_size, |
| EVP_CIPHER_iv_length(c)); |
| return 2 * (EVP_CIPHER_key_length(c) + |
| md_size + |
| EVP_CIPHER_iv_length(c)); |
| #else |
| const SSL_CIPHER *ssl_cipher; |
| int cipher, digest; |
| const EVP_CIPHER *c; |
| const EVP_MD *h; |
| int mac_key_len, enc_key_len, fixed_iv_len; |
| |
| ssl_cipher = SSL_get_current_cipher(ssl); |
| if (!ssl_cipher) |
| return -1; |
| cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher); |
| digest = SSL_CIPHER_get_digest_nid(ssl_cipher); |
| wpa_printf(MSG_DEBUG, "OpenSSL: cipher nid %d digest nid %d", |
| cipher, digest); |
| if (cipher < 0 || digest < 0) |
| return -1; |
| if (cipher == NID_undef) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: no cipher in use?!"); |
| return -1; |
| } |
| c = EVP_get_cipherbynid(cipher); |
| if (!c) |
| return -1; |
| enc_key_len = EVP_CIPHER_key_length(c); |
| if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE || |
| EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE) |
| fixed_iv_len = 4; /* only part of IV from PRF */ |
| else |
| fixed_iv_len = EVP_CIPHER_iv_length(c); |
| if (digest == NID_undef) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: no digest in use (e.g., AEAD)"); |
| mac_key_len = 0; |
| } else { |
| h = EVP_get_digestbynid(digest); |
| if (!h) |
| return -1; |
| mac_key_len = EVP_MD_size(h); |
| } |
| |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d", |
| mac_key_len, enc_key_len, fixed_iv_len); |
| return 2 * (mac_key_len + enc_key_len + fixed_iv_len); |
| #endif |
| } |
| #endif /* OPENSSL_NEED_EAP_FAST_PRF */ |
| |
| |
| int tls_connection_export_key(void *tls_ctx, struct tls_connection *conn, |
| const char *label, const u8 *context, |
| size_t context_len, u8 *out, size_t out_len) |
| { |
| if (!conn || |
| SSL_export_keying_material(conn->ssl, out, out_len, label, |
| os_strlen(label), context, context_len, |
| context != NULL) != 1) |
| return -1; |
| return 0; |
| } |
| |
| |
| int tls_connection_get_eap_fast_key(void *tls_ctx, struct tls_connection *conn, |
| u8 *out, size_t out_len) |
| { |
| #ifdef OPENSSL_NEED_EAP_FAST_PRF |
| SSL *ssl; |
| SSL_SESSION *sess; |
| u8 *rnd; |
| int ret = -1; |
| int skip = 0; |
| u8 *tmp_out = NULL; |
| u8 *_out = out; |
| unsigned char client_random[SSL3_RANDOM_SIZE]; |
| unsigned char server_random[SSL3_RANDOM_SIZE]; |
| unsigned char master_key[64]; |
| size_t master_key_len; |
| const char *ver; |
| |
| /* |
| * TLS library did not support EAP-FAST key generation, so get the |
| * needed TLS session parameters and use an internal implementation of |
| * TLS PRF to derive the key. |
| */ |
| |
| if (conn == NULL) |
| return -1; |
| ssl = conn->ssl; |
| if (ssl == NULL) |
| return -1; |
| ver = SSL_get_version(ssl); |
| sess = SSL_get_session(ssl); |
| if (!ver || !sess) |
| return -1; |
| |
| skip = openssl_get_keyblock_size(ssl); |
| if (skip < 0) |
| return -1; |
| tmp_out = os_malloc(skip + out_len); |
| if (!tmp_out) |
| return -1; |
| _out = tmp_out; |
| |
| rnd = os_malloc(2 * SSL3_RANDOM_SIZE); |
| if (!rnd) { |
| os_free(tmp_out); |
| return -1; |
| } |
| |
| SSL_get_client_random(ssl, client_random, sizeof(client_random)); |
| SSL_get_server_random(ssl, server_random, sizeof(server_random)); |
| master_key_len = SSL_SESSION_get_master_key(sess, master_key, |
| sizeof(master_key)); |
| |
| os_memcpy(rnd, server_random, SSL3_RANDOM_SIZE); |
| os_memcpy(rnd + SSL3_RANDOM_SIZE, client_random, SSL3_RANDOM_SIZE); |
| |
| if (os_strcmp(ver, "TLSv1.2") == 0) { |
| tls_prf_sha256(master_key, master_key_len, |
| "key expansion", rnd, 2 * SSL3_RANDOM_SIZE, |
| _out, skip + out_len); |
| ret = 0; |
| } else if (tls_prf_sha1_md5(master_key, master_key_len, |
| "key expansion", rnd, 2 * SSL3_RANDOM_SIZE, |
| _out, skip + out_len) == 0) { |
| ret = 0; |
| } |
| forced_memzero(master_key, sizeof(master_key)); |
| os_free(rnd); |
| if (ret == 0) |
| os_memcpy(out, _out + skip, out_len); |
| bin_clear_free(tmp_out, skip); |
| |
| return ret; |
| #else /* OPENSSL_NEED_EAP_FAST_PRF */ |
| wpa_printf(MSG_ERROR, |
| "OpenSSL: EAP-FAST keys cannot be exported in FIPS mode"); |
| return -1; |
| #endif /* OPENSSL_NEED_EAP_FAST_PRF */ |
| } |
| |
| |
| static struct wpabuf * |
| openssl_handshake(struct tls_connection *conn, const struct wpabuf *in_data) |
| { |
| struct tls_context *context = conn->context; |
| int res; |
| struct wpabuf *out_data; |
| |
| /* |
| * Give TLS handshake data from the server (if available) to OpenSSL |
| * for processing. |
| */ |
| if (in_data && wpabuf_len(in_data) > 0 && |
| BIO_write(conn->ssl_in, wpabuf_head(in_data), wpabuf_len(in_data)) |
| < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Handshake failed - BIO_write"); |
| return NULL; |
| } |
| |
| /* Initiate TLS handshake or continue the existing handshake */ |
| if (conn->server) |
| res = SSL_accept(conn->ssl); |
| else |
| res = SSL_connect(conn->ssl); |
| if (res != 1) { |
| int err = SSL_get_error(conn->ssl, res); |
| if (err == SSL_ERROR_WANT_READ) |
| wpa_printf(MSG_DEBUG, "SSL: SSL_connect - want " |
| "more data"); |
| else if (err == SSL_ERROR_WANT_WRITE) |
| wpa_printf(MSG_DEBUG, "SSL: SSL_connect - want to " |
| "write"); |
| else { |
| unsigned long error = ERR_peek_last_error(); |
| |
| tls_show_errors(MSG_INFO, __func__, "SSL_connect"); |
| |
| if (context->event_cb && |
| ERR_GET_LIB(error) == ERR_LIB_SSL && |
| ERR_GET_REASON(error) == |
| SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED) { |
| context->event_cb( |
| context->cb_ctx, |
| TLS_UNSAFE_RENEGOTIATION_DISABLED, |
| NULL); |
| } |
| conn->failed++; |
| if (!conn->server && !conn->client_hello_generated) { |
| /* The server would not understand TLS Alert |
| * before ClientHello, so simply terminate |
| * handshake on this type of error case caused |
| * by a likely internal error like no ciphers |
| * available. */ |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Could not generate ClientHello"); |
| conn->write_alerts++; |
| return NULL; |
| } |
| } |
| } |
| |
| if (!conn->server && !conn->failed) |
| conn->client_hello_generated = 1; |
| |
| #ifdef CONFIG_SUITEB |
| if ((conn->flags & TLS_CONN_SUITEB) && !conn->server && |
| os_strncmp(SSL_get_cipher(conn->ssl), "DHE-", 4) == 0 && |
| conn->server_dh_prime_len < 3072) { |
| /* |
| * This should not be reached since earlier cert_cb should have |
| * terminated the handshake. Keep this check here for extra |
| * protection if anything goes wrong with the more low-level |
| * checks based on having to parse the TLS handshake messages. |
| */ |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Server DH prime length: %d bits", |
| conn->server_dh_prime_len); |
| |
| if (context->event_cb) { |
| union tls_event_data ev; |
| |
| os_memset(&ev, 0, sizeof(ev)); |
| ev.alert.is_local = 1; |
| ev.alert.type = "fatal"; |
| ev.alert.description = "insufficient security"; |
| context->event_cb(context->cb_ctx, TLS_ALERT, &ev); |
| } |
| /* |
| * Could send a TLS Alert to the server, but for now, simply |
| * terminate handshake. |
| */ |
| conn->failed++; |
| conn->write_alerts++; |
| return NULL; |
| } |
| #endif /* CONFIG_SUITEB */ |
| |
| /* Get the TLS handshake data to be sent to the server */ |
| res = BIO_ctrl_pending(conn->ssl_out); |
| wpa_printf(MSG_DEBUG, "SSL: %d bytes pending from ssl_out", res); |
| out_data = wpabuf_alloc(res); |
| if (out_data == NULL) { |
| wpa_printf(MSG_DEBUG, "SSL: Failed to allocate memory for " |
| "handshake output (%d bytes)", res); |
| if (BIO_reset(conn->ssl_out) < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "BIO_reset failed"); |
| } |
| return NULL; |
| } |
| res = res == 0 ? 0 : BIO_read(conn->ssl_out, wpabuf_mhead(out_data), |
| res); |
| if (res < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Handshake failed - BIO_read"); |
| if (BIO_reset(conn->ssl_out) < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "BIO_reset failed"); |
| } |
| wpabuf_free(out_data); |
| return NULL; |
| } |
| wpabuf_put(out_data, res); |
| |
| return out_data; |
| } |
| |
| |
| static struct wpabuf * |
| openssl_get_appl_data(struct tls_connection *conn, size_t max_len) |
| { |
| struct wpabuf *appl_data; |
| int res; |
| |
| appl_data = wpabuf_alloc(max_len + 100); |
| if (appl_data == NULL) |
| return NULL; |
| |
| res = SSL_read(conn->ssl, wpabuf_mhead(appl_data), |
| wpabuf_size(appl_data)); |
| if (res < 0) { |
| int err = SSL_get_error(conn->ssl, res); |
| if (err == SSL_ERROR_WANT_READ || |
| err == SSL_ERROR_WANT_WRITE) { |
| wpa_printf(MSG_DEBUG, "SSL: No Application Data " |
| "included"); |
| } else { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to read possible " |
| "Application Data"); |
| } |
| wpabuf_free(appl_data); |
| return NULL; |
| } |
| |
| wpabuf_put(appl_data, res); |
| wpa_hexdump_buf_key(MSG_MSGDUMP, "SSL: Application Data in Finished " |
| "message", appl_data); |
| |
| return appl_data; |
| } |
| |
| |
| static struct wpabuf * |
| openssl_connection_handshake(struct tls_connection *conn, |
| const struct wpabuf *in_data, |
| struct wpabuf **appl_data) |
| { |
| struct wpabuf *out_data; |
| |
| if (appl_data) |
| *appl_data = NULL; |
| |
| out_data = openssl_handshake(conn, in_data); |
| if (out_data == NULL) |
| return NULL; |
| if (conn->invalid_hb_used) { |
| wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); |
| wpabuf_free(out_data); |
| return NULL; |
| } |
| |
| if (SSL_is_init_finished(conn->ssl)) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Handshake finished - resumed=%d", |
| tls_connection_resumed(conn->ssl_ctx, conn)); |
| if (conn->server) { |
| char *buf; |
| size_t buflen = 2000; |
| |
| buf = os_malloc(buflen); |
| if (buf) { |
| if (SSL_get_shared_ciphers(conn->ssl, buf, |
| buflen)) { |
| buf[buflen - 1] = '\0'; |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Shared ciphers: %s", |
| buf); |
| } |
| os_free(buf); |
| } |
| } |
| if (appl_data && in_data) |
| *appl_data = openssl_get_appl_data(conn, |
| wpabuf_len(in_data)); |
| } |
| |
| if (conn->invalid_hb_used) { |
| wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); |
| if (appl_data) { |
| wpabuf_free(*appl_data); |
| *appl_data = NULL; |
| } |
| wpabuf_free(out_data); |
| return NULL; |
| } |
| |
| return out_data; |
| } |
| |
| |
| struct wpabuf * |
| tls_connection_handshake(void *ssl_ctx, struct tls_connection *conn, |
| const struct wpabuf *in_data, |
| struct wpabuf **appl_data) |
| { |
| return openssl_connection_handshake(conn, in_data, appl_data); |
| } |
| |
| |
| struct wpabuf * tls_connection_server_handshake(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data, |
| struct wpabuf **appl_data) |
| { |
| conn->server = 1; |
| return openssl_connection_handshake(conn, in_data, appl_data); |
| } |
| |
| |
| struct wpabuf * tls_connection_encrypt(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data) |
| { |
| int res; |
| struct wpabuf *buf; |
| |
| if (conn == NULL) |
| return NULL; |
| |
| /* Give plaintext data for OpenSSL to encrypt into the TLS tunnel. */ |
| if ((res = BIO_reset(conn->ssl_in)) < 0 || |
| (res = BIO_reset(conn->ssl_out)) < 0) { |
| tls_show_errors(MSG_INFO, __func__, "BIO_reset failed"); |
| return NULL; |
| } |
| res = SSL_write(conn->ssl, wpabuf_head(in_data), wpabuf_len(in_data)); |
| if (res < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Encryption failed - SSL_write"); |
| return NULL; |
| } |
| |
| /* Read encrypted data to be sent to the server */ |
| buf = wpabuf_alloc(wpabuf_len(in_data) + 300); |
| if (buf == NULL) |
| return NULL; |
| res = BIO_read(conn->ssl_out, wpabuf_mhead(buf), wpabuf_size(buf)); |
| if (res < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Encryption failed - BIO_read"); |
| wpabuf_free(buf); |
| return NULL; |
| } |
| wpabuf_put(buf, res); |
| |
| return buf; |
| } |
| |
| |
| struct wpabuf * tls_connection_decrypt(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data) |
| { |
| int res; |
| struct wpabuf *buf; |
| |
| /* Give encrypted data from TLS tunnel for OpenSSL to decrypt. */ |
| res = BIO_write(conn->ssl_in, wpabuf_head(in_data), |
| wpabuf_len(in_data)); |
| if (res < 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Decryption failed - BIO_write"); |
| return NULL; |
| } |
| if (BIO_reset(conn->ssl_out) < 0) { |
| tls_show_errors(MSG_INFO, __func__, "BIO_reset failed"); |
| return NULL; |
| } |
| |
| /* Read decrypted data for further processing */ |
| /* |
| * Even though we try to disable TLS compression, it is possible that |
| * this cannot be done with all TLS libraries. Add extra buffer space |
| * to handle the possibility of the decrypted data being longer than |
| * input data. |
| */ |
| buf = wpabuf_alloc((wpabuf_len(in_data) + 500) * 3); |
| if (buf == NULL) |
| return NULL; |
| res = SSL_read(conn->ssl, wpabuf_mhead(buf), wpabuf_size(buf)); |
| if (res < 0) { |
| int err = SSL_get_error(conn->ssl, res); |
| |
| if (err == SSL_ERROR_WANT_READ) { |
| wpa_printf(MSG_DEBUG, |
| "SSL: SSL_connect - want more data"); |
| res = 0; |
| } else { |
| tls_show_errors(MSG_INFO, __func__, |
| "Decryption failed - SSL_read"); |
| wpabuf_free(buf); |
| return NULL; |
| } |
| } |
| wpabuf_put(buf, res); |
| |
| if (conn->invalid_hb_used) { |
| wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); |
| wpabuf_free(buf); |
| return NULL; |
| } |
| |
| return buf; |
| } |
| |
| |
| int tls_connection_resumed(void *ssl_ctx, struct tls_connection *conn) |
| { |
| return conn ? SSL_session_reused(conn->ssl) : 0; |
| } |
| |
| |
| int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, |
| u8 *ciphers) |
| { |
| char buf[500], *pos, *end; |
| u8 *c; |
| int ret; |
| |
| if (conn == NULL || conn->ssl == NULL || ciphers == NULL) |
| return -1; |
| |
| buf[0] = '\0'; |
| pos = buf; |
| end = pos + sizeof(buf); |
| |
| c = ciphers; |
| while (*c != TLS_CIPHER_NONE) { |
| const char *suite; |
| |
| switch (*c) { |
| case TLS_CIPHER_RC4_SHA: |
| suite = "RC4-SHA"; |
| break; |
| case TLS_CIPHER_AES128_SHA: |
| suite = "AES128-SHA"; |
| break; |
| case TLS_CIPHER_RSA_DHE_AES128_SHA: |
| suite = "DHE-RSA-AES128-SHA"; |
| break; |
| case TLS_CIPHER_ANON_DH_AES128_SHA: |
| suite = "ADH-AES128-SHA"; |
| break; |
| case TLS_CIPHER_RSA_DHE_AES256_SHA: |
| suite = "DHE-RSA-AES256-SHA"; |
| break; |
| case TLS_CIPHER_AES256_SHA: |
| suite = "AES256-SHA"; |
| break; |
| default: |
| wpa_printf(MSG_DEBUG, "TLS: Unsupported " |
| "cipher selection: %d", *c); |
| return -1; |
| } |
| ret = os_snprintf(pos, end - pos, ":%s", suite); |
| if (os_snprintf_error(end - pos, ret)) |
| break; |
| pos += ret; |
| |
| c++; |
| } |
| if (!buf[0]) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: No ciphers listed"); |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: cipher suites: %s", buf + 1); |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) |
| #ifdef EAP_FAST_OR_TEAP |
| if (os_strstr(buf, ":ADH-")) { |
| /* |
| * Need to drop to security level 0 to allow anonymous |
| * cipher suites for EAP-FAST. |
| */ |
| SSL_set_security_level(conn->ssl, 0); |
| } else if (SSL_get_security_level(conn->ssl) == 0) { |
| /* Force at least security level 1 */ |
| SSL_set_security_level(conn->ssl, 1); |
| } |
| #endif /* EAP_FAST_OR_TEAP */ |
| #endif |
| |
| if (SSL_set_cipher_list(conn->ssl, buf + 1) != 1) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Cipher suite configuration failed"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| int tls_get_version(void *ssl_ctx, struct tls_connection *conn, |
| char *buf, size_t buflen) |
| { |
| const char *name; |
| if (conn == NULL || conn->ssl == NULL) |
| return -1; |
| |
| name = SSL_get_version(conn->ssl); |
| if (name == NULL) |
| return -1; |
| |
| os_strlcpy(buf, name, buflen); |
| return 0; |
| } |
| |
| |
| int tls_get_cipher(void *ssl_ctx, struct tls_connection *conn, |
| char *buf, size_t buflen) |
| { |
| const char *name; |
| if (conn == NULL || conn->ssl == NULL) |
| return -1; |
| |
| name = SSL_get_cipher(conn->ssl); |
| if (name == NULL) |
| return -1; |
| |
| os_strlcpy(buf, name, buflen); |
| return 0; |
| } |
| |
| |
| int tls_connection_enable_workaround(void *ssl_ctx, |
| struct tls_connection *conn) |
| { |
| SSL_set_options(conn->ssl, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); |
| |
| return 0; |
| } |
| |
| |
| #ifdef EAP_FAST_OR_TEAP |
| /* ClientHello TLS extensions require a patch to openssl, so this function is |
| * commented out unless explicitly needed for EAP-FAST in order to be able to |
| * build this file with unmodified openssl. */ |
| int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn, |
| int ext_type, const u8 *data, |
| size_t data_len) |
| { |
| if (conn == NULL || conn->ssl == NULL || ext_type != 35) |
| return -1; |
| |
| if (SSL_set_session_ticket_ext(conn->ssl, (void *) data, |
| data_len) != 1) |
| return -1; |
| |
| return 0; |
| } |
| #endif /* EAP_FAST_OR_TEAP */ |
| |
| |
| int tls_connection_get_failed(void *ssl_ctx, struct tls_connection *conn) |
| { |
| if (conn == NULL) |
| return -1; |
| return conn->failed; |
| } |
| |
| |
| int tls_connection_get_read_alerts(void *ssl_ctx, struct tls_connection *conn) |
| { |
| if (conn == NULL) |
| return -1; |
| return conn->read_alerts; |
| } |
| |
| |
| int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn) |
| { |
| if (conn == NULL) |
| return -1; |
| return conn->write_alerts; |
| } |
| |
| |
| #ifdef HAVE_OCSP |
| |
| static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) |
| { |
| #ifndef CONFIG_NO_STDOUT_DEBUG |
| BIO *out; |
| size_t rlen; |
| char *txt; |
| int res; |
| |
| if (wpa_debug_level > MSG_DEBUG) |
| return; |
| |
| out = BIO_new(BIO_s_mem()); |
| if (!out) |
| return; |
| |
| OCSP_RESPONSE_print(out, rsp, 0); |
| rlen = BIO_ctrl_pending(out); |
| txt = os_malloc(rlen + 1); |
| if (!txt) { |
| BIO_free(out); |
| return; |
| } |
| |
| res = BIO_read(out, txt, rlen); |
| if (res > 0) { |
| txt[res] = '\0'; |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP Response\n%s", txt); |
| } |
| os_free(txt); |
| BIO_free(out); |
| #endif /* CONFIG_NO_STDOUT_DEBUG */ |
| } |
| |
| |
| static int ocsp_resp_cb(SSL *s, void *arg) |
| { |
| struct tls_connection *conn = arg; |
| const unsigned char *p; |
| int len, status, reason, res; |
| OCSP_RESPONSE *rsp; |
| OCSP_BASICRESP *basic; |
| OCSP_CERTID *id; |
| ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update; |
| X509_STORE *store; |
| STACK_OF(X509) *certs = NULL; |
| |
| len = SSL_get_tlsext_status_ocsp_resp(s, &p); |
| if (!p) { |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L |
| #if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x30400000L |
| if (SSL_version(s) == TLS1_3_VERSION && SSL_session_reused(s)) { |
| /* TLS 1.3 sends the OCSP response with the server |
| * Certificate message. Since that Certificate message |
| * is not sent when resuming a session, there can be no |
| * new OCSP response. Allow this since the OCSP response |
| * was validated when checking the initial certificate |
| * exchange. */ |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Allow no OCSP response when using TLS 1.3 and a resumed session"); |
| return 1; |
| } |
| #endif |
| #endif |
| wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); |
| return (conn->flags & TLS_CONN_REQUIRE_OCSP) ? 0 : 1; |
| } |
| |
| wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len); |
| |
| rsp = d2i_OCSP_RESPONSE(NULL, &p, len); |
| if (!rsp) { |
| wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response"); |
| return 0; |
| } |
| |
| ocsp_debug_print_resp(rsp); |
| |
| status = OCSP_response_status(rsp); |
| if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { |
| wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)", |
| status, OCSP_response_status_str(status)); |
| return 0; |
| } |
| |
| basic = OCSP_response_get1_basic(rsp); |
| if (!basic) { |
| wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse"); |
| return 0; |
| } |
| |
| store = SSL_CTX_get_cert_store(conn->ssl_ctx); |
| if (conn->peer_issuer) { |
| debug_print_cert(conn->peer_issuer, "Add OCSP issuer"); |
| |
| if (X509_STORE_add_cert(store, conn->peer_issuer) != 1) { |
| tls_show_errors(MSG_INFO, __func__, |
| "OpenSSL: Could not add issuer to certificate store"); |
| } |
| certs = sk_X509_new_null(); |
| if (certs) { |
| X509 *cert; |
| cert = X509_dup(conn->peer_issuer); |
| if (cert && !sk_X509_push(certs, cert)) { |
| tls_show_errors( |
| MSG_INFO, __func__, |
| "OpenSSL: Could not add issuer to OCSP responder trust store"); |
| X509_free(cert); |
| sk_X509_free(certs); |
| certs = NULL; |
| } |
| if (certs && conn->peer_issuer_issuer) { |
| cert = X509_dup(conn->peer_issuer_issuer); |
| if (cert && !sk_X509_push(certs, cert)) { |
| tls_show_errors( |
| MSG_INFO, __func__, |
| "OpenSSL: Could not add issuer's issuer to OCSP responder trust store"); |
| X509_free(cert); |
| } |
| } |
| } |
| } |
| |
| status = OCSP_basic_verify(basic, certs, store, OCSP_TRUSTOTHER); |
| sk_X509_pop_free(certs, X509_free); |
| if (status <= 0) { |
| tls_show_errors(MSG_INFO, __func__, |
| "OpenSSL: OCSP response failed verification"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded"); |
| |
| if (!conn->peer_cert) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate not available for OCSP status check"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| if (!conn->peer_issuer) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Peer issuer certificate not available for OCSP status check"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| id = OCSP_cert_to_id(EVP_sha256(), conn->peer_cert, conn->peer_issuer); |
| if (!id) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Could not create OCSP certificate identifier (SHA256)"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| res = OCSP_resp_find_status(basic, id, &status, &reason, &produced_at, |
| &this_update, &next_update); |
| if (!res) { |
| OCSP_CERTID_free(id); |
| id = OCSP_cert_to_id(NULL, conn->peer_cert, conn->peer_issuer); |
| if (!id) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Could not create OCSP certificate identifier (SHA1)"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| res = OCSP_resp_find_status(basic, id, &status, &reason, |
| &produced_at, &this_update, |
| &next_update); |
| } |
| |
| if (!res) { |
| wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s", |
| (conn->flags & TLS_CONN_REQUIRE_OCSP) ? "" : |
| " (OCSP not required)"); |
| OCSP_CERTID_free(id); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return (conn->flags & TLS_CONN_REQUIRE_OCSP) ? 0 : 1; |
| } |
| OCSP_CERTID_free(id); |
| |
| if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) { |
| tls_show_errors(MSG_INFO, __func__, |
| "OpenSSL: OCSP status times invalid"); |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| return 0; |
| } |
| |
| OCSP_BASICRESP_free(basic); |
| OCSP_RESPONSE_free(rsp); |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s", |
| OCSP_cert_status_str(status)); |
| |
| if (status == V_OCSP_CERTSTATUS_GOOD) |
| return 1; |
| if (status == V_OCSP_CERTSTATUS_REVOKED) |
| return 0; |
| if (conn->flags & TLS_CONN_REQUIRE_OCSP) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required"); |
| return 0; |
| } |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue"); |
| return 1; |
| } |
| |
| |
| static int ocsp_status_cb(SSL *s, void *arg) |
| { |
| char *tmp; |
| char *resp; |
| size_t len; |
| |
| if (tls_global->ocsp_stapling_response == NULL) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - no response configured"); |
| return SSL_TLSEXT_ERR_OK; |
| } |
| |
| resp = os_readfile(tls_global->ocsp_stapling_response, &len); |
| if (resp == NULL) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - could not read response file"); |
| /* TODO: Build OCSPResponse with responseStatus = internalError |
| */ |
| return SSL_TLSEXT_ERR_OK; |
| } |
| wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - send cached response"); |
| tmp = OPENSSL_malloc(len); |
| if (tmp == NULL) { |
| os_free(resp); |
| return SSL_TLSEXT_ERR_ALERT_FATAL; |
| } |
| |
| os_memcpy(tmp, resp, len); |
| os_free(resp); |
| SSL_set_tlsext_status_ocsp_resp(s, tmp, len); |
| |
| return SSL_TLSEXT_ERR_OK; |
| } |
| |
| #endif /* HAVE_OCSP */ |
| |
| |
| static size_t max_str_len(const char **lines) |
| { |
| const char **p; |
| size_t max_len = 0; |
| |
| for (p = lines; *p; p++) { |
| size_t len = os_strlen(*p); |
| |
| if (len > max_len) |
| max_len = len; |
| } |
| |
| return max_len; |
| } |
| |
| |
| static int match_lines_in_file(const char *path, const char **lines) |
| { |
| FILE *f; |
| char *buf; |
| size_t bufsize; |
| int found = 0, is_linestart = 1; |
| |
| bufsize = max_str_len(lines) + sizeof("\r\n"); |
| buf = os_malloc(bufsize); |
| if (!buf) |
| return 0; |
| |
| f = fopen(path, "r"); |
| if (!f) { |
| os_free(buf); |
| return 0; |
| } |
| |
| while (!found && fgets(buf, bufsize, f)) { |
| int is_lineend; |
| size_t len; |
| const char **p; |
| |
| len = strcspn(buf, "\r\n"); |
| is_lineend = buf[len] != '\0'; |
| buf[len] = '\0'; |
| |
| if (is_linestart && is_lineend) { |
| for (p = lines; !found && *p; p++) |
| found = os_strcmp(buf, *p) == 0; |
| } |
| is_linestart = is_lineend; |
| } |
| |
| fclose(f); |
| bin_clear_free(buf, bufsize); |
| |
| return found; |
| } |
| |
| |
| static int is_tpm2_key(const char *path) |
| { |
| /* Check both new and old format of TPM2 PEM guard tag */ |
| static const char *tpm2_tags[] = { |
| "-----BEGIN TSS2 PRIVATE KEY-----", |
| "-----BEGIN TSS2 KEY BLOB-----", |
| NULL |
| }; |
| |
| return match_lines_in_file(path, tpm2_tags); |
| } |
| |
| |
| int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, |
| const struct tls_connection_params *params) |
| { |
| struct tls_data *data = tls_ctx; |
| int ret; |
| unsigned long err; |
| int can_pkcs11 = 0; |
| const char *key_id = params->key_id; |
| const char *cert_id = params->cert_id; |
| const char *ca_cert_id = params->ca_cert_id; |
| const char *engine_id = params->engine ? params->engine_id : NULL; |
| const char *ciphers; |
| |
| if (conn == NULL) |
| return -1; |
| |
| if (params->flags & TLS_CONN_REQUIRE_OCSP_ALL) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: ocsp=3 not supported"); |
| return -1; |
| } |
| |
| /* |
| * If the engine isn't explicitly configured, and any of the |
| * cert/key fields are actually PKCS#11 URIs, then automatically |
| * use the PKCS#11 ENGINE. |
| */ |
| if (!engine_id || os_strcmp(engine_id, "pkcs11") == 0) |
| can_pkcs11 = 1; |
| |
| if (!key_id && params->private_key && can_pkcs11 && |
| os_strncmp(params->private_key, "pkcs11:", 7) == 0) { |
| can_pkcs11 = 2; |
| key_id = params->private_key; |
| } |
| |
| if (!cert_id && params->client_cert && can_pkcs11 && |
| os_strncmp(params->client_cert, "pkcs11:", 7) == 0) { |
| can_pkcs11 = 2; |
| cert_id = params->client_cert; |
| } |
| |
| if (!ca_cert_id && params->ca_cert && can_pkcs11 && |
| os_strncmp(params->ca_cert, "pkcs11:", 7) == 0) { |
| can_pkcs11 = 2; |
| ca_cert_id = params->ca_cert; |
| } |
| |
| /* If we need to automatically enable the PKCS#11 ENGINE, do so. */ |
| if (can_pkcs11 == 2 && !engine_id) |
| engine_id = "pkcs11"; |
| |
| /* If private_key points to a TPM2-wrapped key, automatically enable |
| * tpm2 engine and use it to unwrap the key. */ |
| if (params->private_key && |
| (!engine_id || os_strcmp(engine_id, "tpm2") == 0) && |
| is_tpm2_key(params->private_key)) { |
| wpa_printf(MSG_DEBUG, "OpenSSL: Found TPM2 wrapped key %s", |
| params->private_key); |
| key_id = key_id ? key_id : params->private_key; |
| engine_id = engine_id ? engine_id : "tpm2"; |
| } |
| |
| #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) |
| if (params->flags & TLS_CONN_EAP_FAST) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Use TLSv1_method() for EAP-FAST"); |
| if (SSL_set_ssl_method(conn->ssl, TLSv1_method()) != 1) { |
| tls_show_errors(MSG_INFO, __func__, |
| "Failed to set TLSv1_method() for EAP-FAST"); |
| return -1; |
| } |
| } |
| #endif |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L |
| #ifdef SSL_OP_NO_TLSv1_3 |
| if (params->flags & TLS_CONN_EAP_FAST) { |
| /* Need to disable TLS v1.3 at least for now since OpenSSL 1.1.1 |
| * refuses to start the handshake with the modified ciphersuite |
| * list (no TLS v1.3 ciphersuites included) for EAP-FAST. */ |
| wpa_printf(MSG_DEBUG, "OpenSSL: Disable TLSv1.3 for EAP-FAST"); |
| SSL_set_options(conn->ssl, SSL_OP_NO_TLSv1_3); |
| } |
| #endif /* SSL_OP_NO_TLSv1_3 */ |
| #endif |
| #endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ |
| |
| while ((err = ERR_get_error())) { |
| wpa_printf(MSG_INFO, "%s: Clearing pending SSL error: %s", |
| __func__, ERR_error_string(err, NULL)); |
| } |
| |
| if (tls_set_conn_flags(conn, params->flags, |
| params->openssl_ciphers) < 0) { |
| wpa_printf(MSG_ERROR, "TLS: Failed to set connection flags"); |
| return -1; |
| } |
| |
| if (engine_id) { |
| wpa_printf(MSG_DEBUG, "SSL: Initializing TLS engine %s", |
| engine_id); |
| ret = tls_engine_init(conn, engine_id, params->pin, |
| key_id, cert_id, ca_cert_id); |
| if (ret) |
| return ret; |
| } |
| if (tls_connection_set_subject_match(conn, |
| params->subject_match, |
| params->altsubject_match, |
| params->suffix_match, |
| params->domain_match, |
| params->check_cert_subject)) { |
| wpa_printf(MSG_ERROR, "TLS: Failed to set subject match"); |
| return -1; |
| } |
| |
| if (engine_id && ca_cert_id) { |
| if (tls_connection_engine_ca_cert(data, conn, ca_cert_id)) |
| return TLS_SET_PARAMS_ENGINE_PRV_VERIFY_FAILED; |
| } else if (tls_connection_ca_cert(data, conn, params->ca_cert, |
| params->ca_cert_blob, |
| params->ca_cert_blob_len, |
| params->ca_path)) { |
| wpa_printf(MSG_ERROR, "TLS: Failed to parse Root CA certificate"); |
| return -1; |
| } |
| |
| if (engine_id && cert_id) { |
| if (tls_connection_engine_client_cert(conn, cert_id)) |
| return TLS_SET_PARAMS_ENGINE_PRV_VERIFY_FAILED; |
| } else if (tls_connection_client_cert(conn, params->client_cert, |
| params->client_cert_blob, |
| params->client_cert_blob_len)) { |
| wpa_printf(MSG_ERROR, "TLS: Failed to parse client certificate"); |
| return -1; |
| } |
| |
| if (engine_id && key_id) { |
| wpa_printf(MSG_DEBUG, "TLS: Using private key from engine"); |
| if (tls_connection_engine_private_key(conn)) |
| return TLS_SET_PARAMS_ENGINE_PRV_VERIFY_FAILED; |
| } else if (tls_connection_private_key(data, conn, |
| params->private_key, |
| params->private_key_passwd, |
| params->private_key_blob, |
| params->private_key_blob_len)) { |
| wpa_printf(MSG_INFO, "TLS: Failed to load private key '%s'", |
| params->private_key); |
| return -1; |
| } |
| |
| ciphers = params->openssl_ciphers; |
| #ifdef CONFIG_SUITEB |
| #ifdef OPENSSL_IS_BORINGSSL |
| if (ciphers && os_strcmp(ciphers, "SUITEB192") == 0) { |
| /* BoringSSL removed support for SUITEB192, so need to handle |
| * this with hardcoded ciphersuite and additional checks for |
| * other parameters. */ |
| ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384"; |
| } |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| #endif /* CONFIG_SUITEB */ |
| if (ciphers && SSL_set_cipher_list(conn->ssl, ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set cipher string '%s'", |
| ciphers); |
| return -1; |
| } |
| |
| if (!params->openssl_ecdh_curves) { |
| #ifndef OPENSSL_IS_BORINGSSL |
| #ifndef OPENSSL_NO_EC |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| if (SSL_set_ecdh_auto(conn->ssl, 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set ECDH curves to auto"); |
| return -1; |
| } |
| #endif /* < 1.1.0 */ |
| #endif /* OPENSSL_NO_EC */ |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| } else if (params->openssl_ecdh_curves[0]) { |
| #ifdef OPENSSL_IS_BORINGSSL |
| wpa_printf(MSG_INFO, |
| "OpenSSL: ECDH configuration not supported"); |
| return -1; |
| #else /* !OPENSSL_IS_BORINGSSL */ |
| #ifndef OPENSSL_NO_EC |
| if (SSL_set1_curves_list(conn->ssl, |
| params->openssl_ecdh_curves) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set ECDH curves '%s'", |
| params->openssl_ecdh_curves); |
| return -1; |
| } |
| #else /* OPENSSL_NO_EC */ |
| wpa_printf(MSG_INFO, "OpenSSL: ECDH not supported"); |
| return -1; |
| #endif /* OPENSSL_NO_EC */ |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| } |
| |
| #ifdef OPENSSL_IS_BORINGSSL |
| if (params->flags & TLS_CONN_REQUEST_OCSP) { |
| SSL_enable_ocsp_stapling(conn->ssl); |
| } |
| #else /* OPENSSL_IS_BORINGSSL */ |
| #ifdef HAVE_OCSP |
| if (params->flags & TLS_CONN_REQUEST_OCSP) { |
| SSL_CTX *ssl_ctx = data->ssl; |
| SSL_set_tlsext_status_type(conn->ssl, TLSEXT_STATUSTYPE_ocsp); |
| SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); |
| SSL_CTX_set_tlsext_status_arg(ssl_ctx, conn); |
| } |
| #else /* HAVE_OCSP */ |
| if (params->flags & TLS_CONN_REQUIRE_OCSP) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: No OCSP support included - reject configuration"); |
| return -1; |
| } |
| if (params->flags & TLS_CONN_REQUEST_OCSP) { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: No OCSP support included - allow optional OCSP case to continue"); |
| } |
| #endif /* HAVE_OCSP */ |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| |
| conn->flags = params->flags; |
| |
| tls_get_errors(data); |
| |
| return 0; |
| } |
| |
| |
| static void openssl_debug_dump_cipher_list(SSL_CTX *ssl_ctx) |
| { |
| SSL *ssl; |
| int i; |
| |
| ssl = SSL_new(ssl_ctx); |
| if (!ssl) |
| return; |
| |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Enabled cipher suites in priority order"); |
| for (i = 0; ; i++) { |
| const char *cipher; |
| |
| cipher = SSL_get_cipher_list(ssl, i); |
| if (!cipher) |
| break; |
| wpa_printf(MSG_DEBUG, "Cipher %d: %s", i, cipher); |
| } |
| |
| SSL_free(ssl); |
| } |
| |
| |
| #if !defined(LIBRESSL_VERSION_NUMBER) && !defined(BORINGSSL_API_VERSION) |
| |
| static const char * openssl_pkey_type_str(const EVP_PKEY *pkey) |
| { |
| if (!pkey) |
| return "NULL"; |
| switch (EVP_PKEY_type(EVP_PKEY_id(pkey))) { |
| case EVP_PKEY_RSA: |
| return "RSA"; |
| case EVP_PKEY_DSA: |
| return "DSA"; |
| case EVP_PKEY_DH: |
| return "DH"; |
| case EVP_PKEY_EC: |
| return "EC"; |
| default: |
| return "?"; |
| } |
| } |
| |
| |
| static void openssl_debug_dump_certificate(int i, X509 *cert) |
| { |
| char buf[256]; |
| EVP_PKEY *pkey; |
| ASN1_INTEGER *ser; |
| char serial_num[128]; |
| |
| if (!cert) |
| return; |
| |
| X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); |
| |
| ser = X509_get_serialNumber(cert); |
| if (ser) |
| wpa_snprintf_hex_uppercase(serial_num, sizeof(serial_num), |
| ASN1_STRING_get0_data(ser), |
| ASN1_STRING_length(ser)); |
| else |
| serial_num[0] = '\0'; |
| |
| pkey = X509_get_pubkey(cert); |
| wpa_printf(MSG_DEBUG, "%d: %s (%s) %s", i, buf, |
| openssl_pkey_type_str(pkey), serial_num); |
| EVP_PKEY_free(pkey); |
| } |
| |
| |
| static void openssl_debug_dump_certificates(SSL_CTX *ssl_ctx) |
| { |
| STACK_OF(X509) *certs; |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: Configured certificate chain"); |
| if (SSL_CTX_get0_chain_certs(ssl_ctx, &certs) == 1) { |
| int i; |
| |
| for (i = sk_X509_num(certs); i > 0; i--) |
| openssl_debug_dump_certificate(i, sk_X509_value(certs, |
| i - 1)); |
| } |
| openssl_debug_dump_certificate(0, SSL_CTX_get0_certificate(ssl_ctx)); |
| } |
| |
| #endif |
| |
| |
| static void openssl_debug_dump_certificate_chains(SSL_CTX *ssl_ctx) |
| { |
| #if !defined(LIBRESSL_VERSION_NUMBER) && !defined(BORINGSSL_API_VERSION) |
| int res; |
| |
| for (res = SSL_CTX_set_current_cert(ssl_ctx, SSL_CERT_SET_FIRST); |
| res == 1; |
| res = SSL_CTX_set_current_cert(ssl_ctx, SSL_CERT_SET_NEXT)) |
| openssl_debug_dump_certificates(ssl_ctx); |
| |
| SSL_CTX_set_current_cert(ssl_ctx, SSL_CERT_SET_FIRST); |
| #endif |
| } |
| |
| |
| static void openssl_debug_dump_ctx(SSL_CTX *ssl_ctx) |
| { |
| openssl_debug_dump_cipher_list(ssl_ctx); |
| openssl_debug_dump_certificate_chains(ssl_ctx); |
| } |
| |
| |
| int tls_global_set_params(void *tls_ctx, |
| const struct tls_connection_params *params) |
| { |
| struct tls_data *data = tls_ctx; |
| SSL_CTX *ssl_ctx = data->ssl; |
| unsigned long err; |
| |
| while ((err = ERR_get_error())) { |
| wpa_printf(MSG_INFO, "%s: Clearing pending SSL error: %s", |
| __func__, ERR_error_string(err, NULL)); |
| } |
| |
| os_free(data->check_cert_subject); |
| data->check_cert_subject = NULL; |
| if (params->check_cert_subject) { |
| data->check_cert_subject = |
| os_strdup(params->check_cert_subject); |
| if (!data->check_cert_subject) |
| return -1; |
| } |
| |
| if (tls_global_ca_cert(data, params->ca_cert) || |
| tls_global_client_cert(data, params->client_cert) || |
| tls_global_private_key(data, params->private_key, |
| params->private_key_passwd) || |
| tls_global_client_cert(data, params->client_cert2) || |
| tls_global_private_key(data, params->private_key2, |
| params->private_key_passwd2) || |
| tls_global_dh(data, params->dh_file)) { |
| wpa_printf(MSG_INFO, "TLS: Failed to set global parameters"); |
| return -1; |
| } |
| |
| if (params->openssl_ciphers && |
| SSL_CTX_set_cipher_list(ssl_ctx, params->openssl_ciphers) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set cipher string '%s'", |
| params->openssl_ciphers); |
| return -1; |
| } |
| |
| if (!params->openssl_ecdh_curves) { |
| #ifndef OPENSSL_IS_BORINGSSL |
| #ifndef OPENSSL_NO_EC |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| if (SSL_CTX_set_ecdh_auto(ssl_ctx, 1) != 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set ECDH curves to auto"); |
| return -1; |
| } |
| #endif /* < 1.1.0 */ |
| #endif /* OPENSSL_NO_EC */ |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| } else if (params->openssl_ecdh_curves[0]) { |
| #ifdef OPENSSL_IS_BORINGSSL |
| wpa_printf(MSG_INFO, |
| "OpenSSL: ECDH configuration not supported"); |
| return -1; |
| #else /* !OPENSSL_IS_BORINGSSL */ |
| #ifndef OPENSSL_NO_EC |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| SSL_CTX_set_ecdh_auto(ssl_ctx, 1); |
| #endif |
| if (SSL_CTX_set1_curves_list(ssl_ctx, |
| params->openssl_ecdh_curves) != |
| 1) { |
| wpa_printf(MSG_INFO, |
| "OpenSSL: Failed to set ECDH curves '%s'", |
| params->openssl_ecdh_curves); |
| return -1; |
| } |
| #else /* OPENSSL_NO_EC */ |
| wpa_printf(MSG_INFO, "OpenSSL: ECDH not supported"); |
| return -1; |
| #endif /* OPENSSL_NO_EC */ |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| } |
| |
| #ifdef SSL_OP_NO_TICKET |
| if (params->flags & TLS_CONN_DISABLE_SESSION_TICKET) |
| SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TICKET); |
| else |
| SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TICKET); |
| #endif /* SSL_OP_NO_TICKET */ |
| |
| #ifdef HAVE_OCSP |
| SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_status_cb); |
| SSL_CTX_set_tlsext_status_arg(ssl_ctx, ssl_ctx); |
| os_free(tls_global->ocsp_stapling_response); |
| if (params->ocsp_stapling_response) |
| tls_global->ocsp_stapling_response = |
| os_strdup(params->ocsp_stapling_response); |
| else |
| tls_global->ocsp_stapling_response = NULL; |
| #endif /* HAVE_OCSP */ |
| |
| openssl_debug_dump_ctx(ssl_ctx); |
| |
| return 0; |
| } |
| |
| |
| #ifdef EAP_FAST_OR_TEAP |
| /* Pre-shared secred requires a patch to openssl, so this function is |
| * commented out unless explicitly needed for EAP-FAST in order to be able to |
| * build this file with unmodified openssl. */ |
| |
| #if (defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER) |
| static int tls_sess_sec_cb(SSL *s, void *secret, int *secret_len, |
| STACK_OF(SSL_CIPHER) *peer_ciphers, |
| const SSL_CIPHER **cipher, void *arg) |
| #else /* OPENSSL_IS_BORINGSSL */ |
| static int tls_sess_sec_cb(SSL *s, void *secret, int *secret_len, |
| STACK_OF(SSL_CIPHER) *peer_ciphers, |
| SSL_CIPHER **cipher, void *arg) |
| #endif /* OPENSSL_IS_BORINGSSL */ |
| { |
| struct tls_connection *conn = arg; |
| int ret; |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| if (conn == NULL || conn->session_ticket_cb == NULL) |
| return 0; |
| |
| ret = conn->session_ticket_cb(conn->session_ticket_cb_ctx, |
| conn->session_ticket, |
| conn->session_ticket_len, |
| s->s3->client_random, |
| s->s3->server_random, secret); |
| #else |
| unsigned char client_random[SSL3_RANDOM_SIZE]; |
| unsigned char server_random[SSL3_RANDOM_SIZE]; |
| |
| if (conn == NULL || conn->session_ticket_cb == NULL) |
| return 0; |
| |
| SSL_get_client_random(s, client_random, sizeof(client_random)); |
| SSL_get_server_random(s, server_random, sizeof(server_random)); |
| |
| ret = conn->session_ticket_cb(conn->session_ticket_cb_ctx, |
| conn->session_ticket, |
| conn->session_ticket_len, |
| client_random, |
| server_random, secret); |
| #endif |
| |
| os_free(conn->session_ticket); |
| conn->session_ticket = NULL; |
| |
| if (ret <= 0) |
| return 0; |
| |
| *secret_len = SSL_MAX_MASTER_KEY_LENGTH; |
| return 1; |
| } |
| |
| |
| static int tls_session_ticket_ext_cb(SSL *s, const unsigned char *data, |
| int len, void *arg) |
| { |
| struct tls_connection *conn = arg; |
| |
| if (conn == NULL || conn->session_ticket_cb == NULL) |
| return 0; |
| |
| wpa_printf(MSG_DEBUG, "OpenSSL: %s: length=%d", __func__, len); |
| |
| os_free(conn->session_ticket); |
| conn->session_ticket = NULL; |
| |
| wpa_hexdump(MSG_DEBUG, "OpenSSL: ClientHello SessionTicket " |
| "extension", data, len); |
| |
| conn->session_ticket = os_memdup(data, len); |
| if (conn->session_ticket == NULL) |
| return 0; |
| |
| conn->session_ticket_len = len; |
| |
| return 1; |
| } |
| #endif /* EAP_FAST_OR_TEAP */ |
| |
| |
| int tls_connection_set_session_ticket_cb(void *tls_ctx, |
| struct tls_connection *conn, |
| tls_session_ticket_cb cb, |
| void *ctx) |
| { |
| #ifdef EAP_FAST_OR_TEAP |
| conn->session_ticket_cb = cb; |
| conn->session_ticket_cb_ctx = ctx; |
| |
| if (cb) { |
| if (SSL_set_session_secret_cb(conn->ssl, tls_sess_sec_cb, |
| conn) != 1) |
| return -1; |
| SSL_set_session_ticket_ext_cb(conn->ssl, |
| tls_session_ticket_ext_cb, conn); |
| } else { |
| if (SSL_set_session_secret_cb(conn->ssl, NULL, NULL) != 1) |
| return -1; |
| SSL_set_session_ticket_ext_cb(conn->ssl, NULL, NULL); |
| } |
| |
| return 0; |
| #else /* EAP_FAST_OR_TEAP */ |
| return -1; |
| #endif /* EAP_FAST_OR_TEAP */ |
| } |
| |
| |
| int tls_get_library_version(char *buf, size_t buf_len) |
| { |
| #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) |
| return os_snprintf(buf, buf_len, "OpenSSL build=%s run=%s", |
| OPENSSL_VERSION_TEXT, |
| OpenSSL_version(OPENSSL_VERSION)); |
| #else |
| return os_snprintf(buf, buf_len, "OpenSSL build=%s run=%s", |
| OPENSSL_VERSION_TEXT, |
| SSLeay_version(SSLEAY_VERSION)); |
| #endif |
| } |
| |
| |
| void tls_connection_set_success_data(struct tls_connection *conn, |
| struct wpabuf *data) |
| { |
| SSL_SESSION *sess; |
| struct wpabuf *old; |
| struct tls_session_data *sess_data = NULL; |
| |
| if (tls_ex_idx_session < 0) |
| goto fail; |
| sess = SSL_get_session(conn->ssl); |
| if (!sess) |
| goto fail; |
| old = SSL_SESSION_get_ex_data(sess, tls_ex_idx_session); |
| if (old) { |
| struct tls_session_data *found; |
| |
| found = get_session_data(conn->context, old); |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Replacing old success data %p (sess %p)%s", |
| old, sess, found ? "" : " (not freeing)"); |
| if (found) { |
| dl_list_del(&found->list); |
| os_free(found); |
| wpabuf_free(old); |
| } |
| } |
| |
| sess_data = os_zalloc(sizeof(*sess_data)); |
| if (!sess_data || |
| SSL_SESSION_set_ex_data(sess, tls_ex_idx_session, data) != 1) |
| goto fail; |
| |
| sess_data->buf = data; |
| dl_list_add(&conn->context->sessions, &sess_data->list); |
| wpa_printf(MSG_DEBUG, "OpenSSL: Stored success data %p (sess %p)", |
| data, sess); |
| conn->success_data = 1; |
| return; |
| |
| fail: |
| wpa_printf(MSG_INFO, "OpenSSL: Failed to store success data"); |
| wpabuf_free(data); |
| os_free(sess_data); |
| } |
| |
| |
| void tls_connection_set_success_data_resumed(struct tls_connection *conn) |
| { |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Success data accepted for resumed session"); |
| conn->success_data = 1; |
| } |
| |
| |
| const struct wpabuf * |
| tls_connection_get_success_data(struct tls_connection *conn) |
| { |
| SSL_SESSION *sess; |
| |
| if (tls_ex_idx_session < 0 || |
| !(sess = SSL_get_session(conn->ssl))) |
| return NULL; |
| return SSL_SESSION_get_ex_data(sess, tls_ex_idx_session); |
| } |
| |
| |
| void tls_connection_remove_session(struct tls_connection *conn) |
| { |
| SSL_SESSION *sess; |
| |
| sess = SSL_get_session(conn->ssl); |
| if (!sess) |
| return; |
| |
| if (SSL_CTX_remove_session(conn->ssl_ctx, sess) != 1) |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Session was not cached"); |
| else |
| wpa_printf(MSG_DEBUG, |
| "OpenSSL: Removed cached session to disable session resumption"); |
| } |
| |
| |
| int tls_get_tls_unique(struct tls_connection *conn, u8 *buf, size_t max_len) |
| { |
| size_t len; |
| int reused; |
| |
| reused = SSL_session_reused(conn->ssl); |
| if ((conn->server && !reused) || (!conn->server && reused)) |
| len = SSL_get_peer_finished(conn->ssl, buf, max_len); |
| else |
| len = SSL_get_finished(conn->ssl, buf, max_len); |
| |
| if (len == 0 || len > max_len) |
| return -1; |
| |
| return len; |
| } |
| |
| |
| u16 tls_connection_get_cipher_suite(struct tls_connection *conn) |
| { |
| const SSL_CIPHER *cipher; |
| |
| cipher = SSL_get_current_cipher(conn->ssl); |
| if (!cipher) |
| return 0; |
| #if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) |
| return SSL_CIPHER_get_protocol_id(cipher); |
| #else |
| return SSL_CIPHER_get_id(cipher) & 0xFFFF; |
| #endif |
| } |
| |
| |
| const char * tls_connection_get_peer_subject(struct tls_connection *conn) |
| { |
| if (conn) |
| return conn->peer_subject; |
| return NULL; |
| } |
| |
| |
| bool tls_connection_get_own_cert_used(struct tls_connection *conn) |
| { |
| if (conn) |
| return SSL_get_certificate(conn->ssl) != NULL; |
| return false; |
| } |
| |
| void tls_register_cert_callback(tls_get_certificate_cb cb) |
| { |
| certificate_callback_global = cb; |
| } |
| |
| void tls_register_openssl_failure_callback(tls_openssl_failure_cb cb) |
| { |
| openssl_failure_callback_global = cb; |
| } |