From: Bob Beck Date: Sat, 20 Dec 2025 19:21:40 +0000 (-0700) Subject: Let's support multiple names for certificate verification X-Git-Tag: openssl-4.0.0-alpha1~195 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f584ae959cbc;p=thirdparty%2Fopenssl.git Let's support multiple names for certificate verification This adds the functionality to VERIFY_PARAM to separately add multiple ip's and email addresses for verification purposes. We then mark the unfortunate SSL_add1_host API which unfortunately aquired a confusing "maybe add an IP address" behaviour as deprecated. We replace this with SSL_set1_ and SSL_add1_ to set the things in the SSL corresponding to the VERIFY_PARAM funcitons. Fixes: https://github.com/openssl/openssl/issues/28418 Reviewed-by: Neil Horman Reviewed-by: Viktor Dukhovni MergeDate: Tue Feb 24 14:03:42 2026 (Merged from https://github.com/openssl/openssl/pull/29612) --- diff --git a/crypto/x509/v3_utl.c b/crypto/x509/v3_utl.c index ecb9e872d2f..09b445a64ce 100644 --- a/crypto/x509/v3_utl.c +++ b/crypto/x509/v3_utl.c @@ -867,7 +867,7 @@ static int do_check_string(const ASN1_STRING *a, int cmp_type, equal_fn equal, } static int do_x509_check(X509 *x, const char *chk, size_t chklen, - unsigned int flags, int check_type, char **peername) + unsigned int flags, int check_type, int check_nid, char **peername) { GENERAL_NAMES *gens = NULL; const X509_NAME *name = NULL; @@ -913,6 +913,8 @@ static int do_x509_check(X509 *x, const char *chk, size_t chklen, default: continue; case GEN_OTHERNAME: + if (check_type != GEN_OTHERNAME) + continue; switch (OBJ_obj2nid(gen->d.otherName->type_id)) { default: continue; @@ -942,7 +944,7 @@ static int do_x509_check(X509 *x, const char *chk, size_t chklen, * choose to turn it off, doing so is at this time a best * practice. */ - if (check_type != GEN_EMAIL + if (check_nid != NID_id_on_SmtpUTF8Mailbox || gen->d.otherName->value->type != V_ASN1_UTF8STRING) continue; alt_type = 0; @@ -1015,7 +1017,21 @@ int X509_check_host(X509 *x, const char *chk, size_t chklen, return -2; if (chklen > 1 && chk[chklen - 1] == '\0') --chklen; - return do_x509_check(x, chk, chklen, flags, GEN_DNS, peername); + return do_x509_check(x, chk, chklen, flags, GEN_DNS, 0, peername); +} + +int ossl_x509_check_rfc822(X509 *x, const char *chk, size_t chklen, + unsigned int flags) +{ + return do_x509_check(x, chk, chklen, flags, GEN_EMAIL, 0, NULL) == 1; +} + +int ossl_x509_check_smtputf8(X509 *x, const char *chk, size_t chklen, + unsigned int flags) +{ + return do_x509_check(x, chk, chklen, flags, GEN_OTHERNAME, + NID_id_on_SmtpUTF8Mailbox, NULL) + == 1; } int X509_check_email(X509 *x, const char *chk, size_t chklen, @@ -1034,7 +1050,14 @@ int X509_check_email(X509 *x, const char *chk, size_t chklen, return -2; if (chklen > 1 && chk[chklen - 1] == '\0') --chklen; - return do_x509_check(x, chk, chklen, flags, GEN_EMAIL, NULL); + /* + * As this is public API, historically it has supported checking + * whatever is supplied against both RFC822 and SMTPUTF8. + */ + if (do_x509_check(x, chk, chklen, flags, GEN_EMAIL, 0, NULL) == 1) + return 1; + return do_x509_check(x, chk, chklen, flags, GEN_OTHERNAME, + NID_id_on_SmtpUTF8Mailbox, NULL); } int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, @@ -1042,7 +1065,7 @@ int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, { if (chk == NULL) return -2; - return do_x509_check(x, (char *)chk, chklen, flags, GEN_IPADD, NULL); + return do_x509_check(x, (char *)chk, chklen, flags, GEN_IPADD, 0, NULL); } int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags) @@ -1055,7 +1078,7 @@ int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags) iplen = (size_t)ossl_a2i_ipadd(ipout, ipasc); if (iplen == 0) return -2; - return do_x509_check(x, (char *)ipout, iplen, flags, GEN_IPADD, NULL); + return do_x509_check(x, (char *)ipout, iplen, flags, GEN_IPADD, 0, NULL); } char *ossl_ipaddr_to_asc(unsigned char *p, int len) diff --git a/crypto/x509/x509_local.h b/crypto/x509/x509_local.h index 58f2d9f79b7..a2c3a6ca861 100644 --- a/crypto/x509/x509_local.h +++ b/crypto/x509/x509_local.h @@ -6,6 +6,7 @@ * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ +#include #include "internal/refcount.h" #include "internal/hashtable.h" @@ -13,12 +14,22 @@ #define X509V3_conf_add_error_name_value(val) \ ERR_add_error_data(4, "name=", (val)->name, ", value=", (val)->value) +/* + * Really all I want is CRYPTO_BUFFER from BoringSSL, but let's just do this + * for now. + */ +typedef struct ossl_x509_buffer_st { + const uint8_t *data; + size_t len; +} X509_BUFFER; + +DEFINE_STACK_OF(X509_BUFFER) + /* * This structure holds all parameters associated with a verify operation by * including an X509_VERIFY_PARAM structure in related structures the * parameters used can be customized */ - struct X509_VERIFY_PARAM_st { char *name; int64_t check_time; /* Time to use */ @@ -30,13 +41,16 @@ struct X509_VERIFY_PARAM_st { int auth_level; /* Security level for chain verification */ STACK_OF(ASN1_OBJECT) *policies; /* Permissible policies */ /* Peer identity details */ - STACK_OF(OPENSSL_STRING) *hosts; /* Set of acceptable names */ + STACK_OF(X509_BUFFER) *hosts; /* Set of acceptable names */ + int (*validate_host)(const char *name, size_t len); + STACK_OF(X509_BUFFER) *ips; /* Set of acceptable ip addresses */ + int (*validate_ip)(const uint8_t *name, size_t len); + STACK_OF(X509_BUFFER) *rfc822s; /* Set of acceptable RFC 822 names */ + int (*validate_rfc822)(const char *name, size_t len); + STACK_OF(X509_BUFFER) *smtputf8s; /* Set of acceptable SMTP Utf8 names */ + int (*validate_smtputf8)(const char *name, size_t len); unsigned int hostflags; /* Flags to control matching features */ char *peername; /* Matching hostname in peer certificate */ - char *email; /* If not NULL email address to match */ - size_t emaillen; - unsigned char *ip; /* If not NULL IP address to match */ - size_t iplen; /* Length of IP address */ }; /* No error callback if depth < 0 */ @@ -173,3 +187,7 @@ int ossl_x509_store_ctx_get_by_subject(const X509_STORE_CTX *ctx, X509_LOOKUP_TY __owur int ossl_x509_store_read_lock(X509_STORE *xs); STACK_OF(X509_OBJECT) *ossl_x509_store_ht_get_by_name(const X509_STORE *store, const X509_NAME *xn); +int ossl_x509_check_rfc822(X509 *x, const char *chk, size_t chklen, + unsigned int flags); +int ossl_x509_check_smtputf8(X509 *x, const char *chk, size_t chklen, + unsigned int flags); diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index 1fbc766891b..c2467aaea6a 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c @@ -940,16 +940,60 @@ static int check_id_error(X509_STORE_CTX *ctx, int errcode) static int check_hosts(X509 *x, X509_VERIFY_PARAM *vpm) { int i; - int n = sk_OPENSSL_STRING_num(vpm->hosts); - char *name; + int n = sk_X509_BUFFER_num(vpm->hosts); + const uint8_t *name; if (vpm->peername != NULL) { OPENSSL_free(vpm->peername); vpm->peername = NULL; } for (i = 0; i < n; ++i) { - name = sk_OPENSSL_STRING_value(vpm->hosts, i); - if (X509_check_host(x, name, 0, vpm->hostflags, &vpm->peername) > 0) + size_t len = sk_X509_BUFFER_value(vpm->hosts, i)->len; + name = sk_X509_BUFFER_value(vpm->hosts, i)->data; + if (X509_check_host(x, (const char *)name, len, vpm->hostflags, &vpm->peername) > 0) + return 1; + } + return n == 0; +} + +static int check_email(X509 *x, X509_VERIFY_PARAM *vpm) +{ + int i, n, j; + const uint8_t *name; + + if (vpm->rfc822s == NULL) + return 1; + + n = sk_X509_BUFFER_num(vpm->rfc822s); + + for (i = 0; i < n; ++i) { + size_t len = sk_X509_BUFFER_value(vpm->rfc822s, i)->len; + name = sk_X509_BUFFER_value(vpm->rfc822s, i)->data; + if (ossl_x509_check_rfc822(x, (const char *)name, len, vpm->hostflags)) + return 1; + } + + j = sk_X509_BUFFER_num(vpm->smtputf8s); + for (i = 0; i < j; ++i) { + size_t len = sk_X509_BUFFER_value(vpm->smtputf8s, i)->len; + name = sk_X509_BUFFER_value(vpm->smtputf8s, i)->data; + if (ossl_x509_check_smtputf8(x, (const char *)name, len, vpm->hostflags)) + return 1; + } + + return n == 0 && i == 0; +} + +static int check_ips(X509 *x, X509_VERIFY_PARAM *vpm) +{ + int i; + int n = sk_X509_BUFFER_num(vpm->ips); + const uint8_t *name; + + for (i = 0; i < n; ++i) { + size_t len = sk_X509_BUFFER_value(vpm->ips, i)->len; + name = sk_X509_BUFFER_value(vpm->ips, i)->data; + if (X509_check_ip(x, name, len, vpm->hostflags) > 0) return 1; } return n == 0; @@ -964,12 +1008,13 @@ static int check_id(X509_STORE_CTX *ctx) if (!check_id_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH)) return 0; } - if (vpm->email != NULL - && X509_check_email(x, vpm->email, vpm->emaillen, 0) <= 0) { + + if (!check_email(x, vpm)) { if (!check_id_error(ctx, X509_V_ERR_EMAIL_MISMATCH)) return 0; } - if (vpm->ip != NULL && X509_check_ip(x, vpm->ip, vpm->iplen, 0) <= 0) { + + if (vpm->ips != NULL && check_ips(x, vpm) <= 0) { if (!check_id_error(ctx, X509_V_ERR_IP_ADDRESS_MISMATCH)) return 0; } diff --git a/crypto/x509/x509_vpm.c b/crypto/x509/x509_vpm.c index dc214409af2..2f820a1be18 100644 --- a/crypto/x509/x509_vpm.c +++ b/crypto/x509/x509_vpm.c @@ -14,70 +14,348 @@ #include #include #include + +#include "crypto/ctype.h" #include "crypto/x509.h" #include "x509_local.h" +typedef enum { + OSSL_CHARSET_NONASCII, + OSSL_CHARSET_ASCII, + OSSL_CHARSET_ASCII_ALNUM, +} ossl_charset_t; + /* X509_VERIFY_PARAM functions */ #define SET_HOST 0 #define ADD_HOST 1 -static char *str_copy(const char *s) +static X509_BUFFER *buffer_from_bytes(const uint8_t *bytes, size_t length) { - return OPENSSL_strdup(s); + X509_BUFFER *buf; + + if ((buf = OPENSSL_zalloc(sizeof *buf)) != NULL + && (buf->data = OPENSSL_memdup(bytes, length)) != NULL) + buf->len = length; + else + OPENSSL_free(buf); + return buf; } -static void str_free(char *s) +/* + * Copies |length| bytes from |bytes| to a new buffer, making the data + * A C string. It is an error for the |bytes| to contain any \0 values + * within |length|. |bytes| need not itself be \0 terminated, the data + * in the buffer will be on success. + */ +static X509_BUFFER *buffer_from_string(const uint8_t *bytes, size_t length) { - OPENSSL_free(s); + X509_BUFFER *buf, *ret = NULL; + uint8_t *data = NULL; + + if ((buf = OPENSSL_zalloc(sizeof *buf)) == NULL) + goto err; + + if ((data = (uint8_t *)OPENSSL_strndup((char *)bytes, length)) == NULL) + goto err; + + if (strlen((char *)data) != length) + goto err; + + ret = buf; + buf = NULL; + ret->data = data; + ret->len = length; + data = NULL; + +err: + OPENSSL_free(buf); + OPENSSL_free(data); + + return ret; } -static int int_x509_param_set_hosts(X509_VERIFY_PARAM *vpm, int mode, - const char *name, size_t namelen) +static X509_BUFFER *buffer_copy(const X509_BUFFER *b) { - char *copy; + return buffer_from_bytes(b->data, b->len); +} + +static void buffer_free(X509_BUFFER *b) +{ + if (b == NULL) + return; + OPENSSL_free((void *)b->data); + OPENSSL_free(b); +} + +static int replace_buffer_stack(STACK_OF(X509_BUFFER) **dest, + STACK_OF(X509_BUFFER) *const *src) +{ + sk_X509_BUFFER_pop_free(*dest, buffer_free); + *dest = NULL; + if (*src != NULL) { + *dest = sk_X509_BUFFER_deep_copy(*src, buffer_copy, buffer_free); + if (*dest == NULL) + return 0; + } + return 1; +} + +static int buffer_cmp(const X509_BUFFER *const *a, const X509_BUFFER *const *b) +{ + if ((*a)->len < (*b)->len) + return -1; + if ((*a)->len > (*b)->len) + return 1; + return memcmp((*a)->data, (*b)->data, (*b)->len); +} + +static void clear_buffer_stack(STACK_OF(X509_BUFFER) **buffer_stack) +{ + sk_X509_BUFFER_pop_free(*buffer_stack, buffer_free); + *buffer_stack = NULL; +} + +static int add_bytes_to_buffer_stack(STACK_OF(X509_BUFFER) **buffer_stack, + const uint8_t *name, size_t name_len) +{ + STACK_OF(X509_BUFFER) *tmp_stack = NULL; + X509_BUFFER *copy = NULL; + int ret = 0; + + if ((copy = buffer_from_bytes(name, name_len)) == NULL) + goto err; + + tmp_stack = *buffer_stack; + if (tmp_stack == NULL && (tmp_stack = sk_X509_BUFFER_new(buffer_cmp)) == NULL) + goto err; + + if (!sk_X509_BUFFER_push(tmp_stack, copy)) + goto err; + + ret = 1; + copy = NULL; + *buffer_stack = tmp_stack; + tmp_stack = NULL; + +err: + sk_X509_BUFFER_pop_free(tmp_stack, buffer_free); + buffer_free(copy); + + return ret; +} + +static int add_string_to_buffer_stack(STACK_OF(X509_BUFFER) **buffer_stack, + const uint8_t *name, size_t name_len) +{ + STACK_OF(X509_BUFFER) *tmp_stack = NULL; + X509_BUFFER *copy = NULL; + int ret = 0; + + if ((copy = buffer_from_string(name, name_len)) == NULL) + goto err; + + tmp_stack = *buffer_stack; + if (tmp_stack == NULL && (tmp_stack = sk_X509_BUFFER_new(buffer_cmp)) == NULL) + goto err; + + if (!sk_X509_BUFFER_push(tmp_stack, copy)) + goto err; + + ret = 1; + copy = NULL; + *buffer_stack = tmp_stack; + tmp_stack = NULL; + +err: + sk_X509_BUFFER_pop_free(tmp_stack, buffer_free); + buffer_free(copy); + + return ret; +} + +static int validate_string_name(const char *name, size_t *name_len) +{ + size_t len = *name_len; + + if (name == NULL || len == 0) + return 0; /* - * Refuse names with embedded NUL bytes, except perhaps as final byte. - * XXX: Do we need to push an error onto the error stack? + * Accept the trailing \0 byte if this is a C string. This is to + * preserver behaviour that is traditional for the + * set1_[host|email] functions. */ - if (namelen == 0 || name == NULL) - namelen = name ? strlen(name) : 0; - else if (name != NULL - && memchr(name, '\0', namelen > 1 ? namelen - 1 : namelen) != NULL) + if (name[len - 1] == '\0') + len--; + + /* Refuse the empty string */ + if (len == 0) return 0; - if (namelen > 0 && name[namelen - 1] == '\0') - --namelen; - if (mode == SET_HOST) { - sk_OPENSSL_STRING_pop_free(vpm->hosts, str_free); - vpm->hosts = NULL; - } - if (name == NULL || namelen == 0) + /* Refuse values with embedded \0 bytes other than at the end */ + if (memchr(name, '\0', len) != NULL) + return 0; + + return 1; +} + +/* + * Default input validation for verification parameter names. As these + * could potentially come from untrusted input, doing basic input + * validation makes sense, and ensures that subsequent parsing or + * comparisons do not need to handle extreme out of range input. + */ + +/* Default ip name input validation */ +static int validate_ip_name(const uint8_t *name, size_t len) +{ + if (name != NULL && (len == 4 || len == 16)) return 1; + return 0; +} - copy = OPENSSL_strndup(name, namelen); - if (copy == NULL) +static ossl_charset_t ossl_name_charset(int c, ossl_charset_t charset) +{ + if (ossl_isalnum(c)) + return 1; + if (ossl_isascii(c)) + return charset == OSSL_CHARSET_ASCII + || charset == OSSL_CHARSET_NONASCII; + return charset == OSSL_CHARSET_NONASCII; +} + +/* + * Check for allowed characters in a dns name label. + * |charset| controls the strictness of the checking. + * + * if |charset|is OSSL_CHARSET_NONASCII, anything is allowed + * except the forbidden characters of '.' and '-'. This + * will make minimally valid structure be checked but nothing + * else. + * + * if |charset| is OSSL_CHARSET_ASCII all ascii characters + * are allowed except the forbidden characters of '.' and '-'. + * + * if |charset| is OSSL_CHARSET_ASCII_ALNUM all alphanumeric + * characters plus the character '_' are allowed except the forbidden + * characters of '.' and '-'. + */ +static int is_label_ok(int c, ossl_charset_t charset) +{ + if (!ossl_name_charset(c, charset) && c != '_') return 0; + else + return c != '.' && c != '-'; +} + +/* Default host name input validation */ +static int validate_hostname_part(const char *name, size_t len, + ossl_charset_t charset) +{ + size_t i, part_len; + char c, prev; - if (vpm->hosts == NULL && (vpm->hosts = sk_OPENSSL_STRING_new_null()) == NULL) { - OPENSSL_free(copy); + if (len < 2 || len > 256) return 0; - } - if (!sk_OPENSSL_STRING_push(vpm->hosts, copy)) { - OPENSSL_free(copy); - if (sk_OPENSSL_STRING_num(vpm->hosts) == 0) { - sk_OPENSSL_STRING_free(vpm->hosts); - vpm->hosts = NULL; + part_len = 0; + prev = '\0'; + for (i = 0; i < len; i++) { + c = name[i]; + if (c == '.') { + /* Can not start a label with a . */ + if (part_len == 0) + return 0; + /* Can not end a label with a - */ + if (prev == '-') + return 0; + part_len = 0; + } else { + if (!is_label_ok(c, charset) && c != '-') + return 0; + if (c == '-') { + /* Can not start a label with a - */ + if (part_len == 0) + return 0; + } } + part_len++; + if (part_len > 63) + return 0; + + prev = c; + } + /* Can not end with a . or a _ */ + if (prev == '.' || prev == '-') return 0; + + return 1; +} + +static int validate_local_part(const char *name, size_t len, + ossl_charset_t *out_charset) +{ + ossl_charset_t charset = OSSL_CHARSET_ASCII; + size_t i; + + for (i = 0; i < len; i++) { + if (name[i] == '\0') + return 0; + if (!ossl_isascii(name[i])) + charset = OSSL_CHARSET_NONASCII; } + *out_charset = charset; return 1; } +/* Default email name input validation */ +static int validate_email_name(const char *name, size_t len, int rfc822) +{ + size_t dns_len, local_len; + char *at, *dnsname; + ossl_charset_t local_charset; + + /* + * 64 for local part, 1 for @, 255 for domain name + */ + if (len > 320) + return 0; + + /* Reject it if there is no @ */ + if ((at = memchr(name, '@', len)) == NULL) + return 0; + + /* Ensure the local part is not oversize */ + local_len = len - (at - name); + if (local_len > 64) + return 0; + + if (!validate_local_part(name, len, &local_charset)) + return 0; + + if (rfc822 && local_charset == OSSL_CHARSET_NONASCII) + return 0; + + if (!rfc822 && local_charset == OSSL_CHARSET_ASCII) + return 0; + + /* What is after the @ must be valid as a dns name */ + dnsname = at + 1; + dns_len = len - local_len - 1; + + /* It may not have another @ */ + if ((at = memchr(dnsname, '@', dns_len)) != NULL) + return 0; + + if (rfc822) + return validate_hostname_part(dnsname, dns_len, OSSL_CHARSET_ASCII_ALNUM); + + return validate_hostname_part(dnsname, dns_len, OSSL_CHARSET_NONASCII); +} + X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void) { X509_VERIFY_PARAM *param; @@ -97,10 +375,11 @@ void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *param) if (param == NULL) return; sk_ASN1_OBJECT_pop_free(param->policies, ASN1_OBJECT_free); - sk_OPENSSL_STRING_pop_free(param->hosts, str_free); + clear_buffer_stack(¶m->hosts); + clear_buffer_stack(¶m->ips); + clear_buffer_stack(¶m->rfc822s); + clear_buffer_stack(¶m->smtputf8s); OPENSSL_free(param->peername); - OPENSSL_free(param->email); - OPENSSL_free(param->ip); OPENSSL_free(param); } @@ -192,24 +471,28 @@ int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *dest, x509_verify_param_copy(hostflags, 0); if (test_x509_verify_param_copy(hosts, NULL)) { - sk_OPENSSL_STRING_pop_free(dest->hosts, str_free); - dest->hosts = NULL; - if (src->hosts != NULL) { - dest->hosts = sk_OPENSSL_STRING_deep_copy(src->hosts, str_copy, str_free); - if (dest->hosts == NULL) - return 0; - } + if (!replace_buffer_stack(&dest->hosts, &src->hosts)) + return 0; + } + x509_verify_param_copy(validate_host, NULL); + + if (test_x509_verify_param_copy(ips, NULL)) { + if (!replace_buffer_stack(&dest->ips, &src->ips)) + return 0; } + x509_verify_param_copy(validate_ip, NULL); - if (test_x509_verify_param_copy(email, NULL)) { - if (!X509_VERIFY_PARAM_set1_email(dest, src->email, src->emaillen)) + if (test_x509_verify_param_copy(rfc822s, NULL)) { + if (!replace_buffer_stack(&dest->rfc822s, &src->rfc822s)) return 0; } + x509_verify_param_copy(validate_rfc822, NULL); - if (test_x509_verify_param_copy(ip, NULL)) { - if (!X509_VERIFY_PARAM_set1_ip(dest, src->ip, src->iplen)) + if (test_x509_verify_param_copy(smtputf8s, NULL)) { + if (!replace_buffer_stack(&dest->smtputf8s, &src->smtputf8s)) return 0; } + x509_verify_param_copy(validate_smtputf8, NULL); return 1; } @@ -231,31 +514,6 @@ int X509_VERIFY_PARAM_set1(X509_VERIFY_PARAM *to, return ret; } -static int int_x509_param_set1(char **pdest, size_t *pdestlen, - const char *src, size_t srclen) -{ - char *tmp; - - if (src != NULL) { - if (srclen == 0) - srclen = strlen(src); - - tmp = OPENSSL_malloc(srclen + 1); - if (tmp == NULL) - return 0; - memcpy(tmp, src, srclen); - tmp[srclen] = '\0'; /* enforce NUL termination */ - } else { - tmp = NULL; - srclen = 0; - } - OPENSSL_free(*pdest); - *pdest = tmp; - if (pdestlen != NULL) - *pdestlen = srclen; - return 1; -} - int X509_VERIFY_PARAM_set1_name(X509_VERIFY_PARAM *param, const char *name) { OPENSSL_free(param->name); @@ -389,19 +647,169 @@ int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *param, char *X509_VERIFY_PARAM_get0_host(X509_VERIFY_PARAM *param, int idx) { - return sk_OPENSSL_STRING_value(param->hosts, idx); + X509_BUFFER *buf = sk_X509_BUFFER_value(param->hosts, idx); + + return (buf != NULL) ? (char *)buf->data : NULL; } int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, - const char *name, size_t namelen) + const char *dnsname, size_t len) { - return int_x509_param_set_hosts(param, SET_HOST, name, namelen); + clear_buffer_stack(¶m->hosts); + if (dnsname == NULL) + return 1; + if (len == 0) + len = strlen(dnsname); + if (len == 0) + return 1; + return X509_VERIFY_PARAM_add1_host(param, dnsname, len); } int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, - const char *name, size_t namelen) + const char *dnsname, size_t len) +{ + if (dnsname == NULL) + return 1; + if (len == 0) + len = strlen(dnsname); + if (len == 0) + return 1; + if (!validate_string_name(dnsname, &len)) + return 0; + if (param->validate_host != NULL) { + if (!param->validate_host(dnsname, len)) + return 0; + } else { + if (!validate_hostname_part(dnsname, len, OSSL_CHARSET_ASCII_ALNUM)) + return 0; + } + return add_string_to_buffer_stack(¶m->hosts, (const uint8_t *)dnsname, len); +} + +void X509_VERIFY_PARAM_set1_host_input_validation(X509_VERIFY_PARAM *param, + int (*validate_host)(const char *name, size_t len)) +{ + param->validate_host = validate_host; +} + +int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, + const uint8_t *ip, size_t len) +{ + clear_buffer_stack(¶m->ips); + if (ip == NULL) + return 1; + return X509_VERIFY_PARAM_add1_ip(param, ip, len); +} + +int X509_VERIFY_PARAM_add1_ip(X509_VERIFY_PARAM *param, + const uint8_t *ip, size_t len) +{ + if (param->validate_ip != NULL) { + if (!param->validate_ip(ip, len)) + return 0; + } else { + if (!validate_ip_name(ip, len)) + return 0; + } + return add_bytes_to_buffer_stack(¶m->ips, ip, len); +} + +void X509_VERIFY_PARAM_set1_ip_input_validation(X509_VERIFY_PARAM *param, + int (*validate_ip)(const uint8_t *name, size_t len)) +{ + param->validate_ip = validate_ip; +} + +char *X509_VERIFY_PARAM_get0_email(X509_VERIFY_PARAM *param) +{ + X509_BUFFER *buf = sk_X509_BUFFER_value(param->rfc822s, 0); + + if ((buf = sk_X509_BUFFER_value(param->rfc822s, 0)) != NULL + || (buf = sk_X509_BUFFER_value(param->smtputf8s, 0)) != NULL) + return (char *)buf->data; + + return NULL; +} + +int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, + const char *email, size_t len) +{ + int ret = 0; + + if (X509_VERIFY_PARAM_set1_smtputf8(param, email, len)) + ret = 1; + if (X509_VERIFY_PARAM_set1_rfc822(param, email, len)) + ret = 1; + + return ret; +} + +int X509_VERIFY_PARAM_set1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t len) +{ + clear_buffer_stack(¶m->smtputf8s); + if (email == NULL) + return 1; + return X509_VERIFY_PARAM_add1_smtputf8(param, email, len); +} + +int X509_VERIFY_PARAM_add1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t len) +{ + if (len == 0) + len = strlen(email); + if (!validate_string_name(email, &len)) + return 0; + if (param->validate_smtputf8 != NULL) { + if (!param->validate_smtputf8(email, len)) + return 0; + } else { + if (!validate_email_name(email, len, /*rfc822 =*/0)) + return 0; + } + + return add_string_to_buffer_stack(¶m->smtputf8s, + (const uint8_t *)email, len); +} + +void X509_VERIFY_PARAM_set1_smtputf8_input_validation(X509_VERIFY_PARAM *param, + int (*validate_smtputf8)(const char *name, size_t len)) +{ + param->validate_smtputf8 = validate_smtputf8; +} + +int X509_VERIFY_PARAM_set1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t len) +{ + clear_buffer_stack(¶m->rfc822s); + if (email == NULL) + return 1; + return X509_VERIFY_PARAM_add1_rfc822(param, email, len); +} + +int X509_VERIFY_PARAM_add1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t len) +{ + if (len == 0) + len = strlen(email); + if (!validate_string_name(email, &len)) + return 0; + if (param->validate_rfc822 != NULL) { + if (!param->validate_rfc822(email, len)) + return 0; + } else { + if (!validate_email_name(email, len, /*rfc822 =*/1)) + return 0; + } + + return add_string_to_buffer_stack(¶m->rfc822s, + (const uint8_t *)email, len); +} + +void X509_VERIFY_PARAM_set1_rfc822_input_validation(X509_VERIFY_PARAM *param, + int (*validate_rfc822)(const char *name, size_t len)) { - return int_x509_param_set_hosts(param, ADD_HOST, name, namelen); + param->validate_rfc822 = validate_rfc822; } void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, @@ -438,56 +846,57 @@ void X509_VERIFY_PARAM_move_peername(X509_VERIFY_PARAM *to, from->peername = NULL; } -char *X509_VERIFY_PARAM_get0_email(X509_VERIFY_PARAM *param) +static const unsigned char *int_X509_VERIFY_PARAM_get0_ip(X509_VERIFY_PARAM *param, size_t *plen, size_t idx) { - return param->email; -} + X509_BUFFER *buf; -int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, - const char *email, size_t emaillen) -{ - return int_x509_param_set1(¶m->email, ¶m->emaillen, - email, emaillen); -} + if (idx > INT_MAX) + return NULL; -static unsigned char *int_X509_VERIFY_PARAM_get0_ip(X509_VERIFY_PARAM *param, size_t *plen) -{ - if (param == NULL || param->ip == NULL) { + buf = sk_X509_BUFFER_value(param->ips, (int)idx); + + if (param == NULL || param->ips == NULL) { ERR_raise(ERR_LIB_X509, ERR_R_PASSED_NULL_PARAMETER); return NULL; } - if (plen != NULL) - *plen = param->iplen; - return param->ip; + + if (buf != NULL) { + if (plen != NULL) + *plen = buf->len; + return (unsigned char *)buf->data; + } + return NULL; } char *X509_VERIFY_PARAM_get1_ip_asc(X509_VERIFY_PARAM *param) { size_t iplen; - unsigned char *ip = int_X509_VERIFY_PARAM_get0_ip(param, &iplen); + /* XXX casts away const */ + unsigned char *ip = (unsigned char *)int_X509_VERIFY_PARAM_get0_ip(param, &iplen, 0); return ip == NULL ? NULL : ossl_ipaddr_to_asc(ip, (int)iplen); } -int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, - const unsigned char *ip, size_t iplen) +int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc) { - if (iplen != 0 && iplen != 4 && iplen != 16) { - ERR_raise(ERR_LIB_X509, ERR_R_PASSED_INVALID_ARGUMENT); + unsigned char ipout[16]; + size_t iplen; + + if (ipasc == NULL) + return X509_VERIFY_PARAM_set1_ip(param, NULL, 0); + if ((iplen = (size_t)ossl_a2i_ipadd(ipout, ipasc)) == 0) return 0; - } - return int_x509_param_set1((char **)¶m->ip, ¶m->iplen, - (char *)ip, iplen); + return X509_VERIFY_PARAM_set1_ip(param, ipout, iplen); } -int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc) +int X509_VERIFY_PARAM_add1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc) { unsigned char ipout[16]; - size_t iplen = (size_t)ossl_a2i_ipadd(ipout, ipasc); + size_t iplen; - if (iplen == 0) + if ((iplen = (size_t)ossl_a2i_ipadd(ipout, ipasc)) == 0) return 0; - return X509_VERIFY_PARAM_set1_ip(param, ipout, iplen); + return X509_VERIFY_PARAM_add1_ip(param, ipout, iplen); } int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *param) @@ -505,8 +914,6 @@ const char *X509_VERIFY_PARAM_get0_name(const X509_VERIFY_PARAM *param) return param->name; } -#define vpm_empty_id NULL, 0U, NULL, NULL, 0, NULL, 0 - /* * Default verify parameters: these are used for various applications and can * be overridden by the user specified table. NB: the 'name' field *must* be @@ -514,66 +921,47 @@ const char *X509_VERIFY_PARAM_get0_name(const X509_VERIFY_PARAM *param) */ static const X509_VERIFY_PARAM default_table[] = { - { "code_sign", /* Code sign parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - 0, /* flags */ - X509_PURPOSE_CODE_SIGN, /* purpose */ - X509_TRUST_OBJECT_SIGN, /* trust */ - -1, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id }, - { "default", /* X509 default parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - X509_V_FLAG_TRUSTED_FIRST, /* flags */ - 0, /* purpose */ - 0, /* trust */ - 100, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id }, - { "pkcs7", /* S/MIME sign parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - 0, /* flags */ - X509_PURPOSE_SMIME_SIGN, /* purpose */ - X509_TRUST_EMAIL, /* trust */ - -1, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id }, - { "smime_sign", /* S/MIME sign parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - 0, /* flags */ - X509_PURPOSE_SMIME_SIGN, /* purpose */ - X509_TRUST_EMAIL, /* trust */ - -1, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id }, - { "ssl_client", /* SSL/TLS client parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - 0, /* flags */ - X509_PURPOSE_SSL_CLIENT, /* purpose */ - X509_TRUST_SSL_CLIENT, /* trust */ - -1, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id }, - { "ssl_server", /* SSL/TLS server parameters */ - 0, /* check time to use */ - 0, /* inheritance flags */ - 0, /* flags */ - X509_PURPOSE_SSL_SERVER, /* purpose */ - X509_TRUST_SSL_SERVER, /* trust */ - -1, /* depth */ - -1, /* auth_level */ - NULL, /* policies */ - vpm_empty_id } + { + .name = "code_sign", /* Code sign parameters */ + .purpose = X509_PURPOSE_CODE_SIGN, + .trust = X509_TRUST_OBJECT_SIGN, + .depth = -1, + .auth_level = -1, + }, + { + .name = "default", /* X509 default parameters */ + .flags = X509_V_FLAG_TRUSTED_FIRST, + .depth = 100, + .auth_level = -1, + }, + { + .name = "pkcs7", /* S/MIME sign parameters */ + .purpose = X509_PURPOSE_SMIME_SIGN, + .trust = X509_TRUST_EMAIL, + .depth = -1, + .auth_level = -1, + }, + { + .name = "smime_sign", /* S/MIME sign parameters */ + .purpose = X509_PURPOSE_SMIME_SIGN, + .trust = X509_TRUST_EMAIL, + .depth = -1, + .auth_level = -1, + }, + { + .name = "ssl_client", /* SSL/TLS client parameters */ + .purpose = X509_PURPOSE_SSL_CLIENT, + .trust = X509_TRUST_SSL_CLIENT, + .depth = -1, + .auth_level = -1, + }, + { + .name = "ssl_server", /* SSL/TLS server parameters */ + .purpose = X509_PURPOSE_SSL_SERVER, + .trust = X509_TRUST_SSL_SERVER, + .depth = -1, + .auth_level = -1, + } }; static STACK_OF(X509_VERIFY_PARAM) *param_table = NULL; diff --git a/demos/bio/sconnect.c b/demos/bio/sconnect.c index e069209e726..a3b3eabfc86 100644 --- a/demos/bio/sconnect.c +++ b/demos/bio/sconnect.c @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) /* The BIO has parsed the host:port and even IPv6 literals in [] */ hostname = BIO_get_conn_hostname(out); - if (!hostname || SSL_set1_host(ssl, hostname) <= 0) { + if (!hostname || SSL_set1_dnsname(ssl, hostname) <= 0) { BIO_free(ssl_bio); goto err; } diff --git a/demos/guide/quic-client-block.c b/demos/guide/quic-client-block.c index 80eccbab847..a2208f5e11d 100644 --- a/demos/guide/quic-client-block.c +++ b/demos/guide/quic-client-block.c @@ -204,7 +204,7 @@ int main(int argc, char *argv[]) * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(ssl, hostname)) { + if (!SSL_set1_dnsname(ssl, hostname)) { printf("Failed to set the certificate verification hostname"); goto end; } diff --git a/demos/guide/quic-client-non-block.c b/demos/guide/quic-client-non-block.c index 9e8760dde24..a38fdf411e0 100644 --- a/demos/guide/quic-client-non-block.c +++ b/demos/guide/quic-client-non-block.c @@ -313,7 +313,7 @@ int main(int argc, char *argv[]) * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(ssl, hostname)) { + if (!SSL_set1_dnsname(ssl, hostname)) { printf("Failed to set the certificate verification hostname"); goto end; } diff --git a/demos/guide/quic-multi-stream.c b/demos/guide/quic-multi-stream.c index d08ab4dac19..7e6777d6a43 100644 --- a/demos/guide/quic-multi-stream.c +++ b/demos/guide/quic-multi-stream.c @@ -231,7 +231,7 @@ int main(int argc, char *argv[]) * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(ssl, hostname)) { + if (!SSL_set1_dnsname(ssl, hostname)) { printf("Failed to set the certificate verification hostname"); goto end; } diff --git a/demos/guide/tls-client-block.c b/demos/guide/tls-client-block.c index 4536b1ffa09..8b51d6d8421 100644 --- a/demos/guide/tls-client-block.c +++ b/demos/guide/tls-client-block.c @@ -194,7 +194,7 @@ int main(int argc, char *argv[]) * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(ssl, hostname)) { + if (!SSL_set1_dnsname(ssl, hostname)) { printf("Failed to set the certificate verification hostname"); goto end; } diff --git a/demos/guide/tls-client-non-block.c b/demos/guide/tls-client-non-block.c index 3103e6725b8..31920acffc2 100644 --- a/demos/guide/tls-client-non-block.c +++ b/demos/guide/tls-client-non-block.c @@ -273,7 +273,7 @@ int main(int argc, char *argv[]) * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(ssl, hostname)) { + if (!SSL_set1_dnsname(ssl, hostname)) { printf("Failed to set the certificate verification hostname"); goto end; } diff --git a/demos/http3/ossl-nghttp3.c b/demos/http3/ossl-nghttp3.c index 5be7b1a52cd..942a99acc9b 100644 --- a/demos/http3/ossl-nghttp3.c +++ b/demos/http3/ossl-nghttp3.c @@ -416,7 +416,7 @@ OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const BIO_ADDRIN qconn_bio = NULL; /* Set the hostname we will validate the X.509 certificate against. */ - if (SSL_set1_host(qconn, bare_hostname) <= 0) + if (SSL_set1_dnsname(qconn, bare_hostname) <= 0) goto err; /* Configure SNI */ diff --git a/demos/sslecho/main.c b/demos/sslecho/main.c index 4d4fc969739..a26ebc0ce73 100644 --- a/demos/sslecho/main.c +++ b/demos/sslecho/main.c @@ -309,7 +309,7 @@ int main(int argc, char **argv) /* Set hostname for SNI */ SSL_set_tlsext_host_name(ssl, rem_server_ip); /* Configure server hostname check */ - if (!SSL_set1_host(ssl, rem_server_ip)) { + if (!SSL_set1_dnsname(ssl, rem_server_ip)) { ERR_print_errors_fp(stderr); goto exit; } diff --git a/doc/man3/SSL_CTX_dane_enable.pod b/doc/man3/SSL_CTX_dane_enable.pod index d558e63895f..7a37540c875 100644 --- a/doc/man3/SSL_CTX_dane_enable.pod +++ b/doc/man3/SSL_CTX_dane_enable.pod @@ -66,7 +66,7 @@ L if (and only if) you want to enable DANE for that connection. The B argument specifies the RFC7671 TLSA base domain, which will be the primary peer reference identifier for certificate name checks. -Additional server names can be specified via L. +Additional server names can be specified via L. The B is used as the default SNI hint if none has yet been specified via L. @@ -216,7 +216,7 @@ the lifetime of the SSL connection. */ SSL_dane_set_flags(ssl, DANE_FLAG_NO_DANE_EE_NAMECHECKS); - if (!SSL_add1_host(ssl, nexthop_domain)) + if (!SSL_add1_dnsname(ssl, nexthop_domain)) /* error */ SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); @@ -353,7 +353,7 @@ L with B equal to B. L, L, -L, +L, L, L, L, diff --git a/doc/man3/SSL_set1_host.pod b/doc/man3/SSL_set1_host.pod index b65116a53f5..6a3ada24eca 100644 --- a/doc/man3/SSL_set1_host.pod +++ b/doc/man3/SSL_set1_host.pod @@ -2,7 +2,10 @@ =head1 NAME -SSL_set1_host, SSL_add1_host, SSL_set_hostflags, SSL_get0_peername - +SSL_set1_dnsname, SSL_add1_dnsname, +SSL_set1_ipaddr, SSL_add1_ipaddr, +SSL_set1_host, SSL_add1_host, +SSL_set_hostflags, SSL_get0_peername - SSL server verification parameters =head1 SYNOPSIS @@ -11,39 +14,58 @@ SSL server verification parameters int SSL_set1_host(SSL *s, const char *host); int SSL_add1_host(SSL *s, const char *host); + int SSL_set1_dnsname(SSL *s, const char *dnsname); + int SSL_add1_dnsname(SSL *s, const char *dnsname); + int SSL_set1_ipaddr(SSL *s, const uint8_t *ip, size_t len); + int SSL_add1_ipaddr(SSL *s, const uint8_t *ip size_t len); void SSL_set_hostflags(SSL *s, unsigned int flags); const char *SSL_get0_peername(SSL *s); + int SSL_set1_host(SSL *s, const char *host); + int SSL_add1_host(SSL *s, const char *host); + =head1 DESCRIPTION -These functions configure server hostname checks in the SSL client. +These functions maintain lists of expected matches for peer +certificate subject alternate name (SAN) values is peer certificates +presented in an SSL connection. A peer certificate will be considered +a match for validation purposes if all of the following is true: -SSL_set1_host() sets in the verification parameters of I -the expected DNS hostname or IP address to I, -clearing any previously specified IP address and hostnames. -If I is NULL or the empty string, IP address -and hostname checks are not performed on the peer certificate. -When a nonempty I is specified, certificate verification automatically -checks the peer hostname via L with I as specified -via SSL_set_hostflags(). Clients that enable DANE TLSA authentication -via L should leave it to that function to set -the primary reference identifier of the peer, and should not call -SSL_set1_host(). +* Any name in the dnsname list, if not empty, matches any SAN dnsname + in the certificate. If verification flags allow it, these will also + attempt to match against the CN in the subject. -SSL_add1_host() adds I as an additional reference identifier -that can match the peer's certificate. Any previous hostnames -set via SSL_set1_host() or SSL_add1_host() are retained. -Adding an IP address is allowed only if no IP address has been set before. -No change is made if I is NULL or empty. -When an IP address and/or multiple hostnames are configured, -the peer is considered verified when any of these matches. -This function is required for DANE TLSA in the presence of service name indirection -via CNAME, MX or SRV records as specified in RFCs 7671, 7672, and 7673. - -TLS clients are recommended to use SSL_set1_host() or SSL_add1_host() -for server hostname or IP address validation, -as well as L for Server Name Indication (SNI), -which may be crucial also for correct routing of the connection request. +* Any address in the IP address list, if not empty, matches any IP + address SAN in the certificate. + +The set1 family of functions clears the list, and sets the first value +to a the provided parameter if the provided parameter is not NULL. + +The add1 family of functions adds a single entry to the list. + +SSL_set1_dnsname() clears the list of dnsnames to match certificate +SAN dnsnames. +If I is not NULL, it will be checked for +validity and added to the list of DNS name reference identifiers as its first entry. + +SSL_set1_ipaddr() clears the list of addresses to match certificate IP address SANs. +If is not NULL, it is parsed as an +IPv4 or IPv6 address and added to the list as its first entry. + +SSL_add1_dnsname() adds I to the list of DNS name reference +identifiers, if it has the correct structure for a DNS name. This +function is required for DANE TLSA in the presence of service name +indirection via CNAME, MX or SRV records as specified in RFCs 7671, +7672, and 7673. + +SSL_add1_ipaddr() adds I to the list of IP addresses, if it parses as an IP address. + +It is recommended that TLS clients use SSL_set1_dnsname() to configure server +hostname validation and L to configure +Server Name Indication (SNI), which may be crucial also for correct +server certificate selection and/or routing of the connection request. +SSL_set1_ip(), or SSL_set1_ip_asc() +should be used for server IP address validation, SSL_set_hostflags() sets the I that will be passed to L when name checks are applicable, by default @@ -68,8 +90,35 @@ of scope with the RFC 7671 DANE-EE(3) certificate usage, and the internal check will be suppressed as appropriate when DANE is enabled. +==head1 DEPRECATED FUNCTIONS + +SSL_set1_host and SSL_add1_host are deprecated as of OpenSSL 4.0.0. +SSL_add1_dnsame and SSL_add1_ip should be used instead. + +SSL_set1_host() sets in the verification parameters of I +the expected DNS hostname or IP address to I, +clearing any previously specified IP address and hostnames. +If I is NULL or the empty string, IP address +and hostname checks are not performed on the peer certificate. +When a nonempty I is specified, certificate verification automatically +checks the peer hostname via L with I as specified +via SSL_set_hostflags(). Clients that enable DANE TLSA authentication +via L should leave it to that function to set +the primary reference identifier of the peer, and should not call +SSL_set1_host(). + +SSL_add1_host() adds I as an additional reference identifier +that can match the peer's certificate. Any previous hostnames set via +SSL_set1_host() or SSL_add1_host() are retained. Adding an IP address +is allowed only if no IP address has been set before. No change is +made if I is NULL or empty. The peer is considered verified +when any of the added hostnames, if present, match, and the provided +IP address, if present, matches. + =head1 RETURN VALUES +SSL_set1_dnsname, SSL_set1_ip, SSL_set1_ip_asc, +SSL_add1_dnsname, SSL_add1_ip, SSL_add1_ip_asc, SSL_set1_host() and SSL_add1_host() return 1 for success and 0 for failure. @@ -91,9 +140,9 @@ and must be copied by the application if it is to be retained beyond the lifetime of the SSL connection. SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (!SSL_set1_host(ssl, "smtp.example.com")) + if (!SSL_set1_dnsname(ssl, "smtp.example.com")) /* error */ - if (!SSL_add1_host(ssl, "example.com")) + if (!SSL_add1_dnsname(ssl, "example.com")) /* error */ /* XXX: Perform SSL_connect() handshake and handle errors here */ @@ -115,6 +164,8 @@ L, L These functions were added in OpenSSL 1.1.0. +SSL_set1_host and SSL_add1_host were deprecated in OpenSSL 4.0.0 + =head1 COPYRIGHT Copyright 2016-2025 The OpenSSL Project Authors. All Rights Reserved. diff --git a/doc/man3/X509_VERIFY_PARAM_set_flags.pod b/doc/man3/X509_VERIFY_PARAM_set_flags.pod index f6634bb81e0..67d6438b675 100644 --- a/doc/man3/X509_VERIFY_PARAM_set_flags.pod +++ b/doc/man3/X509_VERIFY_PARAM_set_flags.pod @@ -16,9 +16,17 @@ X509_VERIFY_PARAM_set1_host, X509_VERIFY_PARAM_add1_host, X509_VERIFY_PARAM_set_hostflags, X509_VERIFY_PARAM_get_hostflags, X509_VERIFY_PARAM_get0_peername, -X509_VERIFY_PARAM_get0_email, X509_VERIFY_PARAM_set1_email, -X509_VERIFY_PARAM_set1_ip, X509_VERIFY_PARAM_get1_ip_asc, -X509_VERIFY_PARAM_set1_ip_asc +X509_VERIFY_PARAM_get0_email, +X509_VERIFY_PARAM_set1_email, +X509_VERIFY_PARAM_set1_rfc822, X509_VERIFY_PARAM_add1_rfc822, +X509_VERIFY_PARAM_set1_smtputf8, X509_VERIFY_PARAM_add1_smtputf8, +X509_VERIFY_PARAM_set1_ip, X509_VERIFY_PARAM_add1_ip, +X509_VERIFY_PARAM_set1_ip_asc, X509_VERIFY_PARAM_add1_ip_asc, +X509_VERIFY_PARAM_get1_ip_asc, +X509_VERIFY_PARAM_set1_host_input_validation, +X509_VERIFY_PARAM_set1_rfc822_input_validation, +X509_VERIFY_PARAM_set1_smtputf8_input_validation, +X509_VERIFY_PARAM_set1_ip_input_validation - X509 verification parameters =head1 SYNOPSIS @@ -66,10 +74,29 @@ X509_VERIFY_PARAM_set1_ip_asc char *X509_VERIFY_PARAM_get0_email(X509_VERIFY_PARAM *param); int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, const char *email, size_t emaillen); + int X509_VERIFY_PARAM_set1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); + int X509_VERIFY_PARAM_add1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); + int X509_VERIFY_PARAM_set1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); + int X509_VERIFY_PARAM_add1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); char *X509_VERIFY_PARAM_get1_ip_asc(X509_VERIFY_PARAM *param); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, const unsigned char *ip, size_t iplen); + int X509_VERIFY_PARAM_add1_ip(X509_VERIFY_PARAM *param, + const unsigned char *ip, size_t iplen); int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc); + int X509_VERIFY_PARAM_add1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc); + void X509_VERIFY_PARAM_set1_ip_input_validation(X509_VERIFY_PARAM *param, + int (*validate_ip)(const uint8_t *name, size_t len)); + void X509_VERIFY_PARAM_set1_host_input_validation(X509_VERIFY_PARAM *param, + int (*validate_host)(const char *name, size_t len)); + void X509_VERIFY_PARAM_set1_rfc822_input_validation(X509_VERIFY_PARAM *param, + int (*validate_rfc822)(const char *name, size_t len)); + void X509_VERIFY_PARAM_set1_smtputf8_input_validation(X509_VERIFY_PARAM *param, + int (*validate_smtputf8)(const char *name, size_t len)); =head1 DESCRIPTION @@ -195,14 +222,45 @@ the return value. X509_VERIFY_PARAM_get0_email() returns the expected RFC822 email address. -X509_VERIFY_PARAM_set1_email() sets the expected RFC822 email address to +X509_VERIFY_PARAM_set1_rfc822() clears all expected RFC822 email +addresses, and sets the expected RFC822 email address to I. If +I is NULL no expected address is set. Otherwise, if +I is zero, I must be NUL-terminated; if I +is nonzero, I must be set to the length of I. When +any email address is specified, certificate verification automatically +invokes L. + +X509_VERIFY_PARAM_add1_rfc822() adds I as an additional +reference identifier that can match RFC822 email addresses in the +peer's certificate. Any previous names set via +X509_VERIFY_PARAM_set1_rfc822(), X509_VERIFY_PARAM_add1_rfc822(), or +X509_VERIFY_PARAM_set1_email() are +retained on success, no change is made on failure. It is a failure if +email is NULL or the empty string.The peer is considered verified +when any one of the specified RFC822 or SMTPUTF8 names matches a corresponding email +address SAN in the certificate. + +X509_VERIFY_PARAM_set1_smtputf8() sets the expected SMTPUTF8 email address to I. -If I is NULL, email checking is disabled. Otherwise, +If I is NULL, SMTPUTF8 email checking is disabled. Otherwise, if I is zero, I must be NUL-terminated; if I is nonzero, -I must be set to the length of I. When an email address +I must be set to the length of I. When any email address is specified, certificate verification automatically invokes L. +X509_VERIFY_PARAM_add1_smtputf8() adds I as an additional +reference identifier that can match SMTPUTF8 email addresses in the +peer's certificate. Any previous names set via +X509_VERIFY_PARAM_set1_smtputf8(), X509_VERIFY_PARAM_add1_smtputf8(), or +X509_VERIFY_PARAM_set1_email() are +retained on success, no change is made on failure. It is a failure if +email is NULL or the empty string. The peer is considered verified +when any one of the specified RFC822 or SMTPUTF8 names matches a corresponding email +address SAN in the certificate. + +X509_VERIFY_PARAM_set1_email() calls X509_VERIFY_PARAM_set_rfc822(), and +X509_VERIFY_PARAM_set_smtputf8() and succeeds if any call succeeds. + X509_VERIFY_PARAM_get1_ip_asc() returns the expected IP address as a string. The caller is responsible for freeing it. @@ -213,11 +271,41 @@ I must be set to 4 for IPv4 and 16 for IPv6. When an IP address is specified, certificate verification automatically invokes L. +X509_VERIFY_PARAM_add1_ip() adds I as an additional reference +identifier that can match the peer's certificate on success. Any +previous names set via X509_VERIFY_PARAM_set1_ip(), +X509_VERIFY_PARAM_add1_ip(), X509_VERIFY_PARAM_set1_ip_asc(), or +X509_VERIFY_PARAM_add1_ip_asc() are retained. No change is made on +failure. It is a failure if is NULL or the empty string. When +multiple names are configured, the peer is considered verified when +any name matches. + X509_VERIFY_PARAM_set1_ip_asc() sets the expected IP address to I. The I argument must be a NUL-terminated ASCII string: dotted decimal quad for IPv4 and colon-separated hexadecimal for IPv6. The condensed "::" notation is supported for IPv6 addresses. +X509_VERIFY_PARAM_add1_ip_asc() adds I as an additional +reference identifier that can match the peer's certificate on success. +The I argument must be a NUL-terminated ASCII string: dotted +decimal quad for IPv4 and colon-separated hexadecimal for IPv6. The +condensed "::" notation is supported for IPv6 addresses. Any previous +names set via X509_VERIFY_PARAM_set1_ip(), +X509_VERIFY_PARAM_add1_ip(), X509_VERIFY_PARAM_set1_ip_asc(), or +X509_VERIFY_PARAM_add1_ip_asc() are retained. No change is made on +failure. It is a failure if I is NULL or the empty string. +When multiple names are configured, the peer is considered verified +when any one of the specified addresses matches a corresponding IP address SAN in the certificate. + +X509_VERIFY_PARAM_set1_host_input_validation(), +X509_VERIFY_PARAM_set1_rfc822_input_validation(), +X509_VERIFY_PARAM_set1_smtputf8_input_validation(), and +X509_VERIFY_PARAM_set1_ip_input_validation() set a verification +function to validate the input on setting the corresponding validation +parameter expected values. These functions bypass OpenSSL's input validation +to these commands. if the provided function succeeds, the corresponding +input will be accepted and attempted to be used when verifying certificates. + =head1 RETURN VALUES X509_VERIFY_PARAM_set_flags(), X509_VERIFY_PARAM_clear_flags(), @@ -225,8 +313,10 @@ X509_VERIFY_PARAM_set_inh_flags(), X509_VERIFY_PARAM_set_purpose(), X509_VERIFY_PARAM_set_trust(), X509_VERIFY_PARAM_add0_policy() X509_VERIFY_PARAM_set1_policies(), X509_VERIFY_PARAM_set1_host(), X509_VERIFY_PARAM_add1_host(), -X509_VERIFY_PARAM_set1_email(), X509_VERIFY_PARAM_set1_ip() and -X509_VERIFY_PARAM_set1_ip_asc() return 1 for success and 0 for +X509_VERIFY_PARAM_set1_email(), +X509_VERIFY_PARAM_set1_ip(), X509_VERIFY_PARAM_add1_ip(), +X509_VERIFY_PARAM_set1_ip_asc(), +X509_VERIFY_PARAM_add1_ip_asc() return 1 for success and 0 for failure. X509_VERIFY_PARAM_get0_host(), X509_VERIFY_PARAM_get0_email(), and diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index b5a75720e79..ca2701a0ad4 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -1872,8 +1872,14 @@ __owur int SSL_set_purpose(SSL *ssl, int purpose); __owur int SSL_CTX_set_trust(SSL_CTX *ctx, int trust); __owur int SSL_set_trust(SSL *ssl, int trust); -__owur int SSL_set1_host(SSL *s, const char *host); -__owur int SSL_add1_host(SSL *s, const char *host); +#ifndef OPENSSL_NO_DEPRECATED_4_0 +OSSL_DEPRECATEDIN_4_0 __owur int SSL_set1_host(SSL *s, const char *host); +OSSL_DEPRECATEDIN_4_0 __owur int SSL_add1_host(SSL *s, const char *host); +#endif /* OPENSSL_NO_DEPRECATED_4_0 */ +__owur int SSL_set1_dnsname(SSL *s, const char *dnsname); +__owur int SSL_add1_dnsname(SSL *s, const char *dnsname); +__owur int SSL_set1_ipaddr(SSL *s, const char *ipaddr); +__owur int SSL_add1_ipaddr(SSL *s, const char *ipaddr); __owur const char *SSL_get0_peername(SSL *s); void SSL_set_hostflags(SSL *s, unsigned int flags); diff --git a/include/openssl/x509_vfy.h.in b/include/openssl/x509_vfy.h.in index 0b3bd801d7c..fda59129df1 100644 --- a/include/openssl/x509_vfy.h.in +++ b/include/openssl/x509_vfy.h.in @@ -762,6 +762,8 @@ int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, const char *name, size_t namelen); int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, const char *name, size_t namelen); +void X509_VERIFY_PARAM_set1_host_input_validation(X509_VERIFY_PARAM *param, + int (*validate_host)(const char *name, size_t len)); void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, unsigned int flags); unsigned int X509_VERIFY_PARAM_get_hostflags(const X509_VERIFY_PARAM *param); @@ -770,11 +772,29 @@ void X509_VERIFY_PARAM_move_peername(X509_VERIFY_PARAM *, X509_VERIFY_PARAM *); char *X509_VERIFY_PARAM_get0_email(X509_VERIFY_PARAM *param); int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, const char *email, size_t emaillen); +int X509_VERIFY_PARAM_set1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); +int X509_VERIFY_PARAM_add1_rfc822(X509_VERIFY_PARAM *param, + const char *email, size_t len); +void X509_VERIFY_PARAM_set1_rfc822_input_validation(X509_VERIFY_PARAM *param, + int (*validate_rfc822)(const char *name, size_t len)); +int X509_VERIFY_PARAM_set1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); +int X509_VERIFY_PARAM_add1_smtputf8(X509_VERIFY_PARAM *param, + const char *email, size_t len); +void X509_VERIFY_PARAM_set1_smtputf8_input_validation(X509_VERIFY_PARAM *param, + int (*validate_smtputf8)(const char *name, size_t len)); char *X509_VERIFY_PARAM_get1_ip_asc(X509_VERIFY_PARAM *param); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, - const unsigned char *ip, size_t iplen); + const uint8_t *ip, size_t iplen); +void X509_VERIFY_PARAM_set1_ip_input_validation(X509_VERIFY_PARAM *param, + int (*validate_ip)(const uint8_t *name, size_t len)); int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, const char *ipasc); +int X509_VERIFY_PARAM_add1_ip(X509_VERIFY_PARAM *param, + const uint8_t *ip, size_t len); +int X509_VERIFY_PARAM_add1_ip_asc(X509_VERIFY_PARAM *param, + const char *ipasc); int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *param); int X509_VERIFY_PARAM_get_auth_level(const X509_VERIFY_PARAM *param); diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index fc352ba68ff..76615f48097 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -1140,6 +1140,47 @@ int SSL_set_trust(SSL *s, int trust) return X509_VERIFY_PARAM_set_trust(sc->param, trust); } +int SSL_set1_dnsname(SSL *s, const char *host) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s); + + if (sc == NULL) + return 0; + + return X509_VERIFY_PARAM_set1_host(sc->param, host, 0); +} + +int SSL_add1_dnsname(SSL *s, const char *host) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s); + + if (sc == NULL) + return 0; + + return X509_VERIFY_PARAM_add1_host(sc->param, host, strlen(host)); +} + +int SSL_set1_ipaddr(SSL *s, const char *ipaddr) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s); + + if (sc == NULL) + return 0; + + return X509_VERIFY_PARAM_set1_ip_asc(sc->param, ipaddr); +} + +int SSL_add1_ipaddr(SSL *s, const char *ipaddr) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s); + + if (sc == NULL) + return 0; + + return X509_VERIFY_PARAM_add1_ip_asc(sc->param, ipaddr); +} + +#if !defined(OPENSSL_NO_DEPRECATED_4_0) int SSL_set1_host(SSL *s, const char *host) { SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s); @@ -1190,6 +1231,7 @@ int SSL_add1_host(SSL *s, const char *host) return X509_VERIFY_PARAM_add1_host(sc->param, host, 0); } +#endif /* !defined(OPENSSL_NO_DEPRECATED_4_0) */ void SSL_set_hostflags(SSL *s, unsigned int flags) { diff --git a/test/quic-openssl-docker/hq-interop/quic-hq-interop.c b/test/quic-openssl-docker/hq-interop/quic-hq-interop.c index d1448854c60..d890bfee4fc 100644 --- a/test/quic-openssl-docker/hq-interop/quic-hq-interop.c +++ b/test/quic-openssl-docker/hq-interop/quic-hq-interop.c @@ -783,7 +783,7 @@ static int setup_connection(char *hostname, char *port, * Virtually all clients should do this unless you really know what you * are doing. */ - if (!SSL_set1_host(*ssl, hostname)) { + if (!SSL_set1_dnsname(*ssl, hostname)) { fprintf(stderr, "Failed to set the certificate verification hostname"); goto end; } diff --git a/test/verify_extra_test.c b/test/verify_extra_test.c index ad2649034fa..1420c710348 100644 --- a/test/verify_extra_test.c +++ b/test/verify_extra_test.c @@ -201,6 +201,431 @@ static int test_self_signed(const char *filename, int use_trusted, int expected) return ret; } +static const char *multiname_cert[] = { + "-----BEGIN CERTIFICATE-----\n" + "MIIFnDCCBISgAwIBAgIUTgfdSQm2hjgUZoA8jeQX7sDPAoowDQYJKoZIhvcNAQEL\n" + "BQAwgYUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdBbGJlcnRhMREwDwYDVQQHDAhF\n" + "ZG1vbnRvbjERMA8GA1UECgwITXVwcGV0cnkxITAfBgNVBAsMGFN0YXRsZXIgYW5k\n" + "IFdhbGRvcmYgUiBVUzEbMBkGA1UEAwwSYmVha2VyLm11cHBldHJ5LmNhMB4XDTI2\n" + "MDExMjIwNTUwOVoXDTI3MDExMjIwNTUwOVowgYUxCzAJBgNVBAYTAkNBMRAwDgYD\n" + "VQQIDAdBbGJlcnRhMREwDwYDVQQHDAhFZG1vbnRvbjERMA8GA1UECgwITXVwcGV0\n" + "cnkxITAfBgNVBAsMGFN0YXRsZXIgYW5kIFdhbGRvcmYgUiBVUzEbMBkGA1UEAwwS\n" + "YmVha2VyLm11cHBldHJ5LmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" + "AQEA+EsGQCX4YyZF3QbVcFUcWpYDp8MJHr5vF0cosvj9afGPhpLREWR7EmnNA8Gf\n" + "wb+ef/jNrDg8W81uDD3N29PvbM+hHAQPaHrRupQZ+W+uIVEAu/lpI359jIRS1Sey\n" + "IcU2vIgn3Tlnv4UX3o3QMyH8+RcCvSNrWu4+f9ipMAy/xq3PWBm+fHi/+bI03eDy\n" + "0xNm8kpXbhqZQiZ1tAhsTa3V2pIufqAnctDgl2GUHtfmKO095OHimjhQXHxO8Ctk\n" + "R+vFv0nleJoAAfkmaMdtdTd1O8m3AtQv6xQC4X5Tu/+FKKQOXjf/8OtqW2lrlxxR\n" + "pbFuy66I9HVyf+gGWEbZyqbCpwIDAQABo4ICADCCAfwwggG3BgNVHREEggGuMIIB\n" + "qoILbXVwcGV0cnkuY2GCD3d3dy5tdXBwZXRyeS5jYYITc3RhdGxlci5tdXBwZXRy\n" + "eS5jYYITd2FsZG9yZi5tdXBwZXRyeS5jYYETc3RhdGxlckBtdXBwdGVyeS5jYYET\n" + "d2FsZG9yZkBtdXBwdGVyeS5jYYcExikABIcQIAEFA7o+AAAAAAAAAAIAMIcEqveq\n" + "AocQKAEBuAAQAAAAAAAAAAAAC4cEwCEEDIcQIAEFAAACAAAAAAAAAAAADIcExwdb\n" + "DYcQIAEFAAAtAAAAAAAAAAAADYcEwMvmCocQIAEFAACoAAAAAAAAAAAADocEwAUF\n" + "8YcQIAEFAAAvAAAAAAAAAAAAD4cEwHAkBIcQIAEFAAASAAAAAAAAAAANDYcExmG+\n" + "NYcQIAEFAAABAAAAAAAAAAAAU4cEwCSUEYcQIAEH/gAAAAAAAAAAAAAAU4cEwDqA\n" + "HocQIAEFAwwnAAAAAAAAAAIAMIcEwQAOgYcQIAEH/QAAAAAAAAAAAAAAAYcExwdT\n" + "KocQIAEFAACfAAAAAAAAAAAAQocEygwbIYcQIAENwwAAAAAAAAAAAAAANTALBgNV\n" + "HQ8EBAMCBDAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFCutBN63ufhB\n" + "IY4dOuFcYfC3p+mMMA0GCSqGSIb3DQEBCwUAA4IBAQBBWfTvwxV1s3xaS5Ko6T7B\n" + "vS7TPih0MO8auv0mvZXG3jy/LfAfgu05PbGIf0dzFhBpoZD0VrrugmdemLkJd+u6\n" + "pbEttGFZtcGb//MtjUAYQnEq6fYgDeT0dGU0upwQPWGgh5LpFSab+71C6Ofc3YFM\n" + "WPH7UaRBUV2mqNtUokOce6kYtl97St7p6cGpQW9Q1uFQODvAm3ZPq/YNGnTJAOdb\n" + "9UX8Td1T5fH86H0hb6qB0AEhVdgjPUgs33zYNWRPg8fYleT6w1MpE2HaUqqhld3B\n" + "ZtVZ5IznkY+8qH0rua89m4TV3qzUqNVUL0uxkWnQI3W8g3Adin7QN3EA6ZYrTD3q\n" + "-----END CERTIFICATE-----\n", + NULL, +}; + +static const time_t multiname_valid_at = 1768253189; + +static const char *multiname_dnsnames[] = { + "muppetry.ca", + "www.muppetry.ca", + "statler.muppetry.ca", + "waldorf.muppetry.ca", + NULL, +}; + +static const char *multiname_emails[] = { + "statler@mupptery.ca", + "waldorf@mupptery.ca", + NULL, +}; + +static const char *multiname_ips[] = { + "198.41.0.4", + "2001:503:ba3e::2:30", + "170.247.170.2", + "2801:1b8:10::b", + "192.33.4.12", + "2001:500:2::c", + "199.7.91.13", + "2001:500:2d::d", + "192.203.230.10", + "2001:500:a8::e", + "192.5.5.241", + "2001:500:2f::f", + "192.112.36.4", + "2001:500:12::d0d", + "198.97.190.53", + "2001:500:1::53", + "192.36.148.17", + "2001:7fe::53", + "192.58.128.30", + "2001:503:c27::2:30", + "193.0.14.129", + "2001:7fd::1", + "199.7.83.42", + "2001:500:9f::42", + "202.12.27.33", + "2001:dc3::35", + NULL, +}; + +static int test_multiname_selfsigned(void) +{ + X509 *cert = NULL; + X509_STORE_CTX *ctx = NULL; + X509_STORE *store = NULL; + X509_VERIFY_PARAM *vpm = NULL; + int fails = 0; + int ret = 0; + + if (!TEST_ptr((cert = X509_from_strings(multiname_cert)))) + goto err; + + if (!TEST_true(X509_self_signed(cert, 1))) + goto err; + + if (!TEST_ptr(store = X509_STORE_new())) + goto err; + + if (!TEST_true(X509_STORE_add_cert(store, cert))) + goto err; + + if (!TEST_ptr((vpm = X509_STORE_get0_param(store)))) + goto err; + + if (!TEST_ptr(ctx = X509_STORE_CTX_new())) + goto err; + + X509_VERIFY_PARAM_set_time(vpm, multiname_valid_at); + + for (size_t i = 0; multiname_dnsnames[i] != NULL; i++) { + /* Try one not in the certificate */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, "bunsen.muppetry.ca", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) { + TEST_info("Verify succeeded for non-present name bunsen.muppetry.ca\n"); + goto err; + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, NULL, 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) + goto err; + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, multiname_dnsnames[i], strlen(multiname_dnsnames[i])))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed for initial name %s\n", multiname_dnsnames[i]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + for (size_t j = 0; multiname_dnsnames[j] != NULL; j++) { + if (j != i) { + if (!TEST_true(X509_VERIFY_PARAM_add1_host(vpm, multiname_dnsnames[j], 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed with added name %s\n", multiname_dnsnames[j]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + } + } + /* Try the CN */ + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, "beaker.muppetry.ca", 0))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed for CN name beaker.muppetry.ca\n"); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, NULL, 0))) + goto err; + } + + for (size_t i = 0; multiname_emails[i] != NULL; i++) { + /* Try one not in the certificate */ + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, "bunsen@muppetry.ca", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) { + TEST_info("Verify succeeded for non-present name bunsen@muppetry.ca\n"); + goto err; + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, NULL, 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) + goto err; + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, multiname_emails[i], strlen(multiname_emails[i])))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed for initial name %s\n", multiname_emails[i]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + for (size_t j = 0; multiname_emails[j] != NULL; j++) { + if (j != i) { + if (!TEST_true(X509_VERIFY_PARAM_add1_rfc822(vpm, multiname_emails[j], 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed with added name %s\n", multiname_emails[j]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + } + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, NULL, 0))) + goto err; + } + + for (size_t i = 0; multiname_ips[i] != NULL; i++) { + /* Try one not in the certificate */ + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, "8.8.8.8"))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) { + TEST_info("Verify succeeded for non-present name 8.8.8.8\n"); + goto err; + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, NULL))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) + goto err; + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, multiname_ips[i]))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed for initial name %s\n", multiname_ips[i]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + for (size_t j = 0; multiname_ips[j] != NULL; j++) { + if (j != i) { + if (!TEST_true(X509_VERIFY_PARAM_add1_ip_asc(vpm, multiname_ips[j]))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) { + TEST_info("Verify failed with added name %s\n", multiname_ips[j]); + fails++; + } + X509_STORE_CTX_cleanup(ctx); + } + } + X509_STORE_CTX_cleanup(ctx); + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, NULL))) + goto err; + } + + /* + * Test that individual categories work together, and a non-match will still fail validation + */ + + /* A dnsname, email and ip that are all valid in the cert should succeed */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, "www.muppetry.ca", 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, "2001:503:ba3e::2:30"))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, "waldorf@mupptery.ca", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) + fails++; + X509_STORE_CTX_cleanup(ctx); + + /* Setting an non-matching email should fail validation even with valid dnsname and ip */ + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, "bunsen@mupptery.ca", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) + fails++; + X509_STORE_CTX_cleanup(ctx); + /* reset */ + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, "waldorf@mupptery.ca", 0))) + goto err; + + /* Setting an non-matching ip should fail validation even with valid dnsname and email */ + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, "199.185.178.80"))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) + fails++; + X509_STORE_CTX_cleanup(ctx); + /* reset */ + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, "2001:503:ba3e::2:30"))) + goto err; + + /* Setting an non-matching dnsname should fail validation even with valid ip and email */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, "www.libressl.org", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_false(X509_verify_cert(ctx))) + fails++; + X509_STORE_CTX_cleanup(ctx); + /* reset */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, "www.muppetry.ca", 0))) + goto err; + + /* Adding non-matching values to each category with a match will still succeed */ + if (!TEST_true(X509_VERIFY_PARAM_add1_host(vpm, "www.libressl.org", 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_add1_ip_asc(vpm, "199.185.178.80"))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_add1_rfc822(vpm, "beck@openbsd.org", 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_add1_smtputf8(vpm, "学生@muppetry.ca", 0))) + goto err; + if (!TEST_true(X509_STORE_CTX_init(ctx, store, cert, NULL))) + goto err; + if (!TEST_true(X509_verify_cert(ctx))) + fails++; + X509_STORE_CTX_cleanup(ctx); + + ret = fails == 0; + +err: + X509_STORE_free(store); + X509_STORE_CTX_free(ctx); + X509_free(cert); + return ret; +} + +static int yolo_name_validation(const char *name, size_t len) +{ + return 1; +} + +static int yolo_ip_validation(const uint8_t *name, size_t len) +{ + return 1; +} + +static int test_vpm_input_validation(void) +{ + const char *utf8mail = "学生@muppetry.ca"; + const char *rfc822mail = "beaker@muppetry.ca"; + X509_VERIFY_PARAM *vpm = NULL; + int ret = 0; + + if (!TEST_ptr(vpm = X509_VERIFY_PARAM_new())) + goto err; + + if (!TEST_false(X509_VERIFY_PARAM_set1_rfc822(vpm, utf8mail, 0))) + goto err; + if (!TEST_false(X509_VERIFY_PARAM_set1_smtputf8(vpm, rfc822mail, 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_rfc822(vpm, rfc822mail, 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_smtputf8(vpm, utf8mail, 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, rfc822mail, 0))) + goto err; + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, utf8mail, 0))) + goto err; + + for (size_t i = 0; multiname_dnsnames[i] != NULL; i++) { + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, multiname_dnsnames[i], 0))) + goto err; + if (!TEST_false(X509_VERIFY_PARAM_set1_email(vpm, multiname_dnsnames[i], 0))) + goto err; + } + for (size_t i = 0; multiname_emails[i] != NULL; i++) { + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, multiname_emails[i], 0))) + goto err; + if (!TEST_false(X509_VERIFY_PARAM_set1_host(vpm, multiname_emails[i], 0))) + goto err; + } + for (size_t i = 0; multiname_ips[i] != NULL; i++) { + size_t l = strlen(multiname_ips[i]); + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, multiname_ips[i]))) + goto err; + if (l == 4 || l == 16) { + if (!TEST_true(X509_VERIFY_PARAM_set1_ip(vpm, (const uint8_t *)multiname_ips[i], l))) + goto err; + } else { + if (!TEST_false(X509_VERIFY_PARAM_set1_ip(vpm, (const uint8_t *)multiname_ips[i], l))) + goto err; + } + } + + X509_VERIFY_PARAM_set1_host_input_validation(vpm, yolo_name_validation); + X509_VERIFY_PARAM_set1_rfc822_input_validation(vpm, yolo_name_validation); + X509_VERIFY_PARAM_set1_smtputf8_input_validation(vpm, yolo_name_validation); + X509_VERIFY_PARAM_set1_ip_input_validation(vpm, yolo_ip_validation); + for (size_t i = 0; multiname_dnsnames[i] != NULL; i++) { + /* should still work */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, multiname_dnsnames[i], 0))) + goto err; + /* should be accepted now */ + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, multiname_dnsnames[i], 0))) + goto err; + } + for (size_t i = 0; multiname_emails[i] != NULL; i++) { + /* should still work */ + if (!TEST_true(X509_VERIFY_PARAM_set1_email(vpm, multiname_emails[i], 0))) + goto err; + /* should be accepted now */ + if (!TEST_true(X509_VERIFY_PARAM_set1_host(vpm, multiname_emails[i], 0))) + goto err; + } + for (size_t i = 0; multiname_ips[i] != NULL; i++) { + if (!TEST_true(X509_VERIFY_PARAM_set1_ip_asc(vpm, multiname_ips[i]))) + goto err; + /* should be accepted now */ + if (!TEST_true(X509_VERIFY_PARAM_set1_ip(vpm, (const uint8_t *)multiname_ips[i], strlen(multiname_ips[i])))) + goto err; + } + + ret = 1; + +err: + X509_VERIFY_PARAM_free(vpm); + return ret; +} + static int test_self_signed_good(void) { return test_self_signed(root_f, 1, 1); @@ -321,6 +746,8 @@ int setup_tests(void) ADD_TEST(test_purpose_ssl_client); ADD_TEST(test_purpose_ssl_server); ADD_TEST(test_purpose_any); + ADD_TEST(test_multiname_selfsigned); + ADD_TEST(test_vpm_input_validation); return 1; err: cleanup_tests(); diff --git a/util/libcrypto.num b/util/libcrypto.num index 954486f390a..5e1c4b430dc 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5149,15 +5149,25 @@ X509_VERIFY_PARAM_get_inh_flags ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get0_host ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_set1_host ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_add1_host ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_host_input_validation ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_set_hostflags ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get_hostflags ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get0_peername ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_move_peername ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get0_email ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_set1_email ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_rfc822 ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_add1_rfc822 ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_rfc822_input_validation ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_smtputf8 ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_add1_smtputf8 ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_smtputf8_input_validation ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get1_ip_asc ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_set1_ip ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_set1_ip_asc ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_add1_ip ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_add1_ip_asc ? 4_0_0 EXIST::FUNCTION: +X509_VERIFY_PARAM_set1_ip_input_validation ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get_depth ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get_auth_level ? 4_0_0 EXIST::FUNCTION: X509_VERIFY_PARAM_get0_name ? 4_0_0 EXIST::FUNCTION: diff --git a/util/libssl.num b/util/libssl.num index eb376dc72c9..dca18a55c62 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -266,8 +266,12 @@ SSL_CTX_set_purpose ? 4_0_0 EXIST::FUNCTION: SSL_set_purpose ? 4_0_0 EXIST::FUNCTION: SSL_CTX_set_trust ? 4_0_0 EXIST::FUNCTION: SSL_set_trust ? 4_0_0 EXIST::FUNCTION: -SSL_set1_host ? 4_0_0 EXIST::FUNCTION: -SSL_add1_host ? 4_0_0 EXIST::FUNCTION: +SSL_set1_host ? 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_0 +SSL_add1_host ? 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_0 +SSL_set1_dnsname ? 4_0_0 EXIST::FUNCTION: +SSL_add1_dnsname ? 4_0_0 EXIST::FUNCTION: +SSL_set1_ipaddr ? 4_0_0 EXIST::FUNCTION: +SSL_add1_ipaddr ? 4_0_0 EXIST::FUNCTION: SSL_get0_peername ? 4_0_0 EXIST::FUNCTION: SSL_set_hostflags ? 4_0_0 EXIST::FUNCTION: SSL_CTX_dane_enable ? 4_0_0 EXIST::FUNCTION: