diff --git a/include/mbedtls/x509_crt.h b/include/mbedtls/x509_crt.h index a5c1b8eec..f53573c7f 100644 --- a/include/mbedtls/x509_crt.h +++ b/include/mbedtls/x509_crt.h @@ -314,7 +314,16 @@ int mbedtls_x509_crt_verify( mbedtls_x509_crt *crt, * for ECDSA) apply to all certificates: trusted root, * intermediate CAs if any, and end entity certificate. * - * \note TODO: IP addresses in exp_name + * \note If MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT is enabled, instead + * of a DNS name, exp_name can also contain the string "IP:" + * followed by an IP address. This address must be either an + * IPv4 address in dotted decimal notation, or an IPv6 + * address consisting of exactly 8 groups of 4 hexadecimal + * digits separated by colons. For example, if the expected + * IPv6 is fe80::1, then exp_name must be the string + * "IP:fe80:0000:0000:0000:0000:0000:0000:0001" or its + * uppercase equivalent. If the expected IPv4 is 127.0.0.1, + * then exp_name should be "IP:127.0.0.1". * * \return 0 if successful or MBEDTLS_ERR_X509_CERT_VERIFY_FAILED * in which case *flags will have one or more diff --git a/library/x509_crt.c b/library/x509_crt.c index 8e99d28a0..c80669235 100644 --- a/library/x509_crt.c +++ b/library/x509_crt.c @@ -1796,7 +1796,7 @@ static int x509_memcasecmp( const void *s1, const void *s2, size_t len ) } /* - * Return 0 if names matche as DNS names (inc. wildcard), + * Return 0 if names match as DNS names (inc. wildcard), * -1 otherwise */ static int x509_check_dns_name( const char *exp_name, @@ -2229,13 +2229,79 @@ int mbedtls_x509_crt_verify( mbedtls_x509_crt *crt, exp_name, flags, f_vrfy, p_vrfy ) ); } +#if defined(MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT) /* - * Verify that the certificate matches wit the expected name + * Return 0 if exp_name and name contain the same IP address, + * -1 otherwise. + * + * exp_name must be "IP:" followed by either an IPv4 addres in dotted-quad + * notation, or an IPv6 address with no shortcuts and all leading zeros. + * + * name contains the IP as an octet string + */ +static int x509_check_ip_address( const char *exp_name, + size_t exp_len, + const mbedtls_x509_buf *name ) +{ + /* Print IPv6 addresses with a leading colon for convenience, so + * we have 32 hex digits, 8 colons, plus terminating NULL */ + char ip[32 + 8 + 1]; + int ret; + + if( name->len == 4 ) + { + ret = mbedtls_snprintf( ip, sizeof( ip ), "%d.%d.%d.%d", + name->p[0], name->p[1], name->p[2], name->p[3] ); + + if( ret < 0 || (size_t) ret > sizeof( ip ) ) + return( -1 ); + + if( (size_t) ret + 3 == exp_len && + memcmp( ip, exp_name + 3, exp_len - 3 ) == 0 ) + { + return( 0 ); + } + } + else if( name->len == 16 ) + { + size_t i, n; + char *p; + + /* "IP:" + 32 hex digits + 7 colons */ + if( exp_len != 3 + 32 + 7 ) + return( -1 ); + + for( i = 0, n = sizeof( ip ), p = ip; + i < 16; + i += 2, n -= 5, p += 5 ) + { + ret = mbedtls_snprintf( p, n, ":%02x%02x", + name->p[i], name->p[i+1] ); + if( ret < 0 || (size_t) ret > n ) + return( -1 ); + } + + if( x509_memcasecmp( ip + 1, exp_name + 3, exp_len - 3 ) == 0 ) + return( 0 ); + } + + return( -1 ); +} +#endif /* MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT */ + +/* + * Verify that the certificate matches with the expected name */ static int x509_crt_verify_name( const mbedtls_x509_crt *crt, const char *exp_name ) { const size_t exp_len = strlen( exp_name ); + unsigned char exp_tag = X509_CRT_SAN_TAG_DNS_NAME; + +#if defined(MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT) + if( exp_len >= 3 && memcmp( exp_name, "IP:", 3 ) == 0 ) + exp_tag = X509_CRT_SAN_TAG_IP_ADDRESS; +#endif if( crt->ext_types & MBEDTLS_X509_EXT_SUBJECT_ALT_NAME ) { @@ -2243,11 +2309,26 @@ static int x509_crt_verify_name( const mbedtls_x509_crt *crt, for( cur = &crt->subject_alt_names; cur != NULL; cur = cur->next ) { - if( x509_check_dns_name( exp_name, exp_len, &cur->buf ) == 0 ) + if( cur->buf.tag != exp_tag ) + continue; + + if( exp_tag == X509_CRT_SAN_TAG_DNS_NAME && + x509_check_dns_name( exp_name, exp_len, &cur->buf ) == 0 ) + { return( 0 ); + } + +#if defined(MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT) + if( exp_tag == X509_CRT_SAN_TAG_IP_ADDRESS && + x509_check_ip_address( exp_name, exp_len, &cur->buf ) == 0 ) + { + return( 0 ); + } +#endif } } - else + /* Only DNS names can match the Common Name */ + else if( exp_tag == X509_CRT_SAN_TAG_DNS_NAME ) { const mbedtls_x509_name *cur; diff --git a/tests/suites/test_suite_x509parse.data b/tests/suites/test_suite_x509parse.data index 8b0e7ac4b..102a6e76c 100644 --- a/tests/suites/test_suite_x509parse.data +++ b/tests/suites/test_suite_x509parse.data @@ -703,6 +703,26 @@ X509 Certificate verification #81 (multiple CRLs, none relevant) depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_RSA_C x509_verify:"data_files/enco-cert-utf8str.pem":"data_files/enco-ca-prstr.pem":"data_files/crl_cat_rsa-ec.pem":"NULL":0:0:"NULL" +X509 Certificate verification #82 (SAN with IP, IPv4, OK) +depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT +x509_verify:"data_files/server5-san-ip.crt":"data_files/test-ca2.crt":"data_files/crl-ec-sha256.pem":"IP\:192.168.0.42":0:0:"NULL" + +X509 Certificate verification #83 (SAN with IP, IPv4, bad) +depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT +x509_verify:"data_files/server5-san-ip.crt":"data_files/test-ca2.crt":"data_files/crl-ec-sha256.pem":"IP\:192.168.0.24":MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:MBEDTLS_X509_BADCERT_CN_MISMATCH:"NULL" + +X509 Certificate verification #84 (SAN with IP, IPv6, OK) +depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT +x509_verify:"data_files/server5-san-ip.crt":"data_files/test-ca2.crt":"data_files/crl-ec-sha256.pem":"IP\:FE80\:0000\:0000\:0000\:0000\:0000\:0000\:0001":0:0:"NULL" + +X509 Certificate verification #85 (SAN with IP, IPv6, bad) +depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT +x509_verify:"data_files/server5-san-ip.crt":"data_files/test-ca2.crt":"data_files/crl-ec-sha256.pem":"IP\:FE80\:0000\:0000\:0000\:0000\:0000\:0000\:0002":MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:MBEDTLS_X509_BADCERT_CN_MISMATCH:"NULL" + +X509 Certificate verification #86 (IP without SAN, bad) +depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP384R1_ENABLED:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_X509_SAN_IP_ADDRESS_SUPPORT +x509_verify:"data_files/server5.crt":"data_files/test-ca2.crt":"data_files/crl-ec-sha256.pem":"IP\:FE80\:0000\:0000\:0000\:0000\:0000\:0000\:0001":MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:MBEDTLS_X509_BADCERT_CN_MISMATCH:"NULL" + X509 Certificate verification callback: trusted EE cert depends_on:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_SHA256_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED x509_verify_callback:"data_files/server5-selfsigned.crt":"data_files/server5-selfsigned.crt":0:"depth 0 - serial 53\:A2\:CB\:4B\:12\:4E\:AD\:83\:7D\:A8\:94\:B2 - subject CN=selfsigned, OU=testing, O=PolarSSL, C=NL\n"