diff options
Diffstat (limited to 'src/crypto/tls_openssl.c')
-rw-r--r-- | src/crypto/tls_openssl.c | 322 |
1 files changed, 303 insertions, 19 deletions
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index 99caa68..b0c23ae 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -219,6 +219,7 @@ struct tls_data { char *ca_cert; unsigned int crl_reload_interval; struct os_reltime crl_last_reload; + char *check_cert_subject; }; struct tls_connection { @@ -232,6 +233,7 @@ struct tls_connection { 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; @@ -329,8 +331,7 @@ static X509_STORE * tls_crl_cert_reload(const char *ca_cert, int check_crl) return NULL; } - if (check_crl) - flags = X509_V_FLAG_CRL_CHECK; + flags = check_crl ? X509_V_FLAG_CRL_CHECK : 0; if (check_crl == 2) flags |= X509_V_FLAG_CRL_CHECK_ALL; @@ -1135,6 +1136,7 @@ void tls_deinit(void *ssl_ctx) tls_global = NULL; } + os_free(data->check_cert_subject); os_free(data); } @@ -1347,8 +1349,16 @@ static const char * openssl_handshake_type(int content_type, const u8 *buf, 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: @@ -1367,6 +1377,12 @@ static const char * openssl_handshake_type(int content_type, const u8 *buf, 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 "?"; } @@ -1598,6 +1614,7 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) 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); } @@ -1718,9 +1735,9 @@ static int tls_match_altsubject(X509 *cert, const char *match) #ifndef CONFIG_NATIVE_WINDOWS static int domain_suffix_match(const u8 *val, size_t len, const char *match, - int full) + size_t match_len, int full) { - size_t i, match_len; + size_t i; /* Check for embedded nuls that could mess up suffix matching */ for (i = 0; i < len; i++) { @@ -1730,7 +1747,6 @@ static int domain_suffix_match(const u8 *val, size_t len, const char *match, } } - match_len = os_strlen(match); if (match_len > len || (full && match_len != len)) return 0; @@ -1750,12 +1766,223 @@ static int domain_suffix_match(const u8 *val, size_t len, const char *match, #endif /* CONFIG_NATIVE_WINDOWS */ -static int tls_match_suffix(X509 *cert, const char *match, int full) +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) { -#ifdef CONFIG_NATIVE_WINDOWS - /* wincrypt.h has conflicting X509_NAME definition */ - return -1; -#else /* CONFIG_NATIVE_WINDOWS */ GENERAL_NAME *gen; void *ext; int i; @@ -1777,8 +2004,8 @@ static int tls_match_suffix(X509 *cert, const char *match, int full) gen->d.dNSName->data, gen->d.dNSName->length); if (domain_suffix_match(gen->d.dNSName->data, - gen->d.dNSName->length, match, full) == - 1) { + 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); @@ -1809,8 +2036,8 @@ static int tls_match_suffix(X509 *cert, const char *match, int full) continue; wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", cn->data, cn->length); - if (domain_suffix_match(cn->data, cn->length, match, full) == 1) - { + 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; @@ -1820,6 +2047,25 @@ static int tls_match_suffix(X509 *cert, const char *match, int full) 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 */ } @@ -2014,6 +2260,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) 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); @@ -2114,6 +2361,18 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) "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); @@ -2490,7 +2749,8 @@ 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 *domain_match, + const char *check_cert_subject) { os_free(conn->subject_match); conn->subject_match = NULL; @@ -2524,6 +2784,14 @@ static int tls_connection_set_subject_match(struct tls_connection *conn, 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; } @@ -2867,7 +3135,8 @@ static int tls_connection_client_cert(struct tls_connection *conn, return 0; } -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#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" @@ -3656,11 +3925,13 @@ static int openssl_get_keyblock_size(SSL *ssl) int tls_connection_export_key(void *tls_ctx, struct tls_connection *conn, - const char *label, u8 *out, size_t out_len) + 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), NULL, 0, 0) != 1) + os_strlen(label), context, context_len, + context != NULL) != 1) return -1; return 0; } @@ -4578,7 +4849,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, params->subject_match, params->altsubject_match, params->suffix_match, - params->domain_match)) + params->domain_match, + params->check_cert_subject)) return -1; if (engine_id && ca_cert_id) { @@ -4719,6 +4991,15 @@ int tls_global_set_params(void *tls_ctx, __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, @@ -4756,6 +5037,9 @@ int tls_global_set_params(void *tls_ctx, return -1; #else /* OPENSSL_IS_BORINGSSL || < 1.0.2 */ #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) { |