76162b1828
The recent addition of the X.509v3 certificatePolicies parser had a
copy-paste issue on the inner SEQUENCE parser that ended up using
incorrect length for the remaining buffer. Fix that to calculate the
remaining length properly to avoid reading beyond the end of the buffer
in case of corrupted input data.
Credit to OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=20363
Fixes: d165b32f38
("TLS: TOD-STRICT and TOD-TOFU certificate policies")
Signed-off-by: Jouni Malinen <j@w1.fi>
2338 lines
57 KiB
C
2338 lines
57 KiB
C
/*
|
|
* X.509v3 certificate parsing and processing (RFC 3280 profile)
|
|
* Copyright (c) 2006-2015, 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"
|
|
|
|
|
|
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->subject_dn);
|
|
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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
if (hdr.length > buf + len - hdr.payload)
|
|
return -1;
|
|
pos = hdr.payload;
|
|
end = pos + hdr.length;
|
|
|
|
*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 (hdr.length > end - pos)
|
|
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_memdup(pos + 1, hdr.length - 1);
|
|
if (cert->public_key == NULL) {
|
|
wpa_printf(MSG_DEBUG, "X509: Failed to allocate memory for "
|
|
"public key");
|
|
return -1;
|
|
}
|
|
cert->public_key_len = hdr.length - 1;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: subjectPublicKey",
|
|
cert->public_key, cert->public_key_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
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 (hdr.length > buf + len - pos)
|
|
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 = dup_binstr(hdr.payload, hdr.length);
|
|
if (val == NULL) {
|
|
x509_free_name(name);
|
|
return -1;
|
|
}
|
|
if (os_strlen(val) != hdr.length) {
|
|
wpa_printf(MSG_INFO, "X509: Reject certificate with "
|
|
"embedded NUL byte in a string (%s[NUL])",
|
|
val);
|
|
os_free(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 (os_snprintf_error(end - pos, ret))
|
|
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 (os_snprintf_error(end - pos, ret))
|
|
goto done;
|
|
pos += ret;
|
|
}
|
|
|
|
done:
|
|
if (pos < end)
|
|
*pos = '\0';
|
|
end[-1] = '\0';
|
|
}
|
|
|
|
|
|
static int parse_uint2(const char *pos, size_t len)
|
|
{
|
|
char buf[3];
|
|
int ret;
|
|
|
|
if (len < 2)
|
|
return -1;
|
|
buf[0] = pos[0];
|
|
buf[1] = pos[1];
|
|
buf[2] = 0x00;
|
|
if (sscanf(buf, "%2d", &ret) != 1)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int parse_uint4(const char *pos, size_t len)
|
|
{
|
|
char buf[5];
|
|
int ret;
|
|
|
|
if (len < 4)
|
|
return -1;
|
|
buf[0] = pos[0];
|
|
buf[1] = pos[1];
|
|
buf[2] = pos[2];
|
|
buf[3] = pos[3];
|
|
buf[4] = 0x00;
|
|
if (sscanf(buf, "%4d", &ret) != 1)
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
|
|
int x509_parse_time(const u8 *buf, size_t len, u8 asn1_tag, os_time_t *val)
|
|
{
|
|
const char *pos, *end;
|
|
int year, month, day, hour, min, sec;
|
|
|
|
/*
|
|
* Time ::= CHOICE {
|
|
* utcTime UTCTime,
|
|
* generalTime GeneralizedTime
|
|
* }
|
|
*
|
|
* UTCTime: YYMMDDHHMMSSZ
|
|
* GeneralizedTime: YYYYMMDDHHMMSSZ
|
|
*/
|
|
|
|
pos = (const char *) buf;
|
|
end = pos + len;
|
|
|
|
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;
|
|
}
|
|
year = parse_uint2(pos, end - pos);
|
|
if (year < 0) {
|
|
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;
|
|
}
|
|
year = parse_uint4(pos, end - pos);
|
|
if (year < 0) {
|
|
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;
|
|
}
|
|
|
|
month = parse_uint2(pos, end - pos);
|
|
if (month < 0) {
|
|
wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
|
|
"(month)", buf, len);
|
|
return -1;
|
|
}
|
|
pos += 2;
|
|
|
|
day = parse_uint2(pos, end - pos);
|
|
if (day < 0) {
|
|
wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
|
|
"(day)", buf, len);
|
|
return -1;
|
|
}
|
|
pos += 2;
|
|
|
|
hour = parse_uint2(pos, end - pos);
|
|
if (hour < 0) {
|
|
wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
|
|
"(hour)", buf, len);
|
|
return -1;
|
|
}
|
|
pos += 2;
|
|
|
|
min = parse_uint2(pos, end - pos);
|
|
if (min < 0) {
|
|
wpa_hexdump_ascii(MSG_DEBUG, "X509: Failed to parse Time "
|
|
"(min)", buf, len);
|
|
return -1;
|
|
}
|
|
pos += 2;
|
|
|
|
sec = parse_uint2(pos, end - pos);
|
|
if (sec < 0) {
|
|
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 (plen > (size_t) (buf + len - pos))
|
|
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_any_ext_key_usage_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len == 6 &&
|
|
x509_id_ce_oid(oid) &&
|
|
oid->oid[3] == 37 /* extKeyUsage */ &&
|
|
oid->oid[4] == 0 /* anyExtendedKeyUsage */;
|
|
}
|
|
|
|
|
|
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;
|
|
const u8 *end_seq;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
end_seq = hdr.payload + hdr.length;
|
|
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) {
|
|
cert->ca = hdr.payload[0];
|
|
|
|
pos = hdr.payload + hdr.length;
|
|
if (pos >= end_seq) {
|
|
/* No optional pathLenConstraint */
|
|
wpa_printf(MSG_DEBUG, "X509: BasicConstraints - cA=%d",
|
|
cert->ca);
|
|
return 0;
|
|
}
|
|
if (asn1_get_next(pos, end_seq - pos, &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_memdup(pos, len);
|
|
if (name->ip == NULL)
|
|
return -1;
|
|
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_id_cert_policy_any_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len == 5 &&
|
|
oid->oid[0] == 2 /* iso/itu-t */ &&
|
|
oid->oid[1] == 5 /* X.500 Directory Services */ &&
|
|
oid->oid[2] == 29 /* id-ce */ &&
|
|
oid->oid[3] == 32 /* id-ce-certificate-policies */ &&
|
|
oid->oid[4] == 0 /* anyPolicy */;
|
|
}
|
|
|
|
|
|
static int x509_id_wfa_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len >= 7 &&
|
|
oid->oid[0] == 1 /* iso */ &&
|
|
oid->oid[1] == 3 /* identified-organization */ &&
|
|
oid->oid[2] == 6 /* dod */ &&
|
|
oid->oid[3] == 1 /* internet */ &&
|
|
oid->oid[4] == 4 /* private */ &&
|
|
oid->oid[5] == 1 /* enterprise */ &&
|
|
oid->oid[6] == 40808 /* WFA */;
|
|
}
|
|
|
|
|
|
static int x509_id_wfa_tod_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len >= 9 &&
|
|
x509_id_wfa_oid(oid) &&
|
|
oid->oid[7] == 1 &&
|
|
oid->oid[8] == 3;
|
|
}
|
|
|
|
|
|
static int x509_id_wfa_tod_strict_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len == 10 &&
|
|
x509_id_wfa_tod_oid(oid) &&
|
|
oid->oid[9] == 1;
|
|
}
|
|
|
|
|
|
static int x509_id_wfa_tod_tofu_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len == 10 &&
|
|
x509_id_wfa_tod_oid(oid) &&
|
|
oid->oid[9] == 2;
|
|
}
|
|
|
|
|
|
static int x509_parse_ext_certificate_policies(struct x509_certificate *cert,
|
|
const u8 *pos, size_t len)
|
|
{
|
|
struct asn1_hdr hdr;
|
|
const u8 *end;
|
|
|
|
/*
|
|
* certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
|
*
|
|
* PolicyInformation ::= SEQUENCE {
|
|
* policyIdentifier CertPolicyId,
|
|
* policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
|
* PolicyQualifierInfo OPTIONAL }
|
|
*
|
|
* CertPolicyId ::= OBJECT IDENTIFIER
|
|
*/
|
|
|
|
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 (certificatePolicies) - found class %d tag 0x%x",
|
|
hdr.class, hdr.tag);
|
|
return -1;
|
|
}
|
|
if (hdr.length > pos + len - hdr.payload)
|
|
return -1;
|
|
pos = hdr.payload;
|
|
end = pos + hdr.length;
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: certificatePolicies", pos, end - pos);
|
|
|
|
while (pos < end) {
|
|
const u8 *pol_end;
|
|
struct asn1_oid oid;
|
|
char buf[80];
|
|
|
|
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 (PolicyInformation) - found class %d tag 0x%x",
|
|
hdr.class, hdr.tag);
|
|
return -1;
|
|
}
|
|
if (hdr.length > end - hdr.payload)
|
|
return -1;
|
|
pos = hdr.payload;
|
|
pol_end = pos + hdr.length;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: PolicyInformation",
|
|
pos, pol_end - pos);
|
|
|
|
if (asn1_get_oid(pos, pol_end - pos, &oid, &pos))
|
|
return -1;
|
|
if (x509_id_cert_policy_any_oid(&oid)) {
|
|
os_strlcpy(buf, "anyPolicy-STRICT", sizeof(buf));
|
|
cert->certificate_policy |=
|
|
X509_EXT_CERT_POLICY_ANY;
|
|
} else if (x509_id_wfa_tod_strict_oid(&oid)) {
|
|
os_strlcpy(buf, "TOD-STRICT", sizeof(buf));
|
|
cert->certificate_policy |=
|
|
X509_EXT_CERT_POLICY_TOD_STRICT;
|
|
} else if (x509_id_wfa_tod_tofu_oid(&oid)) {
|
|
os_strlcpy(buf, "TOD-TOFU", sizeof(buf));
|
|
cert->certificate_policy |=
|
|
X509_EXT_CERT_POLICY_TOD_TOFU;
|
|
} else {
|
|
asn1_oid_to_str(&oid, buf, sizeof(buf));
|
|
}
|
|
wpa_printf(MSG_DEBUG, "policyIdentifier: %s", buf);
|
|
|
|
pos = pol_end;
|
|
}
|
|
|
|
cert->extensions_present |= X509_EXT_CERTIFICATE_POLICY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int x509_id_pkix_oid(struct asn1_oid *oid)
|
|
{
|
|
return oid->len >= 7 &&
|
|
oid->oid[0] == 1 /* iso */ &&
|
|
oid->oid[1] == 3 /* identified-organization */ &&
|
|
oid->oid[2] == 6 /* dod */ &&
|
|
oid->oid[3] == 1 /* internet */ &&
|
|
oid->oid[4] == 5 /* security */ &&
|
|
oid->oid[5] == 5 /* mechanisms */ &&
|
|
oid->oid[6] == 7 /* id-pkix */;
|
|
}
|
|
|
|
|
|
static int x509_id_kp_oid(struct asn1_oid *oid)
|
|
{
|
|
/* id-kp */
|
|
return oid->len >= 8 &&
|
|
x509_id_pkix_oid(oid) &&
|
|
oid->oid[7] == 3 /* id-kp */;
|
|
}
|
|
|
|
|
|
static int x509_id_kp_server_auth_oid(struct asn1_oid *oid)
|
|
{
|
|
/* id-kp */
|
|
return oid->len == 9 &&
|
|
x509_id_kp_oid(oid) &&
|
|
oid->oid[8] == 1 /* id-kp-serverAuth */;
|
|
}
|
|
|
|
|
|
static int x509_id_kp_client_auth_oid(struct asn1_oid *oid)
|
|
{
|
|
/* id-kp */
|
|
return oid->len == 9 &&
|
|
x509_id_kp_oid(oid) &&
|
|
oid->oid[8] == 2 /* id-kp-clientAuth */;
|
|
}
|
|
|
|
|
|
static int x509_id_kp_ocsp_oid(struct asn1_oid *oid)
|
|
{
|
|
/* id-kp */
|
|
return oid->len == 9 &&
|
|
x509_id_kp_oid(oid) &&
|
|
oid->oid[8] == 9 /* id-kp-OCSPSigning */;
|
|
}
|
|
|
|
|
|
static int x509_parse_ext_ext_key_usage(struct x509_certificate *cert,
|
|
const u8 *pos, size_t len)
|
|
{
|
|
struct asn1_hdr hdr;
|
|
const u8 *end;
|
|
struct asn1_oid oid;
|
|
|
|
/*
|
|
* ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
|
|
*
|
|
* KeyPurposeId ::= OBJECT IDENTIFIER
|
|
*/
|
|
|
|
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 "
|
|
"(ExtKeyUsageSyntax) - found class %d tag 0x%x",
|
|
hdr.class, hdr.tag);
|
|
return -1;
|
|
}
|
|
if (hdr.length > pos + len - hdr.payload)
|
|
return -1;
|
|
pos = hdr.payload;
|
|
end = pos + hdr.length;
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: ExtKeyUsageSyntax", pos, end - pos);
|
|
|
|
while (pos < end) {
|
|
char buf[80];
|
|
|
|
if (asn1_get_oid(pos, end - pos, &oid, &pos))
|
|
return -1;
|
|
if (x509_any_ext_key_usage_oid(&oid)) {
|
|
os_strlcpy(buf, "anyExtendedKeyUsage", sizeof(buf));
|
|
cert->ext_key_usage |= X509_EXT_KEY_USAGE_ANY;
|
|
} else if (x509_id_kp_server_auth_oid(&oid)) {
|
|
os_strlcpy(buf, "id-kp-serverAuth", sizeof(buf));
|
|
cert->ext_key_usage |= X509_EXT_KEY_USAGE_SERVER_AUTH;
|
|
} else if (x509_id_kp_client_auth_oid(&oid)) {
|
|
os_strlcpy(buf, "id-kp-clientAuth", sizeof(buf));
|
|
cert->ext_key_usage |= X509_EXT_KEY_USAGE_CLIENT_AUTH;
|
|
} else if (x509_id_kp_ocsp_oid(&oid)) {
|
|
os_strlcpy(buf, "id-kp-OCSPSigning", sizeof(buf));
|
|
cert->ext_key_usage |= X509_EXT_KEY_USAGE_OCSP;
|
|
} else {
|
|
asn1_oid_to_str(&oid, buf, sizeof(buf));
|
|
}
|
|
wpa_printf(MSG_DEBUG, "ExtKeyUsage KeyPurposeId: %s", buf);
|
|
}
|
|
|
|
cert->extensions_present |= X509_EXT_EXT_KEY_USAGE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
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:
|
|
* name constraints (section 4.2.1.11)
|
|
* policy constraints (section 4.2.1.12)
|
|
* 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);
|
|
case 32: /* id-ce-certificatePolicies */
|
|
return x509_parse_ext_certificate_policies(cert, pos, len);
|
|
case 37: /* id-ce-extKeyUsage */
|
|
return x509_parse_ext_ext_key_usage(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) {
|
|
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;
|
|
const u8 *subject_dn;
|
|
|
|
/* 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 ||
|
|
hdr.length < 1 || hdr.length > X509_MAX_SERIAL_NUM_LEN) {
|
|
wpa_printf(MSG_DEBUG, "X509: No INTEGER tag found for "
|
|
"serialNumber; class=%d tag=0x%x length=%u",
|
|
hdr.class, hdr.tag, hdr.length);
|
|
return -1;
|
|
}
|
|
|
|
pos = hdr.payload + hdr.length;
|
|
while (hdr.length > 0 && hdr.payload[0] == 0) {
|
|
hdr.payload++;
|
|
hdr.length--;
|
|
}
|
|
os_memcpy(cert->serial_number, hdr.payload, hdr.length);
|
|
cert->serial_number_len = hdr.length;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: serialNumber", cert->serial_number,
|
|
cert->serial_number_len);
|
|
|
|
/* 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 */
|
|
subject_dn = pos;
|
|
if (x509_parse_name(pos, end - pos, &cert->subject, &pos))
|
|
return -1;
|
|
cert->subject_dn = os_malloc(pos - subject_dn);
|
|
if (!cert->subject_dn)
|
|
return -1;
|
|
cert->subject_dn_len = pos - subject_dn;
|
|
os_memcpy(cert->subject_dn, subject_dn, cert->subject_dn_len);
|
|
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 */
|
|
|
|
pos = hdr.payload + hdr.length;
|
|
if (pos == 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 */
|
|
|
|
pos = hdr.payload + hdr.length;
|
|
if (pos == 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 */;
|
|
}
|
|
|
|
|
|
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_sha2_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 */;
|
|
}
|
|
|
|
|
|
int x509_sha256_oid(struct asn1_oid *oid)
|
|
{
|
|
return x509_sha2_oid(oid) &&
|
|
oid->oid[8] == 1 /* sha256 */;
|
|
}
|
|
|
|
|
|
int x509_sha384_oid(struct asn1_oid *oid)
|
|
{
|
|
return x509_sha2_oid(oid) &&
|
|
oid->oid[8] == 2 /* sha384 */;
|
|
}
|
|
|
|
|
|
int x509_sha512_oid(struct asn1_oid *oid)
|
|
{
|
|
return x509_sha2_oid(oid) &&
|
|
oid->oid[8] == 3 /* sha512 */;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 (hdr.length > end - pos) {
|
|
x509_certificate_free(cert);
|
|
return NULL;
|
|
}
|
|
|
|
if (hdr.length < end - pos) {
|
|
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_memdup(pos + 1, 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;
|
|
}
|
|
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)
|
|
{
|
|
return x509_check_signature(issuer, &cert->signature,
|
|
cert->sign_value, cert->sign_value_len,
|
|
cert->tbs_cert_start, cert->tbs_cert_len);
|
|
}
|
|
|
|
|
|
int x509_check_signature(struct x509_certificate *issuer,
|
|
struct x509_algorithm_identifier *signature,
|
|
const u8 *sign_value, size_t sign_value_len,
|
|
const u8 *signed_data, size_t signed_data_len)
|
|
{
|
|
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[64];
|
|
size_t hash_len;
|
|
const u8 *addr[1] = { signed_data };
|
|
size_t len[1] = { signed_data_len };
|
|
|
|
if (!x509_pkcs_oid(&signature->oid) ||
|
|
signature->oid.len != 7 ||
|
|
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 = 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, sign_value,
|
|
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 (signature->oid.oid[6] != 5 /* sha-1WithRSAEncryption */) {
|
|
wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA1 "
|
|
"does not match with certificate "
|
|
"signatureAlgorithm (%lu)",
|
|
signature->oid.oid[6]);
|
|
os_free(data);
|
|
return -1;
|
|
}
|
|
goto skip_digest_oid;
|
|
}
|
|
|
|
if (x509_sha256_oid(&oid)) {
|
|
if (signature->oid.oid[6] !=
|
|
11 /* sha2561WithRSAEncryption */) {
|
|
wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA256 "
|
|
"does not match with certificate "
|
|
"signatureAlgorithm (%lu)",
|
|
signature->oid.oid[6]);
|
|
os_free(data);
|
|
return -1;
|
|
}
|
|
goto skip_digest_oid;
|
|
}
|
|
|
|
if (x509_sha384_oid(&oid)) {
|
|
if (signature->oid.oid[6] != 12 /* sha384WithRSAEncryption */) {
|
|
wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA384 "
|
|
"does not match with certificate "
|
|
"signatureAlgorithm (%lu)",
|
|
signature->oid.oid[6]);
|
|
os_free(data);
|
|
return -1;
|
|
}
|
|
goto skip_digest_oid;
|
|
}
|
|
|
|
if (x509_sha512_oid(&oid)) {
|
|
if (signature->oid.oid[6] != 13 /* sha512WithRSAEncryption */) {
|
|
wpa_printf(MSG_DEBUG, "X509: digestAlgorithm SHA512 "
|
|
"does not match with certificate "
|
|
"signatureAlgorithm (%lu)",
|
|
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 (signature->oid.oid[6] != 4 /* md5WithRSAEncryption */) {
|
|
wpa_printf(MSG_DEBUG, "X509: digestAlgorithm MD5 does "
|
|
"not match with certificate "
|
|
"signatureAlgorithm (%lu)",
|
|
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 (signature->oid.oid[6]) {
|
|
case 4: /* md5WithRSAEncryption */
|
|
md5_vector(1, addr, len, hash);
|
|
hash_len = 16;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (MD5)",
|
|
hash, hash_len);
|
|
break;
|
|
case 5: /* sha-1WithRSAEncryption */
|
|
sha1_vector(1, addr, len, hash);
|
|
hash_len = 20;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA1)",
|
|
hash, hash_len);
|
|
break;
|
|
case 11: /* sha256WithRSAEncryption */
|
|
sha256_vector(1, addr, len, hash);
|
|
hash_len = 32;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA256)",
|
|
hash, hash_len);
|
|
break;
|
|
case 12: /* sha384WithRSAEncryption */
|
|
sha384_vector(1, addr, len, hash);
|
|
hash_len = 48;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA384)",
|
|
hash, hash_len);
|
|
break;
|
|
case 13: /* sha512WithRSAEncryption */
|
|
sha512_vector(1, addr, len, hash);
|
|
hash_len = 64;
|
|
wpa_hexdump(MSG_MSGDUMP, "X509: Certificate hash (SHA512)",
|
|
hash, hash_len);
|
|
break;
|
|
case 2: /* md2WithRSAEncryption */
|
|
default:
|
|
wpa_printf(MSG_INFO, "X509: Unsupported certificate signature "
|
|
"algorithm (%lu)", signature->oid.oid[6]);
|
|
os_free(data);
|
|
return -1;
|
|
}
|
|
|
|
if (hdr.length != hash_len ||
|
|
os_memcmp_const(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;
|
|
}
|
|
|
|
if (hdr.payload + hdr.length < data + data_len) {
|
|
wpa_hexdump(MSG_INFO,
|
|
"X509: Extra data after certificate signature hash",
|
|
hdr.payload + hdr.length,
|
|
data + data_len - hdr.payload - hdr.length);
|
|
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++) {
|
|
cert->issuer_trusted = 0;
|
|
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");
|
|
cert->issuer_trusted = 1;
|
|
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;
|
|
}
|