| /* |
| * X.509v3 certificate parsing and processing (RFC 3280 profile) |
| * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| |
| #include "common.h" |
| #include "crypto/crypto.h" |
| #include "asn1.h" |
| #include "x509v3.h" |
| |
| |
| static void x509_free_name(struct x509_name *name) |
| { |
| size_t i; |
| |
| for (i = 0; i < name->num_attr; i++) { |
| os_free(name->attr[i].value); |
| name->attr[i].value = NULL; |
| name->attr[i].type = X509_NAME_ATTR_NOT_USED; |
| } |
| name->num_attr = 0; |
| os_free(name->email); |
| name->email = NULL; |
| |
| os_free(name->alt_email); |
| os_free(name->dns); |
| os_free(name->uri); |
| os_free(name->ip); |
| name->alt_email = name->dns = name->uri = NULL; |
| name->ip = NULL; |
| name->ip_len = 0; |
| os_memset(&name->rid, 0, sizeof(name->rid)); |
| } |
| |
| |
| /** |
| * x509_certificate_free - Free an X.509 certificate |
| * @cert: Certificate to be freed |
| */ |
| void x509_certificate_free(struct x509_certificate *cert) |
| { |
| if (cert == NULL) |
| return; |
| if (cert->next) { |
| wpa_printf(MSG_DEBUG, "X509: x509_certificate_free: cer=%p " |
| "was still on a list (next=%p)\n", |
| cert, cert->next); |
| } |
| x509_free_name(&cert->issuer); |
| x509_free_name(&cert->subject); |
| os_free(cert->public_key); |
| os_free(cert->sign_value); |
| os_free(cert); |
| } |
| |
| |
| /** |
| * x509_certificate_free - Free an X.509 certificate chain |
| * @cert: Pointer to the first certificate in the chain |
| */ |
| void x509_certificate_chain_free(struct x509_certificate *cert) |
| { |
| struct x509_certificate *next; |
| |
| while (cert) { |
| next = cert->next; |
| cert->next = NULL; |
| x509_certificate_free(cert); |
| cert = next; |
| } |
| } |
| |
| |
| static int x509_whitespace(char c) |
| { |
| return c == ' ' || c == '\t'; |
| } |
| |
| |
| static void x509_str_strip_whitespace(char *a) |
| { |
| char *ipos, *opos; |
| int remove_whitespace = 1; |
| |
| ipos = opos = a; |
| |
| while (*ipos) { |
| if (remove_whitespace && x509_whitespace(*ipos)) |
| ipos++; |
| else { |
| remove_whitespace = x509_whitespace(*ipos); |
| *opos++ = *ipos++; |
| } |
| } |
| |
| *opos-- = '\0'; |
| if (opos > a && x509_whitespace(*opos)) |
| *opos = '\0'; |
| } |
| |
| |
| static int x509_str_compare(const char *a, const char *b) |
| { |
| char *aa, *bb; |
| int ret; |
| |
| if (!a && b) |
| return -1; |
| if (a && !b) |
| return 1; |
| if (!a && !b) |
| return 0; |
| |
| aa = os_strdup(a); |
| bb = os_strdup(b); |
| |
| if (aa == NULL || bb == NULL) { |
| os_free(aa); |
| os_free(bb); |
| return os_strcasecmp(a, b); |
| } |
| |
| x509_str_strip_whitespace(aa); |
| x509_str_strip_whitespace(bb); |
| |
| ret = os_strcasecmp(aa, bb); |
| |
| os_free(aa); |
| os_free(bb); |
| |
| return ret; |
| } |
| |
| |
| /** |
| * x509_name_compare - Compare X.509 certificate names |
| * @a: Certificate name |
| * @b: Certificate name |
| * Returns: <0, 0, or >0 based on whether a is less than, equal to, or |
| * greater than b |
| */ |
| int x509_name_compare(struct x509_name *a, struct x509_name *b) |
| { |
| int res; |
| size_t i; |
| |
| if (!a && b) |
| return -1; |
| if (a && !b) |
| return 1; |
| if (!a && !b) |
| return 0; |
| if (a->num_attr < b->num_attr) |
| return -1; |
| if (a->num_attr > b->num_attr) |
| return 1; |
| |
| for (i = 0; i < a->num_attr; i++) { |
| if (a->attr[i].type < b->attr[i].type) |
| return -1; |
| if (a->attr[i].type > b->attr[i].type) |
| return -1; |
| res = x509_str_compare(a->attr[i].value, b->attr[i].value); |
| if (res) |
| return res; |
| } |
| res = x509_str_compare(a->email, b->email); |
| if (res) |
| return res; |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_algorithm_identifier( |
| const u8 *buf, size_t len, |
| struct x509_algorithm_identifier *id, const u8 **next) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos, *end; |
| |
| /* |
| * AlgorithmIdentifier ::= SEQUENCE { |
| * algorithm OBJECT IDENTIFIER, |
| * parameters ANY DEFINED BY algorithm OPTIONAL |
| * } |
| */ |
| |
| if (asn1_get_next(buf, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(AlgorithmIdentifier) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| end = pos + hdr.length; |
| |
| if (end > buf + len) |
| return -1; |
| |
| *next = end; |
| |
| if (asn1_get_oid(pos, end - pos, &id->oid, &pos)) |
| return -1; |
| |
| /* TODO: optional parameters */ |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_public_key(const u8 *buf, size_t len, |
| struct x509_certificate *cert, |
| const u8 **next) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos, *end; |
| |
| /* |
| * SubjectPublicKeyInfo ::= SEQUENCE { |
| * algorithm AlgorithmIdentifier, |
| * subjectPublicKey BIT STRING |
| * } |
| */ |
| |
| pos = buf; |
| end = buf + len; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(SubjectPublicKeyInfo) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| |
| if (pos + hdr.length > end) |
| return -1; |
| end = pos + hdr.length; |
| *next = end; |
| |
| if (x509_parse_algorithm_identifier(pos, end - pos, |
| &cert->public_key_alg, &pos)) |
| return -1; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_BITSTRING) { |
| wpa_printf(MSG_DEBUG, "X509: Expected BITSTRING " |
| "(subjectPublicKey) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| if (hdr.length < 1) |
| return -1; |
| pos = hdr.payload; |
| if (*pos) { |
| wpa_printf(MSG_DEBUG, "X509: BITSTRING - %d unused bits", |
| *pos); |
| /* |
| * TODO: should this be rejected? X.509 certificates are |
| * unlikely to use such a construction. Now we would end up |
| * including the extra bits in the buffer which may also be |
| * ok. |
| */ |
| } |
| os_free(cert->public_key); |
| cert->public_key = os_malloc(hdr.length - 1); |
| if (cert->public_key == NULL) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to allocate memory for " |
| "public key"); |
| return -1; |
| } |
| os_memcpy(cert->public_key, pos + 1, hdr.length - 1); |
| cert->public_key_len = hdr.length - 1; |
| wpa_hexdump(MSG_MSGDUMP, "X509: subjectPublicKey", |
| cert->public_key, cert->public_key_len); |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_name(const u8 *buf, size_t len, struct x509_name *name, |
| const u8 **next) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos, *end, *set_pos, *set_end, *seq_pos, *seq_end; |
| struct asn1_oid oid; |
| char *val; |
| |
| /* |
| * Name ::= CHOICE { RDNSequence } |
| * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName |
| * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue |
| * AttributeTypeAndValue ::= SEQUENCE { |
| * type AttributeType, |
| * value AttributeValue |
| * } |
| * AttributeType ::= OBJECT IDENTIFIER |
| * AttributeValue ::= ANY DEFINED BY AttributeType |
| */ |
| |
| if (asn1_get_next(buf, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(Name / RDNSequencer) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| |
| if (pos + hdr.length > buf + len) |
| return -1; |
| |
| end = *next = pos + hdr.length; |
| |
| while (pos < end) { |
| enum x509_name_attr_type type; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SET) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SET " |
| "(RelativeDistinguishedName) - found class " |
| "%d tag 0x%x", hdr.class, hdr.tag); |
| x509_free_name(name); |
| return -1; |
| } |
| |
| set_pos = hdr.payload; |
| pos = set_end = hdr.payload + hdr.length; |
| |
| if (asn1_get_next(set_pos, set_end - set_pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(AttributeTypeAndValue) - found class %d " |
| "tag 0x%x", hdr.class, hdr.tag); |
| x509_free_name(name); |
| return -1; |
| } |
| |
| seq_pos = hdr.payload; |
| seq_end = hdr.payload + hdr.length; |
| |
| if (asn1_get_oid(seq_pos, seq_end - seq_pos, &oid, &seq_pos)) { |
| x509_free_name(name); |
| return -1; |
| } |
| |
| if (asn1_get_next(seq_pos, seq_end - seq_pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to parse " |
| "AttributeValue"); |
| x509_free_name(name); |
| return -1; |
| } |
| |
| /* RFC 3280: |
| * MUST: country, organization, organizational-unit, |
| * distinguished name qualifier, state or province name, |
| * common name, serial number. |
| * SHOULD: locality, title, surname, given name, initials, |
| * pseudonym, generation qualifier. |
| * MUST: domainComponent (RFC 2247). |
| */ |
| type = X509_NAME_ATTR_NOT_USED; |
| if (oid.len == 4 && |
| oid.oid[0] == 2 && oid.oid[1] == 5 && oid.oid[2] == 4) { |
| /* id-at ::= 2.5.4 */ |
| switch (oid.oid[3]) { |
| case 3: |
| /* commonName */ |
| type = X509_NAME_ATTR_CN; |
| break; |
| case 6: |
| /* countryName */ |
| type = X509_NAME_ATTR_C; |
| break; |
| case 7: |
| /* localityName */ |
| type = X509_NAME_ATTR_L; |
| break; |
| case 8: |
| /* stateOrProvinceName */ |
| type = X509_NAME_ATTR_ST; |
| break; |
| case 10: |
| /* organizationName */ |
| type = X509_NAME_ATTR_O; |
| break; |
| case 11: |
| /* organizationalUnitName */ |
| type = X509_NAME_ATTR_OU; |
| break; |
| } |
| } else if (oid.len == 7 && |
| oid.oid[0] == 1 && oid.oid[1] == 2 && |
| oid.oid[2] == 840 && oid.oid[3] == 113549 && |
| oid.oid[4] == 1 && oid.oid[5] == 9 && |
| oid.oid[6] == 1) { |
| /* 1.2.840.113549.1.9.1 - e-mailAddress */ |
| os_free(name->email); |
| name->email = os_malloc(hdr.length + 1); |
| if (name->email == NULL) { |
| x509_free_name(name); |
| return -1; |
| } |
| os_memcpy(name->email, hdr.payload, hdr.length); |
| name->email[hdr.length] = '\0'; |
| continue; |
| } else if (oid.len == 7 && |
| oid.oid[0] == 0 && oid.oid[1] == 9 && |
| oid.oid[2] == 2342 && oid.oid[3] == 19200300 && |
| oid.oid[4] == 100 && oid.oid[5] == 1 && |
| oid.oid[6] == 25) { |
| /* 0.9.2342.19200300.100.1.25 - domainComponent */ |
| type = X509_NAME_ATTR_DC; |
| } |
| |
| if (type == X509_NAME_ATTR_NOT_USED) { |
| wpa_hexdump(MSG_DEBUG, "X509: Unrecognized OID", |
| (u8 *) oid.oid, |
| oid.len * sizeof(oid.oid[0])); |
| wpa_hexdump_ascii(MSG_MSGDUMP, "X509: Attribute Data", |
| hdr.payload, hdr.length); |
| continue; |
| } |
| |
| if (name->num_attr == X509_MAX_NAME_ATTRIBUTES) { |
| wpa_printf(MSG_INFO, "X509: Too many Name attributes"); |
| x509_free_name(name); |
| return -1; |
| } |
| |
| val = os_malloc(hdr.length + 1); |
| if (val == NULL) { |
| x509_free_name(name); |
| return -1; |
| } |
| os_memcpy(val, hdr.payload, hdr.length); |
| val[hdr.length] = '\0'; |
| if (os_strlen(val) != hdr.length) { |
| wpa_printf(MSG_INFO, "X509: Reject certificate with " |
| "embedded NUL byte in a string (%s[NUL])", |
| val); |
| x509_free_name(name); |
| return -1; |
| } |
| |
| name->attr[name->num_attr].type = type; |
| name->attr[name->num_attr].value = val; |
| name->num_attr++; |
| } |
| |
| return 0; |
| } |
| |
| |
| static char * x509_name_attr_str(enum x509_name_attr_type type) |
| { |
| switch (type) { |
| case X509_NAME_ATTR_NOT_USED: |
| return "[N/A]"; |
| case X509_NAME_ATTR_DC: |
| return "DC"; |
| case X509_NAME_ATTR_CN: |
| return "CN"; |
| case X509_NAME_ATTR_C: |
| return "C"; |
| case X509_NAME_ATTR_L: |
| return "L"; |
| case X509_NAME_ATTR_ST: |
| return "ST"; |
| case X509_NAME_ATTR_O: |
| return "O"; |
| case X509_NAME_ATTR_OU: |
| return "OU"; |
| } |
| return "?"; |
| } |
| |
| |
| /** |
| * x509_name_string - Convert an X.509 certificate name into a string |
| * @name: Name to convert |
| * @buf: Buffer for the string |
| * @len: Maximum buffer length |
| */ |
| void x509_name_string(struct x509_name *name, char *buf, size_t len) |
| { |
| char *pos, *end; |
| int ret; |
| size_t i; |
| |
| if (len == 0) |
| return; |
| |
| pos = buf; |
| end = buf + len; |
| |
| for (i = 0; i < name->num_attr; i++) { |
| ret = os_snprintf(pos, end - pos, "%s=%s, ", |
| x509_name_attr_str(name->attr[i].type), |
| name->attr[i].value); |
| if (ret < 0 || ret >= end - pos) |
| goto done; |
| pos += ret; |
| } |
| |
| if (pos > buf + 1 && pos[-1] == ' ' && pos[-2] == ',') { |
| pos--; |
| *pos = '\0'; |
| pos--; |
| *pos = '\0'; |
| } |
| |
| if (name->email) { |
| ret = os_snprintf(pos, end - pos, "/emailAddress=%s", |
| name->email); |
| if (ret < 0 || ret >= end - pos) |
| goto done; |
| pos += ret; |
| } |
| |
| done: |
| end[-1] = '\0'; |
| } |
| |
| |
| static int x509_parse_time(const u8 *buf, size_t len, u8 asn1_tag, |
| os_time_t *val) |
| { |
| const char *pos; |
| int year, month, day, hour, min, sec; |
| |
| /* |
| * Time ::= CHOICE { |
| * utcTime UTCTime, |
| * generalTime GeneralizedTime |
| * } |
| * |
| * UTCTime: YYMMDDHHMMSSZ |
| * GeneralizedTime: YYYYMMDDHHMMSSZ |
| */ |
| |
| pos = (const char *) buf; |
| |
| switch (asn1_tag) { |
| case ASN1_TAG_UTCTIME: |
| if (len != 13 || buf[12] != 'Z') { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Unrecognized " |
| "UTCTime format", buf, len); |
| return -1; |
| } |
| if (sscanf(pos, "%02d", &year) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse " |
| "UTCTime year", buf, len); |
| return -1; |
| } |
| if (year < 50) |
| year += 2000; |
| else |
| year += 1900; |
| pos += 2; |
| break; |
| case ASN1_TAG_GENERALIZEDTIME: |
| if (len != 15 || buf[14] != 'Z') { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Unrecognized " |
| "GeneralizedTime format", buf, len); |
| return -1; |
| } |
| if (sscanf(pos, "%04d", &year) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse " |
| "GeneralizedTime year", buf, len); |
| return -1; |
| } |
| pos += 4; |
| break; |
| default: |
| wpa_printf(MSG_DEBUG, "X509: Expected UTCTime or " |
| "GeneralizedTime - found tag 0x%x", asn1_tag); |
| return -1; |
| } |
| |
| if (sscanf(pos, "%02d", &month) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time " |
| "(month)", buf, len); |
| return -1; |
| } |
| pos += 2; |
| |
| if (sscanf(pos, "%02d", &day) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time " |
| "(day)", buf, len); |
| return -1; |
| } |
| pos += 2; |
| |
| if (sscanf(pos, "%02d", &hour) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time " |
| "(hour)", buf, len); |
| return -1; |
| } |
| pos += 2; |
| |
| if (sscanf(pos, "%02d", &min) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time " |
| "(min)", buf, len); |
| return -1; |
| } |
| pos += 2; |
| |
| if (sscanf(pos, "%02d", &sec) != 1) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time " |
| "(sec)", buf, len); |
| return -1; |
| } |
| |
| if (os_mktime(year, month, day, hour, min, sec, val) < 0) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to convert Time", |
| buf, len); |
| if (year < 1970) { |
| /* |
| * At least some test certificates have been configured |
| * to use dates prior to 1970. Set the date to |
| * beginning of 1970 to handle these case. |
| */ |
| wpa_printf(MSG_DEBUG, "X509: Year=%d before epoch - " |
| "assume epoch as the time", year); |
| *val = 0; |
| return 0; |
| } |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_validity(const u8 *buf, size_t len, |
| struct x509_certificate *cert, const u8 **next) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos; |
| size_t plen; |
| |
| /* |
| * Validity ::= SEQUENCE { |
| * notBefore Time, |
| * notAfter Time |
| * } |
| * |
| * RFC 3280, 4.1.2.5: |
| * CAs conforming to this profile MUST always encode certificate |
| * validity dates through the year 2049 as UTCTime; certificate |
| * validity dates in 2050 or later MUST be encoded as GeneralizedTime. |
| */ |
| |
| if (asn1_get_next(buf, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(Validity) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| plen = hdr.length; |
| |
| if (pos + plen > buf + len) |
| return -1; |
| |
| *next = pos + plen; |
| |
| if (asn1_get_next(pos, plen, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| x509_parse_time(hdr.payload, hdr.length, hdr.tag, |
| &cert->not_before) < 0) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse notBefore " |
| "Time", hdr.payload, hdr.length); |
| return -1; |
| } |
| |
| pos = hdr.payload + hdr.length; |
| plen = *next - pos; |
| |
| if (asn1_get_next(pos, plen, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| x509_parse_time(hdr.payload, hdr.length, hdr.tag, |
| &cert->not_after) < 0) { |
| wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse notAfter " |
| "Time", hdr.payload, hdr.length); |
| return -1; |
| } |
| |
| wpa_printf(MSG_MSGDUMP, "X509: Validity: notBefore: %lu notAfter: %lu", |
| (unsigned long) cert->not_before, |
| (unsigned long) cert->not_after); |
| |
| return 0; |
| } |
| |
| |
| static int x509_id_ce_oid(struct asn1_oid *oid) |
| { |
| /* id-ce arc from X.509 for standard X.509v3 extensions */ |
| return oid->len >= 4 && |
| oid->oid[0] == 2 /* joint-iso-ccitt */ && |
| oid->oid[1] == 5 /* ds */ && |
| oid->oid[2] == 29 /* id-ce */; |
| } |
| |
| |
| static int x509_parse_ext_key_usage(struct x509_certificate *cert, |
| const u8 *pos, size_t len) |
| { |
| struct asn1_hdr hdr; |
| |
| /* |
| * KeyUsage ::= BIT STRING { |
| * digitalSignature (0), |
| * nonRepudiation (1), |
| * keyEncipherment (2), |
| * dataEncipherment (3), |
| * keyAgreement (4), |
| * keyCertSign (5), |
| * cRLSign (6), |
| * encipherOnly (7), |
| * decipherOnly (8) } |
| */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_BITSTRING || |
| hdr.length < 1) { |
| wpa_printf(MSG_DEBUG, "X509: Expected BIT STRING in " |
| "KeyUsage; found %d tag 0x%x len %d", |
| hdr.class, hdr.tag, hdr.length); |
| return -1; |
| } |
| |
| cert->extensions_present |= X509_EXT_KEY_USAGE; |
| cert->key_usage = asn1_bit_string_to_long(hdr.payload, hdr.length); |
| |
| wpa_printf(MSG_DEBUG, "X509: KeyUsage 0x%lx", cert->key_usage); |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_ext_basic_constraints(struct x509_certificate *cert, |
| const u8 *pos, size_t len) |
| { |
| struct asn1_hdr hdr; |
| unsigned long value; |
| size_t left; |
| |
| /* |
| * BasicConstraints ::= SEQUENCE { |
| * cA BOOLEAN DEFAULT FALSE, |
| * pathLenConstraint INTEGER (0..MAX) OPTIONAL } |
| */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in " |
| "BasicConstraints; found %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| cert->extensions_present |= X509_EXT_BASIC_CONSTRAINTS; |
| |
| if (hdr.length == 0) |
| return 0; |
| |
| if (asn1_get_next(hdr.payload, hdr.length, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to parse " |
| "BasicConstraints"); |
| return -1; |
| } |
| |
| if (hdr.tag == ASN1_TAG_BOOLEAN) { |
| if (hdr.length != 1) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected " |
| "Boolean length (%u) in BasicConstraints", |
| hdr.length); |
| return -1; |
| } |
| cert->ca = hdr.payload[0]; |
| |
| if (hdr.payload + hdr.length == pos + len) { |
| wpa_printf(MSG_DEBUG, "X509: BasicConstraints - cA=%d", |
| cert->ca); |
| return 0; |
| } |
| |
| if (asn1_get_next(hdr.payload + hdr.length, len - hdr.length, |
| &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to parse " |
| "BasicConstraints"); |
| return -1; |
| } |
| } |
| |
| if (hdr.tag != ASN1_TAG_INTEGER) { |
| wpa_printf(MSG_DEBUG, "X509: Expected INTEGER in " |
| "BasicConstraints; found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| pos = hdr.payload; |
| left = hdr.length; |
| value = 0; |
| while (left) { |
| value <<= 8; |
| value |= *pos++; |
| left--; |
| } |
| |
| cert->path_len_constraint = value; |
| cert->extensions_present |= X509_EXT_PATH_LEN_CONSTRAINT; |
| |
| wpa_printf(MSG_DEBUG, "X509: BasicConstraints - cA=%d " |
| "pathLenConstraint=%lu", |
| cert->ca, cert->path_len_constraint); |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_alt_name_rfc8222(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| /* rfc822Name IA5String */ |
| wpa_hexdump_ascii(MSG_MSGDUMP, "X509: altName - rfc822Name", pos, len); |
| os_free(name->alt_email); |
| name->alt_email = os_zalloc(len + 1); |
| if (name->alt_email == NULL) |
| return -1; |
| os_memcpy(name->alt_email, pos, len); |
| if (os_strlen(name->alt_email) != len) { |
| wpa_printf(MSG_INFO, "X509: Reject certificate with " |
| "embedded NUL byte in rfc822Name (%s[NUL])", |
| name->alt_email); |
| os_free(name->alt_email); |
| name->alt_email = NULL; |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| static int x509_parse_alt_name_dns(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| /* dNSName IA5String */ |
| wpa_hexdump_ascii(MSG_MSGDUMP, "X509: altName - dNSName", pos, len); |
| os_free(name->dns); |
| name->dns = os_zalloc(len + 1); |
| if (name->dns == NULL) |
| return -1; |
| os_memcpy(name->dns, pos, len); |
| if (os_strlen(name->dns) != len) { |
| wpa_printf(MSG_INFO, "X509: Reject certificate with " |
| "embedded NUL byte in dNSName (%s[NUL])", |
| name->dns); |
| os_free(name->dns); |
| name->dns = NULL; |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| static int x509_parse_alt_name_uri(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| /* uniformResourceIdentifier IA5String */ |
| wpa_hexdump_ascii(MSG_MSGDUMP, |
| "X509: altName - uniformResourceIdentifier", |
| pos, len); |
| os_free(name->uri); |
| name->uri = os_zalloc(len + 1); |
| if (name->uri == NULL) |
| return -1; |
| os_memcpy(name->uri, pos, len); |
| if (os_strlen(name->uri) != len) { |
| wpa_printf(MSG_INFO, "X509: Reject certificate with " |
| "embedded NUL byte in uniformResourceIdentifier " |
| "(%s[NUL])", name->uri); |
| os_free(name->uri); |
| name->uri = NULL; |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| static int x509_parse_alt_name_ip(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| /* iPAddress OCTET STRING */ |
| wpa_hexdump(MSG_MSGDUMP, "X509: altName - iPAddress", pos, len); |
| os_free(name->ip); |
| name->ip = os_malloc(len); |
| if (name->ip == NULL) |
| return -1; |
| os_memcpy(name->ip, pos, len); |
| name->ip_len = len; |
| return 0; |
| } |
| |
| |
| static int x509_parse_alt_name_rid(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| char buf[80]; |
| |
| /* registeredID OBJECT IDENTIFIER */ |
| if (asn1_parse_oid(pos, len, &name->rid) < 0) |
| return -1; |
| |
| asn1_oid_to_str(&name->rid, buf, sizeof(buf)); |
| wpa_printf(MSG_MSGDUMP, "X509: altName - registeredID: %s", buf); |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_ext_alt_name(struct x509_name *name, |
| const u8 *pos, size_t len) |
| { |
| struct asn1_hdr hdr; |
| const u8 *p, *end; |
| |
| /* |
| * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName |
| * |
| * GeneralName ::= CHOICE { |
| * otherName [0] OtherName, |
| * rfc822Name [1] IA5String, |
| * dNSName [2] IA5String, |
| * x400Address [3] ORAddress, |
| * directoryName [4] Name, |
| * ediPartyName [5] EDIPartyName, |
| * uniformResourceIdentifier [6] IA5String, |
| * iPAddress [7] OCTET STRING, |
| * registeredID [8] OBJECT IDENTIFIER } |
| * |
| * OtherName ::= SEQUENCE { |
| * type-id OBJECT IDENTIFIER, |
| * value [0] EXPLICIT ANY DEFINED BY type-id } |
| * |
| * EDIPartyName ::= SEQUENCE { |
| * nameAssigner [0] DirectoryString OPTIONAL, |
| * partyName [1] DirectoryString } |
| */ |
| |
| for (p = pos, end = pos + len; p < end; p = hdr.payload + hdr.length) { |
| int res; |
| |
| if (asn1_get_next(p, end - p, &hdr) < 0) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to parse " |
| "SubjectAltName item"); |
| return -1; |
| } |
| |
| if (hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) |
| continue; |
| |
| switch (hdr.tag) { |
| case 1: |
| res = x509_parse_alt_name_rfc8222(name, hdr.payload, |
| hdr.length); |
| break; |
| case 2: |
| res = x509_parse_alt_name_dns(name, hdr.payload, |
| hdr.length); |
| break; |
| case 6: |
| res = x509_parse_alt_name_uri(name, hdr.payload, |
| hdr.length); |
| break; |
| case 7: |
| res = x509_parse_alt_name_ip(name, hdr.payload, |
| hdr.length); |
| break; |
| case 8: |
| res = x509_parse_alt_name_rid(name, hdr.payload, |
| hdr.length); |
| break; |
| case 0: /* TODO: otherName */ |
| case 3: /* TODO: x500Address */ |
| case 4: /* TODO: directoryName */ |
| case 5: /* TODO: ediPartyName */ |
| default: |
| res = 0; |
| break; |
| } |
| if (res < 0) |
| return res; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_ext_subject_alt_name(struct x509_certificate *cert, |
| const u8 *pos, size_t len) |
| { |
| struct asn1_hdr hdr; |
| |
| /* SubjectAltName ::= GeneralNames */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in " |
| "SubjectAltName; found %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "X509: SubjectAltName"); |
| cert->extensions_present |= X509_EXT_SUBJECT_ALT_NAME; |
| |
| if (hdr.length == 0) |
| return 0; |
| |
| return x509_parse_ext_alt_name(&cert->subject, hdr.payload, |
| hdr.length); |
| } |
| |
| |
| static int x509_parse_ext_issuer_alt_name(struct x509_certificate *cert, |
| const u8 *pos, size_t len) |
| { |
| struct asn1_hdr hdr; |
| |
| /* IssuerAltName ::= GeneralNames */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE in " |
| "IssuerAltName; found %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "X509: IssuerAltName"); |
| cert->extensions_present |= X509_EXT_ISSUER_ALT_NAME; |
| |
| if (hdr.length == 0) |
| return 0; |
| |
| return x509_parse_ext_alt_name(&cert->issuer, hdr.payload, |
| hdr.length); |
| } |
| |
| |
| static int x509_parse_extension_data(struct x509_certificate *cert, |
| struct asn1_oid *oid, |
| const u8 *pos, size_t len) |
| { |
| if (!x509_id_ce_oid(oid)) |
| return 1; |
| |
| /* TODO: add other extensions required by RFC 3280, Ch 4.2: |
| * certificate policies (section 4.2.1.5) |
| * name constraints (section 4.2.1.11) |
| * policy constraints (section 4.2.1.12) |
| * extended key usage (section 4.2.1.13) |
| * inhibit any-policy (section 4.2.1.15) |
| */ |
| switch (oid->oid[3]) { |
| case 15: /* id-ce-keyUsage */ |
| return x509_parse_ext_key_usage(cert, pos, len); |
| case 17: /* id-ce-subjectAltName */ |
| return x509_parse_ext_subject_alt_name(cert, pos, len); |
| case 18: /* id-ce-issuerAltName */ |
| return x509_parse_ext_issuer_alt_name(cert, pos, len); |
| case 19: /* id-ce-basicConstraints */ |
| return x509_parse_ext_basic_constraints(cert, pos, len); |
| default: |
| return 1; |
| } |
| } |
| |
| |
| static int x509_parse_extension(struct x509_certificate *cert, |
| const u8 *pos, size_t len, const u8 **next) |
| { |
| const u8 *end; |
| struct asn1_hdr hdr; |
| struct asn1_oid oid; |
| int critical_ext = 0, res; |
| char buf[80]; |
| |
| /* |
| * Extension ::= SEQUENCE { |
| * extnID OBJECT IDENTIFIER, |
| * critical BOOLEAN DEFAULT FALSE, |
| * extnValue OCTET STRING |
| * } |
| */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header in " |
| "Extensions: class %d tag 0x%x; expected SEQUENCE", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| *next = end = pos + hdr.length; |
| |
| if (asn1_get_oid(pos, end - pos, &oid, &pos) < 0) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 data for " |
| "Extension (expected OID)"); |
| return -1; |
| } |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| (hdr.tag != ASN1_TAG_BOOLEAN && |
| hdr.tag != ASN1_TAG_OCTETSTRING)) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header in " |
| "Extensions: class %d tag 0x%x; expected BOOLEAN " |
| "or OCTET STRING", hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| if (hdr.tag == ASN1_TAG_BOOLEAN) { |
| if (hdr.length != 1) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected " |
| "Boolean length (%u)", hdr.length); |
| return -1; |
| } |
| critical_ext = hdr.payload[0]; |
| pos = hdr.payload; |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| (hdr.class != ASN1_CLASS_UNIVERSAL && |
| hdr.class != ASN1_CLASS_PRIVATE) || |
| hdr.tag != ASN1_TAG_OCTETSTRING) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 header " |
| "in Extensions: class %d tag 0x%x; " |
| "expected OCTET STRING", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| } |
| |
| asn1_oid_to_str(&oid, buf, sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "X509: Extension: extnID=%s critical=%d", |
| buf, critical_ext); |
| wpa_hexdump(MSG_MSGDUMP, "X509: extnValue", hdr.payload, hdr.length); |
| |
| res = x509_parse_extension_data(cert, &oid, hdr.payload, hdr.length); |
| if (res < 0) |
| return res; |
| if (res == 1 && critical_ext) { |
| wpa_printf(MSG_INFO, "X509: Unknown critical extension %s", |
| buf); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_extensions(struct x509_certificate *cert, |
| const u8 *pos, size_t len) |
| { |
| const u8 *end; |
| struct asn1_hdr hdr; |
| |
| /* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension */ |
| |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected ASN.1 data " |
| "for Extensions: class %d tag 0x%x; " |
| "expected SEQUENCE", hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| pos = hdr.payload; |
| end = pos + hdr.length; |
| |
| while (pos < end) { |
| if (x509_parse_extension(cert, pos, end - pos, &pos) |
| < 0) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int x509_parse_tbs_certificate(const u8 *buf, size_t len, |
| struct x509_certificate *cert, |
| const u8 **next) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos, *end; |
| size_t left; |
| char sbuf[128]; |
| unsigned long value; |
| |
| /* tbsCertificate TBSCertificate ::= SEQUENCE */ |
| if (asn1_get_next(buf, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: tbsCertificate did not start " |
| "with a valid SEQUENCE - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| pos = hdr.payload; |
| end = *next = pos + hdr.length; |
| |
| /* |
| * version [0] EXPLICIT Version DEFAULT v1 |
| * Version ::= INTEGER { v1(0), v2(1), v3(2) } |
| */ |
| if (asn1_get_next(pos, end - pos, &hdr) < 0) |
| return -1; |
| pos = hdr.payload; |
| |
| if (hdr.class == ASN1_CLASS_CONTEXT_SPECIFIC) { |
| if (asn1_get_next(pos, end - pos, &hdr) < 0) |
| return -1; |
| |
| if (hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_INTEGER) { |
| wpa_printf(MSG_DEBUG, "X509: No INTEGER tag found for " |
| "version field - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| if (hdr.length != 1) { |
| wpa_printf(MSG_DEBUG, "X509: Unexpected version field " |
| "length %u (expected 1)", hdr.length); |
| return -1; |
| } |
| pos = hdr.payload; |
| left = hdr.length; |
| value = 0; |
| while (left) { |
| value <<= 8; |
| value |= *pos++; |
| left--; |
| } |
| |
| cert->version = value; |
| if (cert->version != X509_CERT_V1 && |
| cert->version != X509_CERT_V2 && |
| cert->version != X509_CERT_V3) { |
| wpa_printf(MSG_DEBUG, "X509: Unsupported version %d", |
| cert->version + 1); |
| return -1; |
| } |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0) |
| return -1; |
| } else |
| cert->version = X509_CERT_V1; |
| wpa_printf(MSG_MSGDUMP, "X509: Version X.509v%d", cert->version + 1); |
| |
| /* serialNumber CertificateSerialNumber ::= INTEGER */ |
| if (hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_INTEGER) { |
| wpa_printf(MSG_DEBUG, "X509: No INTEGER tag found for " |
| "serialNumber; class=%d tag=0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| pos = hdr.payload; |
| left = hdr.length; |
| while (left) { |
| cert->serial_number <<= 8; |
| cert->serial_number |= *pos++; |
| left--; |
| } |
| wpa_printf(MSG_MSGDUMP, "X509: serialNumber %lu", cert->serial_number); |
| |
| /* signature AlgorithmIdentifier */ |
| if (x509_parse_algorithm_identifier(pos, end - pos, &cert->signature, |
| &pos)) |
| return -1; |
| |
| /* issuer Name */ |
| if (x509_parse_name(pos, end - pos, &cert->issuer, &pos)) |
| return -1; |
| x509_name_string(&cert->issuer, sbuf, sizeof(sbuf)); |
| wpa_printf(MSG_MSGDUMP, "X509: issuer %s", sbuf); |
| |
| /* validity Validity */ |
| if (x509_parse_validity(pos, end - pos, cert, &pos)) |
| return -1; |
| |
| /* subject Name */ |
| if (x509_parse_name(pos, end - pos, &cert->subject, &pos)) |
| return -1; |
| x509_name_string(&cert->subject, sbuf, sizeof(sbuf)); |
| wpa_printf(MSG_MSGDUMP, "X509: subject %s", sbuf); |
| |
| /* subjectPublicKeyInfo SubjectPublicKeyInfo */ |
| if (x509_parse_public_key(pos, end - pos, cert, &pos)) |
| return -1; |
| |
| if (pos == end) |
| return 0; |
| |
| if (cert->version == X509_CERT_V1) |
| return 0; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) { |
| wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific" |
| " tag to parse optional tbsCertificate " |
| "field(s); parsed class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| |
| if (hdr.tag == 1) { |
| /* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL */ |
| wpa_printf(MSG_DEBUG, "X509: issuerUniqueID"); |
| /* TODO: parse UniqueIdentifier ::= BIT STRING */ |
| |
| if (hdr.payload + hdr.length == end) |
| return 0; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) { |
| wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific" |
| " tag to parse optional tbsCertificate " |
| "field(s); parsed class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| } |
| |
| if (hdr.tag == 2) { |
| /* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL */ |
| wpa_printf(MSG_DEBUG, "X509: subjectUniqueID"); |
| /* TODO: parse UniqueIdentifier ::= BIT STRING */ |
| |
| if (hdr.payload + hdr.length == end) |
| return 0; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_CONTEXT_SPECIFIC) { |
| wpa_printf(MSG_DEBUG, "X509: Expected Context-Specific" |
| " tag to parse optional tbsCertificate " |
| "field(s); parsed class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| return -1; |
| } |
| } |
| |
| if (hdr.tag != 3) { |
| wpa_printf(MSG_DEBUG, "X509: Ignored unexpected " |
| "Context-Specific tag %d in optional " |
| "tbsCertificate fields", hdr.tag); |
| return 0; |
| } |
| |
| /* extensions [3] EXPLICIT Extensions OPTIONAL */ |
| |
| if (cert->version != X509_CERT_V3) { |
| wpa_printf(MSG_DEBUG, "X509: X.509%d certificate and " |
| "Extensions data which are only allowed for " |
| "version 3", cert->version + 1); |
| return -1; |
| } |
| |
| if (x509_parse_extensions(cert, hdr.payload, hdr.length) < 0) |
| return -1; |
| |
| pos = hdr.payload + hdr.length; |
| if (pos < end) { |
| wpa_hexdump(MSG_DEBUG, |
| "X509: Ignored extra tbsCertificate data", |
| pos, end - pos); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int x509_rsadsi_oid(struct asn1_oid *oid) |
| { |
| return oid->len >= 4 && |
| oid->oid[0] == 1 /* iso */ && |
| oid->oid[1] == 2 /* member-body */ && |
| oid->oid[2] == 840 /* us */ && |
| oid->oid[3] == 113549 /* rsadsi */; |
| } |
| |
| |
| static int x509_pkcs_oid(struct asn1_oid *oid) |
| { |
| return oid->len >= 5 && |
| x509_rsadsi_oid(oid) && |
| oid->oid[4] == 1 /* pkcs */; |
| } |
| |
| |
| static int x509_digest_oid(struct asn1_oid *oid) |
| { |
| return oid->len >= 5 && |
| x509_rsadsi_oid(oid) && |
| oid->oid[4] == 2 /* digestAlgorithm */; |
| } |
| |
| |
| static int x509_sha1_oid(struct asn1_oid *oid) |
| { |
| return oid->len == 6 && |
| oid->oid[0] == 1 /* iso */ && |
| oid->oid[1] == 3 /* identified-organization */ && |
| oid->oid[2] == 14 /* oiw */ && |
| oid->oid[3] == 3 /* secsig */ && |
| oid->oid[4] == 2 /* algorithms */ && |
| oid->oid[5] == 26 /* id-sha1 */; |
| } |
| |
| |
| static int x509_sha256_oid(struct asn1_oid *oid) |
| { |
| return oid->len == 9 && |
| oid->oid[0] == 2 /* joint-iso-itu-t */ && |
| oid->oid[1] == 16 /* country */ && |
| oid->oid[2] == 840 /* us */ && |
| oid->oid[3] == 1 /* organization */ && |
| oid->oid[4] == 101 /* gov */ && |
| oid->oid[5] == 3 /* csor */ && |
| oid->oid[6] == 4 /* nistAlgorithm */ && |
| oid->oid[7] == 2 /* hashAlgs */ && |
| oid->oid[8] == 1 /* sha256 */; |
| } |
| |
| |
| /** |
| * x509_certificate_parse - Parse a X.509 certificate in DER format |
| * @buf: Pointer to the X.509 certificate in DER format |
| * @len: Buffer length |
| * Returns: Pointer to the parsed certificate or %NULL on failure |
| * |
| * Caller is responsible for freeing the returned certificate by calling |
| * x509_certificate_free(). |
| */ |
| struct x509_certificate * x509_certificate_parse(const u8 *buf, size_t len) |
| { |
| struct asn1_hdr hdr; |
| const u8 *pos, *end, *hash_start; |
| struct x509_certificate *cert; |
| |
| cert = os_zalloc(sizeof(*cert) + len); |
| if (cert == NULL) |
| return NULL; |
| os_memcpy(cert + 1, buf, len); |
| cert->cert_start = (u8 *) (cert + 1); |
| cert->cert_len = len; |
| |
| pos = buf; |
| end = buf + len; |
| |
| /* RFC 3280 - X.509 v3 certificate / ASN.1 DER */ |
| |
| /* Certificate ::= SEQUENCE */ |
| if (asn1_get_next(pos, len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Certificate did not start with " |
| "a valid SEQUENCE - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| pos = hdr.payload; |
| |
| if (pos + hdr.length > end) { |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| |
| if (pos + hdr.length < end) { |
| wpa_hexdump(MSG_MSGDUMP, "X509: Ignoring extra data after DER " |
| "encoded certificate", |
| pos + hdr.length, end - pos + hdr.length); |
| end = pos + hdr.length; |
| } |
| |
| hash_start = pos; |
| cert->tbs_cert_start = cert->cert_start + (hash_start - buf); |
| if (x509_parse_tbs_certificate(pos, end - pos, cert, &pos)) { |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| cert->tbs_cert_len = pos - hash_start; |
| |
| /* signatureAlgorithm AlgorithmIdentifier */ |
| if (x509_parse_algorithm_identifier(pos, end - pos, |
| &cert->signature_alg, &pos)) { |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| |
| /* signatureValue BIT STRING */ |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_BITSTRING) { |
| wpa_printf(MSG_DEBUG, "X509: Expected BITSTRING " |
| "(signatureValue) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| if (hdr.length < 1) { |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| pos = hdr.payload; |
| if (*pos) { |
| wpa_printf(MSG_DEBUG, "X509: BITSTRING - %d unused bits", |
| *pos); |
| /* PKCS #1 v1.5 10.2.1: |
| * It is an error if the length in bits of the signature S is |
| * not a multiple of eight. |
| */ |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| os_free(cert->sign_value); |
| cert->sign_value = os_malloc(hdr.length - 1); |
| if (cert->sign_value == NULL) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to allocate memory for " |
| "signatureValue"); |
| x509_certificate_free(cert); |
| return NULL; |
| } |
| os_memcpy(cert->sign_value, pos + 1, hdr.length - 1); |
| cert->sign_value_len = hdr.length - 1; |
| wpa_hexdump(MSG_MSGDUMP, "X509: signature", |
| cert->sign_value, cert->sign_value_len); |
| |
| return cert; |
| } |
| |
| |
| /** |
| * x509_certificate_check_signature - Verify certificate signature |
| * @issuer: Issuer certificate |
| * @cert: Certificate to be verified |
| * Returns: 0 if cert has a valid signature that was signed by the issuer, |
| * -1 if not |
| */ |
| int x509_certificate_check_signature(struct x509_certificate *issuer, |
| struct x509_certificate *cert) |
| { |
| struct crypto_public_key *pk; |
| u8 *data; |
| const u8 *pos, *end, *next, *da_end; |
| size_t data_len; |
| struct asn1_hdr hdr; |
| struct asn1_oid oid; |
| u8 hash[32]; |
| size_t hash_len; |
| |
| if (!x509_pkcs_oid(&cert->signature.oid) || |
| cert->signature.oid.len != 7 || |
| cert->signature.oid.oid[5] != 1 /* pkcs-1 */) { |
| wpa_printf(MSG_DEBUG, "X509: Unrecognized signature " |
| "algorithm"); |
| return -1; |
| } |
| |
| pk = crypto_public_key_import(issuer->public_key, |
| issuer->public_key_len); |
| if (pk == NULL) |
| return -1; |
| |
| data_len = cert->sign_value_len; |
| data = os_malloc(data_len); |
| if (data == NULL) { |
| crypto_public_key_free(pk); |
| return -1; |
| } |
| |
| if (crypto_public_key_decrypt_pkcs1(pk, cert->sign_value, |
| cert->sign_value_len, data, |
| &data_len) < 0) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to decrypt signature"); |
| crypto_public_key_free(pk); |
| os_free(data); |
| return -1; |
| } |
| crypto_public_key_free(pk); |
| |
| wpa_hexdump(MSG_MSGDUMP, "X509: Signature data D", data, data_len); |
| |
| /* |
| * PKCS #1 v1.5, 10.1.2: |
| * |
| * DigestInfo ::= SEQUENCE { |
| * digestAlgorithm DigestAlgorithmIdentifier, |
| * digest Digest |
| * } |
| * |
| * DigestAlgorithmIdentifier ::= AlgorithmIdentifier |
| * |
| * Digest ::= OCTET STRING |
| * |
| */ |
| if (asn1_get_next(data, data_len, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(DigestInfo) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| os_free(data); |
| return -1; |
| } |
| |
| pos = hdr.payload; |
| end = pos + hdr.length; |
| |
| /* |
| * X.509: |
| * AlgorithmIdentifier ::= SEQUENCE { |
| * algorithm OBJECT IDENTIFIER, |
| * parameters ANY DEFINED BY algorithm OPTIONAL |
| * } |
| */ |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_SEQUENCE) { |
| wpa_printf(MSG_DEBUG, "X509: Expected SEQUENCE " |
| "(AlgorithmIdentifier) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| os_free(data); |
| return -1; |
| } |
| da_end = hdr.payload + hdr.length; |
| |
| if (asn1_get_oid(hdr.payload, hdr.length, &oid, &next)) { |
| wpa_printf(MSG_DEBUG, "X509: Failed to parse digestAlgorithm"); |
| os_free(data); |
| return -1; |
| } |
| |
| if (x509_sha1_oid(&oid)) { |
| if (cert->signature.oid.oid[6] != |
| 5 /* sha-1WithRSAEncryption */) { |
| wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA1 " |
| "does not match with certificate " |
| "signatureAlgorithm (%lu)", |
| cert->signature.oid.oid[6]); |
| os_free(data); |
| return -1; |
| } |
| goto skip_digest_oid; |
| } |
| |
| if (x509_sha256_oid(&oid)) { |
| if (cert->signature.oid.oid[6] != |
| 11 /* sha2561WithRSAEncryption */) { |
| wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA256 " |
| "does not match with certificate " |
| "signatureAlgorithm (%lu)", |
| cert->signature.oid.oid[6]); |
| os_free(data); |
| return -1; |
| } |
| goto skip_digest_oid; |
| } |
| |
| if (!x509_digest_oid(&oid)) { |
| wpa_printf(MSG_DEBUG, "X509: Unrecognized digestAlgorithm"); |
| os_free(data); |
| return -1; |
| } |
| switch (oid.oid[5]) { |
| case 5: /* md5 */ |
| if (cert->signature.oid.oid[6] != 4 /* md5WithRSAEncryption */) |
| { |
| wpa_printf(MSG_DEBUG, "X509: digestAlgorithm MD5 does " |
| "not match with certificate " |
| "signatureAlgorithm (%lu)", |
| cert->signature.oid.oid[6]); |
| os_free(data); |
| return -1; |
| } |
| break; |
| case 2: /* md2 */ |
| case 4: /* md4 */ |
| default: |
| wpa_printf(MSG_DEBUG, "X509: Unsupported digestAlgorithm " |
| "(%lu)", oid.oid[5]); |
| os_free(data); |
| return -1; |
| } |
| |
| skip_digest_oid: |
| /* Digest ::= OCTET STRING */ |
| pos = da_end; |
| end = data + data_len; |
| |
| if (asn1_get_next(pos, end - pos, &hdr) < 0 || |
| hdr.class != ASN1_CLASS_UNIVERSAL || |
| hdr.tag != ASN1_TAG_OCTETSTRING) { |
| wpa_printf(MSG_DEBUG, "X509: Expected OCTETSTRING " |
| "(Digest) - found class %d tag 0x%x", |
| hdr.class, hdr.tag); |
| os_free(data); |
| return -1; |
| } |
| wpa_hexdump(MSG_MSGDUMP, "X509: Decrypted Digest", |
| hdr.payload, hdr.length); |
| |
| switch (cert->signature.oid.oid[6]) { |
| case 4: /* md5WithRSAEncryption */ |
| md5_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len, |
| hash); |
| hash_len = 16; |
| wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (MD5)", |
| hash, hash_len); |
| break; |
| case 5: /* sha-1WithRSAEncryption */ |
| sha1_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len, |
| hash); |
| hash_len = 20; |
| wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA1)", |
| hash, hash_len); |
| break; |
| case 11: /* sha256WithRSAEncryption */ |
| sha256_vector(1, &cert->tbs_cert_start, &cert->tbs_cert_len, |
| hash); |
| hash_len = 32; |
| wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA256)", |
| hash, hash_len); |
| break; |
| case 2: /* md2WithRSAEncryption */ |
| case 12: /* sha384WithRSAEncryption */ |
| case 13: /* sha512WithRSAEncryption */ |
| default: |
| wpa_printf(MSG_INFO, "X509: Unsupported certificate signature " |
| "algorithm (%lu)", cert->signature.oid.oid[6]); |
| os_free(data); |
| return -1; |
| } |
| |
| if (hdr.length != hash_len || |
| os_memcmp(hdr.payload, hash, hdr.length) != 0) { |
| wpa_printf(MSG_INFO, "X509: Certificate Digest does not match " |
| "with calculated tbsCertificate hash"); |
| os_free(data); |
| return -1; |
| } |
| |
| os_free(data); |
| |
| wpa_printf(MSG_DEBUG, "X509: Certificate Digest matches with " |
| "calculated tbsCertificate hash"); |
| |
| return 0; |
| } |
| |
| |
| static int x509_valid_issuer(const struct x509_certificate *cert) |
| { |
| if ((cert->extensions_present & X509_EXT_BASIC_CONSTRAINTS) && |
| !cert->ca) { |
| wpa_printf(MSG_DEBUG, "X509: Non-CA certificate used as an " |
| "issuer"); |
| return -1; |
| } |
| |
| if (cert->version == X509_CERT_V3 && |
| !(cert->extensions_present & X509_EXT_BASIC_CONSTRAINTS)) { |
| wpa_printf(MSG_DEBUG, "X509: v3 CA certificate did not " |
| "include BasicConstraints extension"); |
| return -1; |
| } |
| |
| if ((cert->extensions_present & X509_EXT_KEY_USAGE) && |
| !(cert->key_usage & X509_KEY_USAGE_KEY_CERT_SIGN)) { |
| wpa_printf(MSG_DEBUG, "X509: Issuer certificate did not have " |
| "keyCertSign bit in Key Usage"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * x509_certificate_chain_validate - Validate X.509 certificate chain |
| * @trusted: List of trusted certificates |
| * @chain: Certificate chain to be validated (first chain must be issued by |
| * signed by the second certificate in the chain and so on) |
| * @reason: Buffer for returning failure reason (X509_VALIDATE_*) |
| * Returns: 0 if chain is valid, -1 if not |
| */ |
| int x509_certificate_chain_validate(struct x509_certificate *trusted, |
| struct x509_certificate *chain, |
| int *reason, int disable_time_checks) |
| { |
| long unsigned idx; |
| int chain_trusted = 0; |
| struct x509_certificate *cert, *trust; |
| char buf[128]; |
| struct os_time now; |
| |
| *reason = X509_VALIDATE_OK; |
| |
| wpa_printf(MSG_DEBUG, "X509: Validate certificate chain"); |
| os_get_time(&now); |
| |
| for (cert = chain, idx = 0; cert; cert = cert->next, idx++) { |
| x509_name_string(&cert->subject, buf, sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "X509: %lu: %s", idx, buf); |
| |
| if (chain_trusted) |
| continue; |
| |
| if (!disable_time_checks && |
| ((unsigned long) now.sec < |
| (unsigned long) cert->not_before || |
| (unsigned long) now.sec > |
| (unsigned long) cert->not_after)) { |
| wpa_printf(MSG_INFO, "X509: Certificate not valid " |
| "(now=%lu not_before=%lu not_after=%lu)", |
| now.sec, cert->not_before, cert->not_after); |
| *reason = X509_VALIDATE_CERTIFICATE_EXPIRED; |
| return -1; |
| } |
| |
| if (cert->next) { |
| if (x509_name_compare(&cert->issuer, |
| &cert->next->subject) != 0) { |
| wpa_printf(MSG_DEBUG, "X509: Certificate " |
| "chain issuer name mismatch"); |
| x509_name_string(&cert->issuer, buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "X509: cert issuer: %s", |
| buf); |
| x509_name_string(&cert->next->subject, buf, |
| sizeof(buf)); |
| wpa_printf(MSG_DEBUG, "X509: next cert " |
| "subject: %s", buf); |
| *reason = X509_VALIDATE_CERTIFICATE_UNKNOWN; |
| return -1; |
| } |
| |
| if (x509_valid_issuer(cert->next) < 0) { |
| *reason = X509_VALIDATE_BAD_CERTIFICATE; |
| return -1; |
| } |
| |
| if ((cert->next->extensions_present & |
| X509_EXT_PATH_LEN_CONSTRAINT) && |
| idx > cert->next->path_len_constraint) { |
| wpa_printf(MSG_DEBUG, "X509: pathLenConstraint" |
| " not met (idx=%lu issuer " |
| "pathLenConstraint=%lu)", idx, |
| cert->next->path_len_constraint); |
| *reason = X509_VALIDATE_BAD_CERTIFICATE; |
| return -1; |
| } |
| |
| if (x509_certificate_check_signature(cert->next, cert) |
| < 0) { |
| wpa_printf(MSG_DEBUG, "X509: Invalid " |
| "certificate signature within " |
| "chain"); |
| *reason = X509_VALIDATE_BAD_CERTIFICATE; |
| return -1; |
| } |
| } |
| |
| for (trust = trusted; trust; trust = trust->next) { |
| if (x509_name_compare(&cert->issuer, &trust->subject) |
| == 0) |
| break; |
| } |
| |
| if (trust) { |
| wpa_printf(MSG_DEBUG, "X509: Found issuer from the " |
| "list of trusted certificates"); |
| if (x509_valid_issuer(trust) < 0) { |
| *reason = X509_VALIDATE_BAD_CERTIFICATE; |
| return -1; |
| } |
| |
| if (x509_certificate_check_signature(trust, cert) < 0) |
| { |
| wpa_printf(MSG_DEBUG, "X509: Invalid " |
| "certificate signature"); |
| *reason = X509_VALIDATE_BAD_CERTIFICATE; |
| return -1; |
| } |
| |
| wpa_printf(MSG_DEBUG, "X509: Trusted certificate " |
| "found to complete the chain"); |
| chain_trusted = 1; |
| } |
| } |
| |
| if (!chain_trusted) { |
| wpa_printf(MSG_DEBUG, "X509: Did not find any of the issuers " |
| "from the list of trusted certificates"); |
| if (trusted) { |
| *reason = X509_VALIDATE_UNKNOWN_CA; |
| return -1; |
| } |
| wpa_printf(MSG_DEBUG, "X509: Certificate chain validation " |
| "disabled - ignore unknown CA issue"); |
| } |
| |
| wpa_printf(MSG_DEBUG, "X509: Certificate chain valid"); |
| |
| return 0; |
| } |
| |
| |
| /** |
| * x509_certificate_get_subject - Get a certificate based on Subject name |
| * @chain: Certificate chain to search through |
| * @name: Subject name to search for |
| * Returns: Pointer to the certificate with the given Subject name or |
| * %NULL on failure |
| */ |
| struct x509_certificate * |
| x509_certificate_get_subject(struct x509_certificate *chain, |
| struct x509_name *name) |
| { |
| struct x509_certificate *cert; |
| |
| for (cert = chain; cert; cert = cert->next) { |
| if (x509_name_compare(&cert->subject, name) == 0) |
| return cert; |
| } |
| return NULL; |
| } |
| |
| |
| /** |
| * x509_certificate_self_signed - Is the certificate self-signed? |
| * @cert: Certificate |
| * Returns: 1 if certificate is self-signed, 0 if not |
| */ |
| int x509_certificate_self_signed(struct x509_certificate *cert) |
| { |
| return x509_name_compare(&cert->issuer, &cert->subject) == 0; |
| } |