]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[crypto] Add support for subjectAltName and wildcard certificates
authorMichael Brown <mcb30@ipxe.org>
Mon, 31 Mar 2014 00:11:06 +0000 (01:11 +0100)
committerMichael Brown <mcb30@ipxe.org>
Mon, 31 Mar 2014 12:36:54 +0000 (13:36 +0100)
Originally-implemented-by: Alex Chernyakhovsky <achernya@google.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/crypto/x509.c
src/include/ipxe/asn1.h
src/include/ipxe/x509.h
src/tests/x509_test.c

index 87b924c84f9553fa0f5caa2a8a30d4ea710bea8a..8e503f3b8c6eede9c4ae46e3f372f059597509eb 100644 (file)
@@ -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,
+       },
 };
 
 /**
@@ -1340,6 +1376,82 @@ int x509_validate ( struct x509_certificate *cert,
        return 0;
 }
 
+/**
+ * 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
  *
@@ -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;
 }
 
 /**
index 3e73b59c7817fbeeeb90a07252d4d06ba6fdf582..d12524ddb22bc2de105efbce9cd389aca09e2629 100644 (file)
@@ -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,                              \
index c925472373b54dd8ceb7745306f6816bb857f77c..055a4460e9c00443a6aefa7055d2fd86f7bdf879 100644 (file)
@@ -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 */
index 69ede4c8543d4e0e539beffc6484cf292908e609..d3e01faf1ed0b856fcdc9e785d43f95886d9ceac 100644 (file)
@@ -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 );