summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/crypto/x509.c140
-rw-r--r--src/include/ipxe/asn1.h5
-rw-r--r--src/include/ipxe/x509.h14
-rw-r--r--src/tests/x509_test.c9
4 files changed, 160 insertions, 8 deletions
diff --git a/src/crypto/x509.c b/src/crypto/x509.c
index 87b924c8..8e503f3b 100644
--- a/src/crypto/x509.c
+++ b/src/crypto/x509.c
@@ -603,7 +603,7 @@ static int x509_parse_ocsp ( struct x509_certificate *cert,
/* Enter accessLocation */
memcpy ( uri, raw, sizeof ( *uri ) );
- if ( ( rc = asn1_enter ( uri, ASN1_IMPLICIT_TAG ( 6 ) ) ) != 0 ) {
+ if ( ( rc = asn1_enter ( uri, X509_GENERAL_NAME_URI ) ) != 0 ) {
DBGC ( cert, "X509 %p OCSP does not contain "
"uniformResourceIdentifier:\n", cert );
DBGC_HDA ( cert, 0, raw->data, raw->len );
@@ -708,6 +708,33 @@ static int x509_parse_authority_info_access ( struct x509_certificate *cert,
return 0;
}
+/**
+ * Parse X.509 certificate subject alternative name
+ *
+ * @v cert X.509 certificate
+ * @v raw ASN.1 cursor
+ * @ret rc Return status code
+ */
+static int x509_parse_subject_alt_name ( struct x509_certificate *cert,
+ const struct asn1_cursor *raw ) {
+ struct x509_subject_alt_name *alt_name = &cert->extensions.alt_name;
+ struct asn1_cursor *names = &alt_name->names;
+ int rc;
+
+ /* Enter subjectAltName */
+ memcpy ( names, raw, sizeof ( *names ) );
+ if ( ( rc = asn1_enter ( names, ASN1_SEQUENCE ) ) != 0 ) {
+ DBGC ( cert, "X509 %p invalid subjectAltName: %s\n",
+ cert, strerror ( rc ) );
+ DBGC_HDA ( cert, 0, raw->data, raw->len );
+ return rc;
+ }
+ DBGC2 ( cert, "X509 %p has subjectAltName:\n", cert );
+ DBGC2_HDA ( cert, 0, names->data, names->len );
+
+ return 0;
+}
+
/** "id-ce-basicConstraints" object identifier */
static uint8_t oid_ce_basic_constraints[] =
{ ASN1_OID_BASICCONSTRAINTS };
@@ -724,6 +751,10 @@ static uint8_t oid_ce_ext_key_usage[] =
static uint8_t oid_pe_authority_info_access[] =
{ ASN1_OID_AUTHORITYINFOACCESS };
+/** "id-ce-subjectAltName" object identifier */
+static uint8_t oid_ce_subject_alt_name[] =
+ { ASN1_OID_SUBJECTALTNAME };
+
/** Supported certificate extensions */
static struct x509_extension x509_extensions[] = {
{
@@ -746,6 +777,11 @@ static struct x509_extension x509_extensions[] = {
.oid = ASN1_OID_CURSOR ( oid_pe_authority_info_access ),
.parse = x509_parse_authority_info_access,
},
+ {
+ .name = "subjectAltName",
+ .oid = ASN1_OID_CURSOR ( oid_ce_subject_alt_name ),
+ .parse = x509_parse_subject_alt_name,
+ },
};
/**
@@ -1341,6 +1377,82 @@ int x509_validate ( struct x509_certificate *cert,
}
/**
+ * Check X.509 certificate alternative dNSName
+ *
+ * @v cert X.509 certificate
+ * @v raw ASN.1 cursor
+ * @v name Name
+ * @ret rc Return status code
+ */
+static int x509_check_dnsname ( struct x509_certificate *cert,
+ const struct asn1_cursor *raw,
+ const char *name ) {
+ const char *fullname = name;
+ const char *dnsname = raw->data;
+ size_t len = raw->len;
+
+ /* Check for wildcards */
+ if ( ( len >= 2 ) && ( dnsname[0] == '*' ) && ( dnsname[1] == '.' ) ) {
+
+ /* Skip initial "*." */
+ dnsname += 2;
+ len -= 2;
+
+ /* Skip initial portion of name to be tested */
+ name = strchr ( name, '.' );
+ if ( ! name )
+ return -ENOENT;
+ name++;
+ }
+
+ /* Compare names */
+ if ( ! ( ( strlen ( name ) == len ) &&
+ ( memcmp ( name, dnsname, len ) == 0 ) ) )
+ return -ENOENT;
+
+ if ( name == fullname ) {
+ DBGC2 ( cert, "X509 %p \"%s\" subjectAltName matches \"%s\"\n",
+ cert, x509_name ( cert ), name );
+ } else {
+ DBGC2 ( cert, "X509 %p \"%s\" subjectAltName matches \"%s\" "
+ "(via \"*.%s\")\n", cert, x509_name ( cert ),
+ fullname, name );
+ }
+ return 0;
+}
+
+/**
+ * Check X.509 certificate alternative name
+ *
+ * @v cert X.509 certificate
+ * @v raw ASN.1 cursor
+ * @v name Name
+ * @ret rc Return status code
+ */
+static int x509_check_alt_name ( struct x509_certificate *cert,
+ const struct asn1_cursor *raw,
+ const char *name ) {
+ struct asn1_cursor alt_name;
+ unsigned int type;
+
+ /* Enter generalName */
+ memcpy ( &alt_name, raw, sizeof ( alt_name ) );
+ type = asn1_type ( &alt_name );
+ asn1_enter_any ( &alt_name );
+
+ /* Check this name */
+ switch ( type ) {
+ case X509_GENERAL_NAME_DNS :
+ return x509_check_dnsname ( cert, &alt_name, name );
+ default:
+ DBGC2 ( cert, "X509 %p \"%s\" unknown name of type %#02x:\n",
+ cert, x509_name ( cert ), type );
+ DBGC2_HDA ( cert, 0, alt_name.data, alt_name.len );
+ return -ENOTSUP;
+ }
+}
+
+/**
* Check X.509 certificate name
*
* @v cert X.509 certificate
@@ -1349,17 +1461,29 @@ int x509_validate ( struct x509_certificate *cert,
*/
int x509_check_name ( struct x509_certificate *cert, const char *name ) {
struct asn1_cursor *common_name = &cert->subject.common_name;
- size_t len = strlen ( name );
+ struct asn1_cursor alt_name;
+ int rc;
/* Check commonName */
- if ( ! ( ( len == common_name->len ) &&
- ( memcmp ( name, common_name->data, len ) == 0 ) ) ) {
- DBGC ( cert, "X509 %p \"%s\" does not match name \"%s\"\n",
- cert, x509_name ( cert ), name );
- return -EACCES_WRONG_NAME;
+ if ( ( strlen ( name ) == common_name->len ) &&
+ ( memcmp ( name, common_name->data, common_name->len ) == 0 ) ) {
+ DBGC2 ( cert, "X509 %p \"%s\" commonName matches \"%s\"\n",
+ cert, x509_name ( cert ), name );
+ return 0;
}
- return 0;
+ /* Check any subjectAlternativeNames */
+ memcpy ( &alt_name, &cert->extensions.alt_name.names,
+ sizeof ( alt_name ) );
+ for ( ; alt_name.len ; asn1_skip_any ( &alt_name ) ) {
+ if ( ( rc = x509_check_alt_name ( cert, &alt_name,
+ name ) ) == 0 )
+ return 0;
+ }
+
+ DBGC ( cert, "X509 %p \"%s\" does not match name \"%s\"\n",
+ cert, x509_name ( cert ), name );
+ return -EACCES_WRONG_NAME;
}
/**
diff --git a/src/include/ipxe/asn1.h b/src/include/ipxe/asn1.h
index 3e73b59c..d12524dd 100644
--- a/src/include/ipxe/asn1.h
+++ b/src/include/ipxe/asn1.h
@@ -222,6 +222,11 @@ struct asn1_builder_header {
ASN1_OID_SINGLE ( 5 ), ASN1_OID_SINGLE ( 7 ), \
ASN1_OID_SINGLE ( 3 ), ASN1_OID_SINGLE ( 9 )
+/** ASN.1 OID for id-ce-subjectAltName (2.5.29.17) */
+#define ASN1_OID_SUBJECTALTNAME \
+ ASN1_OID_INITIAL ( 2, 5 ), ASN1_OID_SINGLE ( 29 ), \
+ ASN1_OID_SINGLE ( 17 )
+
/** Define an ASN.1 cursor containing an OID */
#define ASN1_OID_CURSOR( oid_value ) { \
.data = oid_value, \
diff --git a/src/include/ipxe/x509.h b/src/include/ipxe/x509.h
index c9254723..055a4460 100644
--- a/src/include/ipxe/x509.h
+++ b/src/include/ipxe/x509.h
@@ -136,6 +136,18 @@ struct x509_authority_info_access {
struct x509_ocsp_responder ocsp;
};
+/** X.509 certificate subject alternative name */
+struct x509_subject_alt_name {
+ /** Names */
+ struct asn1_cursor names;
+};
+
+/** X.509 certificate general name types */
+enum x509_general_name_types {
+ X509_GENERAL_NAME_DNS = ASN1_IMPLICIT_TAG ( 2 ),
+ X509_GENERAL_NAME_URI = ASN1_IMPLICIT_TAG ( 6 ),
+};
+
/** An X.509 certificate extensions set */
struct x509_extensions {
/** Basic constraints */
@@ -146,6 +158,8 @@ struct x509_extensions {
struct x509_extended_key_usage ext_usage;
/** Authority information access */
struct x509_authority_info_access auth_info;
+ /** Subject alternative name */
+ struct x509_subject_alt_name alt_name;
};
/** A link in an X.509 certificate chain */
diff --git a/src/tests/x509_test.c b/src/tests/x509_test.c
index 69ede4c8..d3e01faf 100644
--- a/src/tests/x509_test.c
+++ b/src/tests/x509_test.c
@@ -1023,7 +1023,16 @@ static void x509_test_exec ( void ) {
/* Check certificate names */
x509_check_name_ok ( &server_crt, "boot.test.ipxe.org" );
+ x509_check_name_ok ( &server_crt, "demo.test.ipxe.org" );
x509_check_name_fail_ok ( &server_crt, "incorrect.test.ipxe.org" );
+ x509_check_name_ok ( &server_crt, "anything.alt.test.ipxe.org" );
+ x509_check_name_ok ( &server_crt, "wildcard.alt.test.ipxe.org" );
+ x509_check_name_fail_ok ( &server_crt, "sub.domain.alt.test.ipxe.org" );
+ x509_check_name_fail_ok ( &server_crt, "alt.test.ipxe.org" );
+ x509_check_name_fail_ok ( &server_crt, "test.ipxe.org" );
+ x509_check_name_fail_ok ( &server_crt, "ipxe.org" );
+ x509_check_name_fail_ok ( &server_crt, "org" );
+ x509_check_name_fail_ok ( &server_crt, "" );
/* Parse all certificate chains */
x509_chain_ok ( &server_chain );