/* * SSL/TLS interface functions for wolfSSL TLS case * Copyright (c) 2004-2017, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include "common.h" #include "crypto.h" #include "crypto/sha1.h" #include "crypto/sha256.h" #include "tls.h" /* wolfSSL includes */ #include #include #include #include #include #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) #define HAVE_AESGCM #include #endif #ifdef CONFIG_FIPS #include #endif /* CONFIG_FIPS */ #if !defined(CONFIG_FIPS) && \ (defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || \ defined(EAP_SERVER_FAST)) #define WOLFSSL_NEED_EAP_FAST_PRF #endif #define SECRET_LEN 48 #define RAN_LEN 32 #define SESSION_TICKET_LEN 256 static int tls_ref_count = 0; static int tls_ex_idx_session = 0; /* tls input data for wolfSSL Read Callback */ struct tls_in_data { const struct wpabuf *in_data; size_t consumed; /* how many bytes have we used already */ }; /* tls output data for wolfSSL Write Callback */ struct tls_out_data { struct wpabuf *out_data; }; 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; unsigned int tls_session_lifetime; }; static struct tls_context *tls_global = NULL; /* wolfssl tls_connection */ struct tls_connection { struct tls_context *context; WOLFSSL *ssl; int read_alerts; int write_alerts; int failed; struct tls_in_data input; struct tls_out_data output; char *subject_match; char *alt_subject_match; char *suffix_match; char *domain_match; u8 srv_cert_hash[32]; unsigned char client_random[RAN_LEN]; unsigned char server_random[RAN_LEN]; unsigned int flags; #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) tls_session_ticket_cb session_ticket_cb; void *session_ticket_cb_ctx; byte session_ticket[SESSION_TICKET_LEN]; #endif unsigned int ca_cert_verify:1; unsigned int cert_probe:1; unsigned int server_cert_only:1; unsigned int success_data:1; WOLFSSL_X509 *peer_cert; WOLFSSL_X509 *peer_issuer; WOLFSSL_X509 *peer_issuer_issuer; char *peer_subject; /* peer subject info for authenticated peer */ }; static struct tls_context * tls_context_new(const struct tls_config *conf) { struct tls_context *context = os_zalloc(sizeof(*context)); if (!context) return NULL; if (conf) { context->event_cb = conf->event_cb; context->cb_ctx = conf->cb_ctx; context->cert_in_cb = conf->cert_in_cb; } return context; } static void wolfssl_reset_in_data(struct tls_in_data *in, const struct wpabuf *buf) { /* old one not owned by us so don't free */ in->in_data = buf; in->consumed = 0; } static void wolfssl_reset_out_data(struct tls_out_data *out) { /* old one not owned by us so don't free */ out->out_data = wpabuf_alloc_copy("", 0); } /* wolfSSL I/O Receive CallBack */ static int wolfssl_receive_cb(WOLFSSL *ssl, char *buf, int sz, void *ctx) { size_t get = sz; struct tls_in_data *data = ctx; if (!data) return -1; if (get > (wpabuf_len(data->in_data) - data->consumed)) get = wpabuf_len(data->in_data) - data->consumed; os_memcpy(buf, wpabuf_head_u8(data->in_data) + data->consumed, get); data->consumed += get; if (get == 0) return -2; /* WANT_READ */ return (int) get; } /* wolfSSL I/O Send CallBack */ static int wolfssl_send_cb(WOLFSSL *ssl, char *buf, int sz, void *ctx) { struct wpabuf *tmp; struct tls_out_data *data = ctx; if (!data) return -1; wpa_printf(MSG_DEBUG, "SSL: adding %d bytes", sz); tmp = wpabuf_alloc_copy(buf, sz); if (!tmp) return -1; data->out_data = wpabuf_concat(data->out_data, tmp); if (!data->out_data) return -1; return sz; } static void remove_session_cb(WOLFSSL_CTX *ctx, WOLFSSL_SESSION *sess) { struct wpabuf *buf; buf = wolfSSL_SESSION_get_ex_data(sess, tls_ex_idx_session); if (!buf) return; wpa_printf(MSG_DEBUG, "wolfSSL: Free application session data %p (sess %p)", buf, sess); wpabuf_free(buf); wolfSSL_SESSION_set_ex_data(sess, tls_ex_idx_session, NULL); } #if defined(CONFIG_FIPS) && defined(HAVE_FIPS) static void wcFipsCb(int ok, int err, const char *hash) { wpa_printf(MSG_INFO, "wolfFIPS: wolfCrypt Fips error callback, ok = %d, err = %d", ok, err); wpa_printf(MSG_INFO, "wolfFIPS: message = %s", wc_GetErrorString(err)); wpa_printf(MSG_INFO, "wolfFIPS: hash = %s", hash); if (err == IN_CORE_FIPS_E) { wpa_printf(MSG_ERROR, "wolfFIPS: In core integrity hash check failure, copy above hash"); wpa_printf(MSG_ERROR, "wolfFIPS: into verifyCore[] in fips_test.c and rebuild"); } } #endif /* CONFIG_FIPS && HAVE_FIPS */ #ifdef DEBUG_WOLFSSL static void wolfSSL_logging_cb(const int log_level, const char * const log_message) { (void) log_level; wpa_printf(MSG_DEBUG, "wolfSSL log:%s", log_message); } #endif /* DEBUG_WOLFSSL */ void * tls_init(const struct tls_config *conf) { WOLFSSL_CTX *ssl_ctx; struct tls_context *context; const char *ciphers; #ifdef DEBUG_WOLFSSL wolfSSL_SetLoggingCb(wolfSSL_logging_cb); wolfSSL_Debugging_ON(); #endif /* DEBUG_WOLFSSL */ context = tls_context_new(conf); if (!context) return NULL; if (tls_ref_count == 0) { tls_global = context; if (wolfSSL_Init() < 0) return NULL; #if defined(CONFIG_FIPS) && defined(HAVE_FIPS) wolfCrypt_SetCb_fips(wcFipsCb); #endif /* CONFIG_FIPS && HAVE_FIPS */ } tls_ref_count++; /* start as client */ ssl_ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); if (!ssl_ctx) { tls_ref_count--; if (context != tls_global) os_free(context); if (tls_ref_count == 0) { os_free(tls_global); tls_global = NULL; } } wolfSSL_SetIORecv(ssl_ctx, wolfssl_receive_cb); wolfSSL_SetIOSend(ssl_ctx, wolfssl_send_cb); context->tls_session_lifetime = conf->tls_session_lifetime; wolfSSL_CTX_set_ex_data(ssl_ctx, 0, context); if (conf->tls_session_lifetime > 0) { wolfSSL_CTX_set_session_id_context(ssl_ctx, (const unsigned char *) "hostapd", 7); wolfSSL_CTX_set_quiet_shutdown(ssl_ctx, 1); wolfSSL_CTX_set_session_cache_mode(ssl_ctx, WOLFSSL_SESS_CACHE_SERVER); wolfSSL_CTX_set_timeout(ssl_ctx, conf->tls_session_lifetime); wolfSSL_CTX_sess_set_remove_cb(ssl_ctx, remove_session_cb); } else { wolfSSL_CTX_set_session_cache_mode(ssl_ctx, WOLFSSL_SESS_CACHE_OFF); } if (conf && conf->openssl_ciphers) ciphers = conf->openssl_ciphers; else ciphers = "ALL"; if (wolfSSL_CTX_set_cipher_list(ssl_ctx, ciphers) != 1) { wpa_printf(MSG_ERROR, "wolfSSL: Failed to set cipher string '%s'", ciphers); tls_deinit(ssl_ctx); return NULL; } return ssl_ctx; } void tls_deinit(void *ssl_ctx) { struct tls_context *context = wolfSSL_CTX_get_ex_data(ssl_ctx, 0); if (context != tls_global) os_free(context); wolfSSL_CTX_free((WOLFSSL_CTX *) ssl_ctx); tls_ref_count--; if (tls_ref_count == 0) { wolfSSL_Cleanup(); os_free(tls_global); tls_global = NULL; } } int tls_get_errors(void *tls_ctx) { #ifdef DEBUG_WOLFSSL #if 0 unsigned long err; err = wolfSSL_ERR_peek_last_error_line(NULL, NULL); if (err != 0) { wpa_printf(MSG_INFO, "TLS - SSL error: %s", wolfSSL_ERR_error_string(err, NULL)); return 1; } #endif #endif /* DEBUG_WOLFSSL */ return 0; } struct tls_connection * tls_connection_init(void *tls_ctx) { WOLFSSL_CTX *ssl_ctx = tls_ctx; struct tls_connection *conn; wpa_printf(MSG_DEBUG, "SSL: connection init"); conn = os_zalloc(sizeof(*conn)); if (!conn) return NULL; conn->ssl = wolfSSL_new(ssl_ctx); if (!conn->ssl) { os_free(conn); return NULL; } wolfSSL_SetIOReadCtx(conn->ssl, &conn->input); wolfSSL_SetIOWriteCtx(conn->ssl, &conn->output); wolfSSL_set_ex_data(conn->ssl, 0, conn); conn->context = wolfSSL_CTX_get_ex_data(ssl_ctx, 0); /* Need randoms post-hanshake for EAP-FAST, export key and deriving * session ID in EAP methods. */ wolfSSL_KeepArrays(conn->ssl); wolfSSL_KeepHandshakeResources(conn->ssl); wolfSSL_UseClientSuites(conn->ssl); return conn; } void tls_connection_deinit(void *tls_ctx, struct tls_connection *conn) { if (!conn) return; wpa_printf(MSG_DEBUG, "SSL: connection deinit"); /* parts */ wolfSSL_free(conn->ssl); os_free(conn->subject_match); os_free(conn->alt_subject_match); os_free(conn->suffix_match); os_free(conn->domain_match); os_free(conn->peer_subject); /* self */ os_free(conn); } int tls_connection_established(void *tls_ctx, struct tls_connection *conn) { return conn ? wolfSSL_is_init_finished(conn->ssl) : 0; } char * tls_connection_peer_serial_num(void *tls_ctx, struct tls_connection *conn) { /* TODO */ return NULL; } int tls_connection_shutdown(void *tls_ctx, struct tls_connection *conn) { WOLFSSL_SESSION *session; if (!conn) return -1; wpa_printf(MSG_DEBUG, "SSL: connection shutdown"); /* Set quiet as OpenSSL does */ wolfSSL_set_quiet_shutdown(conn->ssl, 1); wolfSSL_shutdown(conn->ssl); session = wolfSSL_get1_session(conn->ssl); if (wolfSSL_clear(conn->ssl) != 1) { wolfSSL_SESSION_free(session); return -1; } wolfSSL_set_session(conn->ssl, session); wolfSSL_SESSION_free(session); return 0; } static int tls_connection_set_subject_match(struct tls_connection *conn, const char *subject_match, const char *alt_subject_match, const char *suffix_match, const char *domain_match) { os_free(conn->subject_match); conn->subject_match = NULL; if (subject_match) { conn->subject_match = os_strdup(subject_match); if (!conn->subject_match) return -1; } os_free(conn->alt_subject_match); conn->alt_subject_match = NULL; if (alt_subject_match) { conn->alt_subject_match = os_strdup(alt_subject_match); if (!conn->alt_subject_match) 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) 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) return -1; } return 0; } static int tls_connection_client_cert(struct tls_connection *conn, const char *client_cert, const u8 *client_cert_blob, size_t blob_len) { if (!client_cert && !client_cert_blob) return 0; if (client_cert_blob) { if (wolfSSL_use_certificate_chain_buffer_format( conn->ssl, client_cert_blob, blob_len, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use client cert DER blob failed"); if (wolfSSL_use_certificate_chain_buffer_format( conn->ssl, client_cert_blob, blob_len, SSL_FILETYPE_PEM) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use client cert PEM blob failed"); return -1; } } wpa_printf(MSG_DEBUG, "SSL: use client cert blob OK"); return 0; } if (client_cert) { if (wolfSSL_use_certificate_chain_file( conn->ssl, client_cert) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use client cert PEM file failed"); if (wolfSSL_use_certificate_chain_file_format( conn->ssl, client_cert, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use client cert DER file failed"); return -1; } } wpa_printf(MSG_DEBUG, "SSL: use client cert file OK"); return 0; } return 0; } static int tls_passwd_cb(char *buf, int size, int rwflag, void *password) { if (!password) return 0; os_strlcpy(buf, (char *) password, size); return os_strlen(buf); } static int tls_connection_private_key(void *tls_ctx, struct tls_connection *conn, const char *private_key, const char *private_key_passwd, const u8 *private_key_blob, size_t blob_len) { WOLFSSL_CTX *ctx = tls_ctx; char *passwd = NULL; int ok = 0; if (!private_key && !private_key_blob) return 0; if (private_key_passwd) { passwd = os_strdup(private_key_passwd); if (!passwd) return -1; } wolfSSL_CTX_set_default_passwd_cb(ctx, tls_passwd_cb); wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, passwd); if (private_key_blob) { if (wolfSSL_use_PrivateKey_buffer(conn->ssl, private_key_blob, blob_len, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use private DER blob failed"); if (wolfSSL_use_PrivateKey_buffer( conn->ssl, private_key_blob, blob_len, SSL_FILETYPE_PEM) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use private PEM blob failed"); } else { ok = 1; } } else { ok = 1; } if (ok) wpa_printf(MSG_DEBUG, "SSL: use private key blob OK"); } if (!ok && private_key) { if (wolfSSL_use_PrivateKey_file(conn->ssl, private_key, SSL_FILETYPE_PEM) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use private key PEM file failed"); if (wolfSSL_use_PrivateKey_file(conn->ssl, private_key, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: use private key DER file failed"); } else { ok = 1; } } else { ok = 1; } if (ok) wpa_printf(MSG_DEBUG, "SSL: use private key file OK"); } wolfSSL_CTX_set_default_passwd_cb(ctx, NULL); os_free(passwd); if (!ok) return -1; return 0; } static int tls_match_alt_subject_component(WOLFSSL_X509 *cert, int type, const char *value, size_t len) { WOLFSSL_GENERAL_NAME *gen; void *ext; int found = 0; int i; ext = wolfSSL_X509_get_ext_d2i(cert, ALT_NAMES_OID, NULL, NULL); for (i = 0; ext && i < wolfSSL_sk_num(ext); i++) { gen = wolfSSL_sk_value(ext, i); if (!gen || gen->type != type) continue; if ((size_t) wolfSSL_ASN1_STRING_length(gen->d.ia5) == len && os_memcmp(value, wolfSSL_ASN1_STRING_data(gen->d.ia5), len) == 0) found++; } wolfSSL_sk_GENERAL_NAME_free(ext); return found; } static int tls_match_alt_subject(WOLFSSL_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_alt_subject_component(cert, type, pos, len) > 0) return 1; pos = end + 1; } while (end); return 0; } static int domain_suffix_match(const char *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(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; } static int tls_match_suffix_helper(WOLFSSL_X509 *cert, const char *match, size_t match_len, int full) { WOLFSSL_GENERAL_NAME *gen; void *ext; int i; int j; int dns_name = 0; WOLFSSL_X509_NAME *name; wpa_printf(MSG_DEBUG, "TLS: Match domain against %s%s", full ? "" : "suffix ", match); ext = wolfSSL_X509_get_ext_d2i(cert, ALT_NAMES_OID, NULL, NULL); for (j = 0; ext && j < wolfSSL_sk_num(ext); j++) { gen = wolfSSL_sk_value(ext, j); if (!gen || gen->type != ASN_DNS_TYPE) continue; dns_name++; wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate dNSName", wolfSSL_ASN1_STRING_data(gen->d.ia5), wolfSSL_ASN1_STRING_length(gen->d.ia5)); if (domain_suffix_match( (const char *) wolfSSL_ASN1_STRING_data(gen->d.ia5), wolfSSL_ASN1_STRING_length(gen->d.ia5), match, match_len, full) == 1) { wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", full ? "Match" : "Suffix match"); wolfSSL_sk_ASN1_OBJECT_free(ext); return 1; } } wolfSSL_sk_GENERAL_NAME_free(ext); if (dns_name) { wpa_printf(MSG_DEBUG, "TLS: None of the dNSName(s) matched"); return 0; } name = wolfSSL_X509_get_subject_name(cert); i = -1; for (;;) { WOLFSSL_X509_NAME_ENTRY *e; WOLFSSL_ASN1_STRING *cn; i = wolfSSL_X509_NAME_get_index_by_NID(name, NID_commonName, i); if (i == -1) break; e = wolfSSL_X509_NAME_get_entry(name, i); if (!e) continue; cn = wolfSSL_X509_NAME_ENTRY_get_data(e); if (!cn) 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; } static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full) { 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; } static enum tls_fail_reason wolfssl_tls_fail_reason(int err) { switch (err) { case X509_V_ERR_CERT_REVOKED: return TLS_FAIL_REVOKED; case ASN_BEFORE_DATE_E: case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_CRL_NOT_YET_VALID: return TLS_FAIL_NOT_YET_VALID; case ASN_AFTER_DATE_E: 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 const char * wolfssl_tls_err_string(int err, const char *err_str) { switch (err) { case ASN_BEFORE_DATE_E: return "certificate is not yet valid"; case ASN_AFTER_DATE_E: return "certificate has expired"; default: return err_str; } } static struct wpabuf * get_x509_cert(WOLFSSL_X509 *cert) { struct wpabuf *buf = NULL; const u8 *data; int cert_len; data = wolfSSL_X509_get_der(cert, &cert_len); if (!data) buf = wpabuf_alloc_copy(data, cert_len); return buf; } static void wolfssl_tls_fail_event(struct tls_connection *conn, WOLFSSL_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; if (!context->event_cb) return; cert = get_x509_cert(err_cert); os_memset(&ev, 0, sizeof(ev)); ev.cert_fail.reason = reason != TLS_FAIL_UNSPECIFIED ? reason : wolfssl_tls_fail_reason(err); ev.cert_fail.depth = depth; ev.cert_fail.subject = subject; ev.cert_fail.reason_txt = wolfssl_tls_err_string(err, err_str); ev.cert_fail.cert = cert; context->event_cb(context->cb_ctx, TLS_CERT_CHAIN_FAILURE, &ev); wpabuf_free(cert); } static void wolfssl_tls_cert_event(struct tls_connection *conn, WOLFSSL_X509 *err_cert, int depth, const char *subject) { struct wpabuf *cert = NULL; union tls_event_data ev; struct tls_context *context = conn->context; char *alt_subject[TLS_MAX_ALT_SUBJECT]; int alt, num_alt_subject = 0; WOLFSSL_GENERAL_NAME *gen; void *ext; int i; #ifdef CONFIG_SHA256 u8 hash[32]; #endif /* CONFIG_SHA256 */ if (!context->event_cb) 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; ext = wolfSSL_X509_get_ext_d2i(err_cert, ALT_NAMES_OID, NULL, NULL); for (i = 0; ext && i < wolfSSL_sk_num(ext); i++) { char *pos; if (num_alt_subject == TLS_MAX_ALT_SUBJECT) break; gen = wolfSSL_sk_value((void *) ext, i); if (!gen || (gen->type != GEN_EMAIL && gen->type != GEN_DNS && gen->type != GEN_URI)) continue; pos = os_malloc(10 + wolfSSL_ASN1_STRING_length(gen->d.ia5) + 1); if (!pos) break; alt_subject[num_alt_subject++] = 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, wolfSSL_ASN1_STRING_data(gen->d.ia5), wolfSSL_ASN1_STRING_length(gen->d.ia5)); pos += wolfSSL_ASN1_STRING_length(gen->d.ia5); *pos = '\0'; } wolfSSL_sk_GENERAL_NAME_free(ext); for (alt = 0; alt < num_alt_subject; alt++) ev.peer_cert.altsubject[alt] = alt_subject[alt]; ev.peer_cert.num_altsubject = num_alt_subject; context->event_cb(context->cb_ctx, TLS_PEER_CERTIFICATE, &ev); wpabuf_free(cert); for (alt = 0; alt < num_alt_subject; alt++) os_free(alt_subject[alt]); } static int tls_verify_cb(int preverify_ok, WOLFSSL_X509_STORE_CTX *x509_ctx) { char buf[256]; WOLFSSL_X509 *err_cert; int err, depth; WOLFSSL *ssl; struct tls_connection *conn; struct tls_context *context; char *match, *altmatch, *suffix_match, *domain_match; const char *err_str; err_cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx); if (!err_cert) { wpa_printf(MSG_DEBUG, "wolfSSL: No Cert"); return 0; } err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx); depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx); ssl = wolfSSL_X509_STORE_CTX_get_ex_data( x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()); wolfSSL_X509_NAME_oneline(wolfSSL_X509_get_subject_name(err_cert), buf, sizeof(buf)); conn = wolfSSL_get_ex_data(ssl, 0); if (!conn) { wpa_printf(MSG_DEBUG, "wolfSSL: No ex_data"); 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->alt_subject_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 == ASN_AFTER_DATE_E || err == ASN_BEFORE_DATE_E || err == X509_V_ERR_CERT_NOT_YET_VALID)) { wpa_printf(MSG_DEBUG, "wolfSSL: Ignore certificate validity time mismatch"); preverify_ok = 1; } err_str = wolfSSL_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, "wolfSSL: 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, "wolfSSL: Ignore validation issues for a pinned server certificate"); preverify_ok = 1; } wpabuf_free(cert); } } #endif /* CONFIG_SHA256 */ if (!preverify_ok) { wpa_printf(MSG_WARNING, "TLS: Certificate verification failed, error %d (%s) depth %d for '%s'", err, err_str, depth, buf); wolfssl_tls_fail_event(conn, err_cert, err, depth, buf, err_str, TLS_FAIL_UNSPECIFIED); return preverify_ok; } wpa_printf(MSG_DEBUG, "TLS: %s - preverify_ok=%d err=%d (%s) ca_cert_verify=%d depth=%d buf='%s'", __func__, preverify_ok, err, err_str, conn->ca_cert_verify, depth, buf); if (depth == 0 && match && os_strstr(buf, match) == NULL) { wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not match with '%s'", buf, match); preverify_ok = 0; wolfssl_tls_fail_event(conn, err_cert, err, depth, buf, "Subject mismatch", TLS_FAIL_SUBJECT_MISMATCH); } else if (depth == 0 && altmatch && !tls_match_alt_subject(err_cert, altmatch)) { wpa_printf(MSG_WARNING, "TLS: altSubjectName match '%s' not found", altmatch); preverify_ok = 0; wolfssl_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; wolfssl_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; wolfssl_tls_fail_event(conn, err_cert, err, depth, buf, "Domain mismatch", TLS_FAIL_DOMAIN_MISMATCH); } else { wolfssl_tls_cert_event(conn, err_cert, depth, buf); } if (conn->cert_probe && preverify_ok && depth == 0) { wpa_printf(MSG_DEBUG, "wolfSSL: Reject server certificate on probe-only run"); preverify_ok = 0; wolfssl_tls_fail_event(conn, err_cert, err, depth, buf, "Server certificate chain probe", TLS_FAIL_SERVER_CHAIN_PROBE); } #ifdef HAVE_OCSP_WOLFSSL 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; wolfssl_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; wolfssl_tls_fail_event(conn, err_cert, err, depth, buf, "bad certificate status response", TLS_FAIL_UNSPECIFIED); } } #endif /* HAVE_OCSP_WOLFSSL */ 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; } static int tls_connection_ca_cert(void *tls_ctx, struct tls_connection *conn, const char *ca_cert, const u8 *ca_cert_blob, size_t blob_len, const char *ca_path) { WOLFSSL_CTX *ctx = tls_ctx; wolfSSL_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, "wolfSSL: 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, "wolfSSL: Unsupported ca_cert hash value '%s'", ca_cert); return -1; } pos += 14; if (os_strlen(pos) != 32 * 2) { wpa_printf(MSG_DEBUG, "wolfSSL: 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, "wolfSSL: Invalid SHA256 hash value in ca_cert '%s'", ca_cert); return -1; } conn->server_cert_only = 1; wpa_printf(MSG_DEBUG, "wolfSSL: 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) { if (wolfSSL_CTX_load_verify_buffer(ctx, ca_cert_blob, blob_len, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: failed to load DER CA blob"); if (wolfSSL_CTX_load_verify_buffer( ctx, ca_cert_blob, blob_len, SSL_FILETYPE_PEM) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: failed to load PEM CA blob"); return -1; } } wpa_printf(MSG_DEBUG, "SSL: use CA cert blob OK"); return 0; } if (ca_cert || ca_path) { WOLFSSL_X509_STORE *cm = wolfSSL_X509_STORE_new(); if (!cm) { wpa_printf(MSG_INFO, "SSL: failed to create certificate store"); return -1; } wolfSSL_CTX_set_cert_store(ctx, cm); if (wolfSSL_CTX_load_verify_locations(ctx, ca_cert, ca_path) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: failed to load ca_cert as PEM"); if (!ca_cert) return -1; if (wolfSSL_CTX_der_load_verify_locations( ctx, ca_cert, SSL_FILETYPE_ASN1) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "SSL: failed to load ca_cert as DER"); return -1; } } return 0; } conn->ca_cert_verify = 0; return 0; } static void tls_set_conn_flags(WOLFSSL *ssl, unsigned int flags) { #ifdef HAVE_SESSION_TICKET if (!(flags & TLS_CONN_DISABLE_SESSION_TICKET)) wolfSSL_UseSessionTicket(ssl); #endif /* HAVE_SESSION_TICKET */ if (flags & TLS_CONN_DISABLE_TLSv1_0) wolfSSL_set_options(ssl, SSL_OP_NO_TLSv1); if (flags & TLS_CONN_DISABLE_TLSv1_1) wolfSSL_set_options(ssl, SSL_OP_NO_TLSv1_1); if (flags & TLS_CONN_DISABLE_TLSv1_2) wolfSSL_set_options(ssl, SSL_OP_NO_TLSv1_2); if (flags & TLS_CONN_DISABLE_TLSv1_3) wolfSSL_set_options(ssl, SSL_OP_NO_TLSv1_3); } int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, const struct tls_connection_params *params) { wpa_printf(MSG_DEBUG, "SSL: set params"); if (tls_connection_set_subject_match(conn, params->subject_match, params->altsubject_match, params->suffix_match, params->domain_match) < 0) { wpa_printf(MSG_INFO, "Error setting subject match"); return -1; } if (tls_connection_ca_cert(tls_ctx, conn, params->ca_cert, params->ca_cert_blob, params->ca_cert_blob_len, params->ca_path) < 0) { wpa_printf(MSG_INFO, "Error setting CA cert"); return -1; } if (tls_connection_client_cert(conn, params->client_cert, params->client_cert_blob, params->client_cert_blob_len) < 0) { wpa_printf(MSG_INFO, "Error setting client cert"); return -1; } if (tls_connection_private_key(tls_ctx, conn, params->private_key, params->private_key_passwd, params->private_key_blob, params->private_key_blob_len) < 0) { wpa_printf(MSG_INFO, "Error setting private key"); return -1; } if (params->openssl_ciphers && wolfSSL_set_cipher_list(conn->ssl, params->openssl_ciphers) != 1) { wpa_printf(MSG_INFO, "wolfSSL: Failed to set cipher string '%s'", params->openssl_ciphers); return -1; } tls_set_conn_flags(conn->ssl, params->flags); #ifdef HAVE_CERTIFICATE_STATUS_REQUEST if (params->flags & TLS_CONN_REQUEST_OCSP) { if (wolfSSL_UseOCSPStapling(conn->ssl, WOLFSSL_CSR_OCSP, WOLFSSL_CSR_OCSP_USE_NONCE) != SSL_SUCCESS) return -1; if (wolfSSL_EnableOCSPStapling(conn->ssl) != SSL_SUCCESS) return -1; } #endif /* HAVE_CERTIFICATE_STATUS_REQUEST */ #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 if (params->flags & TLS_CONN_REQUEST_OCSP) { if (wolfSSL_UseOCSPStaplingV2(conn->ssl, WOLFSSL_CSR2_OCSP_MULTI, 0) != SSL_SUCCESS) return -1; if (wolfSSL_EnableOCSPStapling(conn->ssl) != SSL_SUCCESS) return -1; } #endif /* HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ #if !defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ !defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) #ifdef HAVE_OCSP if (params->flags & TLS_CONN_REQUEST_OCSP) wolfSSL_CTX_EnableOCSP(ctx, 0); #else /* HAVE_OCSP */ if (params->flags & TLS_CONN_REQUIRE_OCSP) { wpa_printf(MSG_INFO, "wolfSSL: No OCSP support included - reject configuration"); return -1; } if (params->flags & TLS_CONN_REQUEST_OCSP) { wpa_printf(MSG_DEBUG, "wolfSSL: No OCSP support included - allow optional OCSP case to continue"); } #endif /* HAVE_OCSP */ #endif /* !HAVE_CERTIFICATE_STATUS_REQUEST && * !HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ conn->flags = params->flags; return 0; } static int tls_global_ca_cert(void *ssl_ctx, const char *ca_cert) { WOLFSSL_CTX *ctx = ssl_ctx; if (ca_cert) { if (wolfSSL_CTX_load_verify_locations(ctx, ca_cert, NULL) != 1) { wpa_printf(MSG_WARNING, "Failed to load root certificates"); return -1; } wpa_printf(MSG_DEBUG, "TLS: Trusted root certificate(s) loaded"); } return 0; } static int tls_global_client_cert(void *ssl_ctx, const char *client_cert) { WOLFSSL_CTX *ctx = ssl_ctx; if (!client_cert) return 0; if (wolfSSL_CTX_use_certificate_chain_file_format(ctx, client_cert, SSL_FILETYPE_ASN1) != SSL_SUCCESS && wolfSSL_CTX_use_certificate_chain_file(ctx, client_cert) != SSL_SUCCESS) { wpa_printf(MSG_INFO, "Failed to load client certificate"); return -1; } wpa_printf(MSG_DEBUG, "SSL: Loaded global client certificate: %s", client_cert); return 0; } static int tls_global_private_key(void *ssl_ctx, const char *private_key, const char *private_key_passwd) { WOLFSSL_CTX *ctx = ssl_ctx; char *passwd = NULL; int ret = 0; if (!private_key) return 0; if (private_key_passwd) { passwd = os_strdup(private_key_passwd); if (!passwd) return -1; } wolfSSL_CTX_set_default_passwd_cb(ctx, tls_passwd_cb); wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, passwd); if (wolfSSL_CTX_use_PrivateKey_file(ctx, private_key, SSL_FILETYPE_ASN1) != 1 && wolfSSL_CTX_use_PrivateKey_file(ctx, private_key, SSL_FILETYPE_PEM) != 1) { wpa_printf(MSG_INFO, "Failed to load private key"); ret = -1; } wpa_printf(MSG_DEBUG, "SSL: Loaded global private key"); os_free(passwd); wolfSSL_CTX_set_default_passwd_cb(ctx, NULL); return ret; } static int tls_global_dh(void *ssl_ctx, const char *dh_file) { WOLFSSL_CTX *ctx = ssl_ctx; if (dh_file) { if (wolfSSL_CTX_SetTmpDH_file(ctx, dh_file, SSL_FILETYPE_PEM) < 0) { wpa_printf(MSG_INFO, "SSL: global use DH PEM file failed"); if (wolfSSL_CTX_SetTmpDH_file(ctx, dh_file, SSL_FILETYPE_ASN1) < 0) { wpa_printf(MSG_INFO, "SSL: global use DH DER file failed"); return -1; } } wpa_printf(MSG_DEBUG, "SSL: global use DH file OK"); return 0; } return 0; } #ifdef HAVE_OCSP int ocsp_status_cb(void *unused, const char *url, int url_sz, unsigned char *request, int request_sz, unsigned char **response) { size_t len; (void) unused; if (!url) { wpa_printf(MSG_DEBUG, "wolfSSL: OCSP status callback - no response configured"); *response = NULL; return 0; } *response = (unsigned char *) os_readfile(url, &len); if (!*response) { wpa_printf(MSG_DEBUG, "wolfSSL: OCSP status callback - could not read response file"); return -1; } wpa_printf(MSG_DEBUG, "wolfSSL: OCSP status callback - send cached response"); return len; } void ocsp_resp_free_cb(void *ocsp_stapling_response, unsigned char *response) { os_free(response); } #endif /* HAVE_OCSP */ int tls_global_set_params(void *tls_ctx, const struct tls_connection_params *params) { wpa_printf(MSG_DEBUG, "SSL: global set params"); if (params->check_cert_subject) return -1; /* not yet supported */ if (tls_global_ca_cert(tls_ctx, params->ca_cert) < 0) { wpa_printf(MSG_INFO, "SSL: Failed to load ca cert file '%s'", params->ca_cert); return -1; } if (tls_global_client_cert(tls_ctx, params->client_cert) < 0) { wpa_printf(MSG_INFO, "SSL: Failed to load client cert file '%s'", params->client_cert); return -1; } if (tls_global_private_key(tls_ctx, params->private_key, params->private_key_passwd) < 0) { wpa_printf(MSG_INFO, "SSL: Failed to load private key file '%s'", params->private_key); return -1; } if (tls_global_dh(tls_ctx, params->dh_file) < 0) { wpa_printf(MSG_INFO, "SSL: Failed to load DH file '%s'", params->dh_file); return -1; } if (params->openssl_ciphers && wolfSSL_CTX_set_cipher_list(tls_ctx, params->openssl_ciphers) != 1) { wpa_printf(MSG_INFO, "wolfSSL: Failed to set cipher string '%s'", params->openssl_ciphers); return -1; } if (params->openssl_ecdh_curves) { wpa_printf(MSG_INFO, "wolfSSL: openssl_ecdh_curves not supported"); return -1; } #ifdef HAVE_SESSION_TICKET /* Session ticket is off by default - can't disable once on. */ if (!(params->flags & TLS_CONN_DISABLE_SESSION_TICKET)) wolfSSL_CTX_UseSessionTicket(tls_ctx); #endif /* HAVE_SESSION_TICKET */ #ifdef HAVE_OCSP if (params->ocsp_stapling_response) { wolfSSL_CTX_SetOCSP_OverrideURL(tls_ctx, params->ocsp_stapling_response); wolfSSL_CTX_SetOCSP_Cb(tls_ctx, ocsp_status_cb, ocsp_resp_free_cb, NULL); } #endif /* HAVE_OCSP */ return 0; } int tls_global_set_verify(void *tls_ctx, int check_crl, int strict) { wpa_printf(MSG_DEBUG, "SSL: global set verify: %d", check_crl); if (check_crl) { /* Hack to Enable CRLs. */ wolfSSL_CTX_LoadCRLBuffer(tls_ctx, NULL, 0, SSL_FILETYPE_PEM); } 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_context *context; if (!conn) return -1; wpa_printf(MSG_DEBUG, "SSL: set verify: %d", verify_peer); if (verify_peer) { conn->ca_cert_verify = 1; wolfSSL_set_verify(conn->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, tls_verify_cb); } else { conn->ca_cert_verify = 0; wolfSSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); } wolfSSL_set_accept_state(conn->ssl); context = wolfSSL_CTX_get_ex_data((WOLFSSL_CTX *) ssl_ctx, 0); if (context && context->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++; wolfSSL_set_session_id_context(conn->ssl, (const unsigned char *) &counter, sizeof(counter)); } else { wolfSSL_set_session_id_context(conn->ssl, session_ctx, session_ctx_len); } /* TODO: do we need to fake a session like OpenSSL does here? */ return 0; } static struct wpabuf * wolfssl_handshake(struct tls_connection *conn, const struct wpabuf *in_data, int server) { int res; wolfssl_reset_out_data(&conn->output); /* Initiate TLS handshake or continue the existing handshake */ if (server) { wolfSSL_set_accept_state(conn->ssl); res = wolfSSL_accept(conn->ssl); wpa_printf(MSG_DEBUG, "SSL: wolfSSL_accept: %d", res); } else { wolfSSL_set_connect_state(conn->ssl); res = wolfSSL_connect(conn->ssl); wpa_printf(MSG_DEBUG, "SSL: wolfSSL_connect: %d", res); } if (res != 1) { int err = wolfSSL_get_error(conn->ssl, res); if (err == SSL_ERROR_WANT_READ) { wpa_printf(MSG_DEBUG, "SSL: wolfSSL_connect - want more data"); } else if (err == SSL_ERROR_WANT_WRITE) { wpa_printf(MSG_DEBUG, "SSL: wolfSSL_connect - want to write"); } else { char msg[80]; wpa_printf(MSG_DEBUG, "SSL: wolfSSL_connect - failed %s", wolfSSL_ERR_error_string(err, msg)); conn->failed++; } } return conn->output.out_data; } static struct wpabuf * wolfssl_get_appl_data(struct tls_connection *conn, size_t max_len) { int res; struct wpabuf *appl_data = wpabuf_alloc(max_len + 100); if (!appl_data) return NULL; res = wolfSSL_read(conn->ssl, wpabuf_mhead(appl_data), wpabuf_size(appl_data)); if (res < 0) { int err = wolfSSL_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 { char msg[80]; wpa_printf(MSG_DEBUG, "Failed to read possible Application Data %s", wolfSSL_ERR_error_string(err, msg)); } 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 * wolfssl_connection_handshake(struct tls_connection *conn, const struct wpabuf *in_data, struct wpabuf **appl_data, int server) { struct wpabuf *out_data; wolfssl_reset_in_data(&conn->input, in_data); if (appl_data) *appl_data = NULL; out_data = wolfssl_handshake(conn, in_data, server); if (!out_data) return NULL; if (wolfSSL_is_init_finished(conn->ssl)) { wpa_printf(MSG_DEBUG, "wolfSSL: Handshake finished - resumed=%d", tls_connection_resumed(NULL, conn)); if (appl_data && in_data) *appl_data = wolfssl_get_appl_data(conn, wpabuf_len(in_data)); } return out_data; } struct wpabuf * tls_connection_handshake(void *tls_ctx, struct tls_connection *conn, const struct wpabuf *in_data, struct wpabuf **appl_data) { return wolfssl_connection_handshake(conn, in_data, appl_data, 0); } struct wpabuf * tls_connection_server_handshake(void *tls_ctx, struct tls_connection *conn, const struct wpabuf *in_data, struct wpabuf **appl_data) { return wolfssl_connection_handshake(conn, in_data, appl_data, 1); } struct wpabuf * tls_connection_encrypt(void *tls_ctx, struct tls_connection *conn, const struct wpabuf *in_data) { int res; if (!conn) return NULL; wpa_printf(MSG_DEBUG, "SSL: encrypt: %zu bytes", wpabuf_len(in_data)); wolfssl_reset_out_data(&conn->output); res = wolfSSL_write(conn->ssl, wpabuf_head(in_data), wpabuf_len(in_data)); if (res < 0) { int err = wolfSSL_get_error(conn->ssl, res); char msg[80]; wpa_printf(MSG_INFO, "Encryption failed - SSL_write: %s", wolfSSL_ERR_error_string(err, msg)); return NULL; } return conn->output.out_data; } struct wpabuf * tls_connection_decrypt(void *tls_ctx, struct tls_connection *conn, const struct wpabuf *in_data) { int res; struct wpabuf *buf; if (!conn) return NULL; wpa_printf(MSG_DEBUG, "SSL: decrypt"); wolfssl_reset_in_data(&conn->input, in_data); /* 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) return NULL; res = wolfSSL_read(conn->ssl, wpabuf_mhead(buf), wpabuf_size(buf)); if (res < 0) { wpa_printf(MSG_INFO, "Decryption failed - SSL_read"); wpabuf_free(buf); return NULL; } wpabuf_put(buf, res); wpa_printf(MSG_DEBUG, "SSL: decrypt: %zu bytes", wpabuf_len(buf)); return buf; } int tls_connection_resumed(void *tls_ctx, struct tls_connection *conn) { return conn ? wolfSSL_session_reused(conn->ssl) : 0; } int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, u8 *ciphers) { char buf[128], *pos, *end; u8 *c; int ret; if (!conn || !conn->ssl || !ciphers) 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++; } wpa_printf(MSG_DEBUG, "wolfSSL: cipher suites: %s", buf + 1); if (wolfSSL_set_cipher_list(conn->ssl, buf + 1) != 1) { wpa_printf(MSG_DEBUG, "Cipher suite configuration failed"); return -1; } return 0; } int tls_get_cipher(void *tls_ctx, struct tls_connection *conn, char *buf, size_t buflen) { WOLFSSL_CIPHER *cipher; const char *name; if (!conn || !conn->ssl) return -1; cipher = wolfSSL_get_current_cipher(conn->ssl); if (!cipher) return -1; name = wolfSSL_CIPHER_get_name(cipher); if (!name) return -1; if (os_strcmp(name, "SSL_RSA_WITH_RC4_128_SHA") == 0) os_strlcpy(buf, "RC4-SHA", buflen); else if (os_strcmp(name, "TLS_RSA_WITH_AES_128_CBC_SHA") == 0) os_strlcpy(buf, "AES128-SHA", buflen); else if (os_strcmp(name, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA") == 0) os_strlcpy(buf, "DHE-RSA-AES128-SHA", buflen); else if (os_strcmp(name, "TLS_DH_anon_WITH_AES_128_CBC_SHA") == 0) os_strlcpy(buf, "ADH-AES128-SHA", buflen); else if (os_strcmp(name, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA") == 0) os_strlcpy(buf, "DHE-RSA-AES256-SHA", buflen); else if (os_strcmp(name, "TLS_RSA_WITH_AES_256_CBC_SHA") == 0) os_strlcpy(buf, "AES256-SHA", buflen); else os_strlcpy(buf, name, buflen); return 0; } int tls_connection_enable_workaround(void *tls_ctx, struct tls_connection *conn) { /* no empty fragments in wolfSSL for now */ return 0; } int tls_connection_get_failed(void *tls_ctx, struct tls_connection *conn) { if (!conn) return -1; return conn->failed; } int tls_connection_get_read_alerts(void *tls_ctx, struct tls_connection *conn) { if (!conn) return -1; /* TODO: this is not incremented anywhere */ return conn->read_alerts; } int tls_connection_get_write_alerts(void *tls_ctx, struct tls_connection *conn) { if (!conn) return -1; /* TODO: this is not incremented anywhere */ return conn->write_alerts; } int tls_get_library_version(char *buf, size_t buf_len) { return os_snprintf(buf, buf_len, "wolfSSL build=%s run=%s", WOLFSSL_VERSION, wolfSSL_lib_version()); } int tls_get_version(void *ssl_ctx, struct tls_connection *conn, char *buf, size_t buflen) { const char *name; if (!conn || !conn->ssl) return -1; name = wolfSSL_get_version(conn->ssl); if (!name) return -1; os_strlcpy(buf, name, buflen); return 0; } int tls_connection_get_random(void *ssl_ctx, struct tls_connection *conn, struct tls_random *keys) { WOLFSSL *ssl; if (!conn || !keys) return -1; ssl = conn->ssl; if (!ssl) return -1; os_memset(keys, 0, sizeof(*keys)); keys->client_random = conn->client_random; keys->client_random_len = wolfSSL_get_client_random( ssl, conn->client_random, sizeof(conn->client_random)); keys->server_random = conn->server_random; keys->server_random_len = wolfSSL_get_server_random( ssl, conn->server_random, sizeof(conn->server_random)); return 0; } 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) return -1; #if LIBWOLFSSL_VERSION_HEX >= 0x04007000 if (wolfSSL_export_keying_material(conn->ssl, out, out_len, label, os_strlen(label), context, context_len, context != NULL) != WOLFSSL_SUCCESS) return -1; return 0; #else if (context || wolfSSL_make_eap_keys(conn->ssl, out, out_len, label) != 0) return -1; #endif return 0; } #define SEED_LEN (RAN_LEN + RAN_LEN) int tls_connection_get_eap_fast_key(void *tls_ctx, struct tls_connection *conn, u8 *out, size_t out_len) { byte seed[SEED_LEN]; int ret = -1; WOLFSSL *ssl; byte *tmp_out; byte *_out; int skip = 0; byte *master_key; unsigned int master_key_len; byte *server_random; unsigned int server_len; byte *client_random; unsigned int client_len; if (!conn || !conn->ssl) return -1; ssl = conn->ssl; skip = 2 * (wolfSSL_GetKeySize(ssl) + wolfSSL_GetHmacSize(ssl) + wolfSSL_GetIVSize(ssl)); tmp_out = os_malloc(skip + out_len); if (!tmp_out) return -1; _out = tmp_out; wolfSSL_get_keys(ssl, &master_key, &master_key_len, &server_random, &server_len, &client_random, &client_len); os_memcpy(seed, server_random, RAN_LEN); os_memcpy(seed + RAN_LEN, client_random, RAN_LEN); if (wolfSSL_GetVersion(ssl) == WOLFSSL_TLSV1_2) { tls_prf_sha256(master_key, master_key_len, "key expansion", seed, sizeof(seed), _out, skip + out_len); ret = 0; } else { #ifdef CONFIG_FIPS wpa_printf(MSG_ERROR, "wolfSSL: Can't use sha1_md5 in FIPS build"); ret = -1; #else /* CONFIG_FIPS */ ret = tls_prf_sha1_md5(master_key, master_key_len, "key expansion", seed, sizeof(seed), _out, skip + out_len); #endif /* CONFIG_FIPS */ } forced_memzero(master_key, master_key_len); if (ret == 0) os_memcpy(out, _out + skip, out_len); bin_clear_free(tmp_out, skip + out_len); return ret; } #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn, int ext_type, const u8 *data, size_t data_len) { (void) ssl_ctx; if (!conn || !conn->ssl || ext_type != 35) return -1; if (wolfSSL_set_SessionTicket(conn->ssl, data, (unsigned int) data_len) != 1) return -1; return 0; } static int tls_sess_sec_cb(WOLFSSL *s, void *secret, int *secret_len, void *arg) { struct tls_connection *conn = arg; int ret; unsigned char client_random[RAN_LEN]; unsigned char server_random[RAN_LEN]; word32 ticket_len = sizeof(conn->session_ticket); if (!conn || !conn->session_ticket_cb) return 1; if (wolfSSL_get_client_random(s, client_random, sizeof(client_random)) == 0 || wolfSSL_get_server_random(s, server_random, sizeof(server_random)) == 0 || wolfSSL_get_SessionTicket(s, conn->session_ticket, &ticket_len) != 1) return 1; if (ticket_len == 0) return 0; ret = conn->session_ticket_cb(conn->session_ticket_cb_ctx, conn->session_ticket, ticket_len, client_random, server_random, secret); if (ret <= 0) return 1; *secret_len = SECRET_LEN; return 0; } #endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ int tls_connection_set_session_ticket_cb(void *tls_ctx, struct tls_connection *conn, tls_session_ticket_cb cb, void *ctx) { #if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) conn->session_ticket_cb = cb; conn->session_ticket_cb_ctx = ctx; if (cb) { if (wolfSSL_set_session_secret_cb(conn->ssl, tls_sess_sec_cb, conn) != 1) return -1; } else { if (wolfSSL_set_session_secret_cb(conn->ssl, NULL, NULL) != 1) return -1; } return 0; #else /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ return -1; #endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ } void tls_connection_set_success_data_resumed(struct tls_connection *conn) { wpa_printf(MSG_DEBUG, "wolfSSL: Success data accepted for resumed session"); } void tls_connection_remove_session(struct tls_connection *conn) { WOLFSSL_SESSION *sess; sess = wolfSSL_get_session(conn->ssl); if (!sess) return; wolfSSL_SSL_SESSION_set_timeout(sess, 0); wpa_printf(MSG_DEBUG, "wolfSSL: 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 = wolfSSL_session_reused(conn->ssl); if ((wolfSSL_is_server(conn->ssl) && !reused) || (!wolfSSL_is_server(conn->ssl) && reused)) len = wolfSSL_get_peer_finished(conn->ssl, buf, max_len); else len = wolfSSL_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) { return (u16) wolfSSL_get_current_cipher_suite(conn->ssl); } const char * tls_connection_get_peer_subject(struct tls_connection *conn) { if (conn) return conn->peer_subject; return NULL; } void tls_connection_set_success_data(struct tls_connection *conn, struct wpabuf *data) { WOLFSSL_SESSION *sess; struct wpabuf *old; wpa_printf(MSG_DEBUG, "wolfSSL: Set success data"); sess = wolfSSL_get_session(conn->ssl); if (!sess) { wpa_printf(MSG_DEBUG, "wolfSSL: No session found for success data"); goto fail; } old = wolfSSL_SESSION_get_ex_data(sess, tls_ex_idx_session); if (old) { wpa_printf(MSG_DEBUG, "wolfSSL: Replacing old success data %p", old); wpabuf_free(old); } if (wolfSSL_SESSION_set_ex_data(sess, tls_ex_idx_session, data) != 1) goto fail; wpa_printf(MSG_DEBUG, "wolfSSL: Stored success data %p", data); conn->success_data = 1; return; fail: wpa_printf(MSG_INFO, "wolfSSL: Failed to store success data"); wpabuf_free(data); } const struct wpabuf * tls_connection_get_success_data(struct tls_connection *conn) { WOLFSSL_SESSION *sess; wpa_printf(MSG_DEBUG, "wolfSSL: Get success data"); sess = wolfSSL_get_session(conn->ssl); if (!sess) return NULL; return wolfSSL_SESSION_get_ex_data(sess, tls_ex_idx_session); } bool tls_connection_get_own_cert_used(struct tls_connection *conn) { if (conn) return wolfSSL_get_certificate(conn->ssl) != NULL; return false; }