hostapd/src/tls/asn1.c
Jouni Malinen ee76493bbd ASN.1: Reject invalid definite long form length values in DER encoding
The definite long form for the length is allowed only for cases where
the definite short form cannot be used, i.e., if the length is 128 or
greater. This was not previously enforced and as such, multiple
different encoding options for the same length could have been accepted.

Perform more strict checks to reject invalid cases for the definite long
form for the length. This is needed for a compliant implementation and
this is especially important for the case of verifying DER encoded
signatures to prevent potential forging attacks.

Signed-off-by: Jouni Malinen <j@w1.fi>
2021-03-14 11:37:58 +02:00

650 lines
14 KiB
C

/*
* ASN.1 DER parsing
* Copyright (c) 2006-2014, 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 "utils/wpabuf.h"
#include "asn1.h"
const struct asn1_oid asn1_sha1_oid = {
.oid = { 1, 3, 14, 3, 2, 26 },
.len = 6
};
const struct asn1_oid asn1_sha256_oid = {
.oid = { 2, 16, 840, 1, 101, 3, 4, 2, 1 },
.len = 9
};
const struct asn1_oid asn1_ec_public_key_oid = {
.oid = { 1, 2, 840, 10045, 2, 1 },
.len = 6
};
const struct asn1_oid asn1_prime256v1_oid = {
.oid = { 1, 2, 840, 10045, 3, 1, 7 },
.len = 7
};
const struct asn1_oid asn1_secp384r1_oid = {
.oid = { 1, 3, 132, 0, 34 },
.len = 5
};
const struct asn1_oid asn1_secp521r1_oid = {
.oid = { 1, 3, 132, 0, 35 },
.len = 5
};
const struct asn1_oid asn1_brainpoolP256r1_oid = {
.oid = { 1, 3, 36, 3, 3, 2, 8, 1, 1, 7 },
.len = 10
};
const struct asn1_oid asn1_brainpoolP384r1_oid = {
.oid = { 1, 3, 36, 3, 3, 2, 8, 1, 1, 11 },
.len = 10
};
const struct asn1_oid asn1_brainpoolP512r1_oid = {
.oid = { 1, 3, 36, 3, 3, 2, 8, 1, 1, 13 },
.len = 10
};
const struct asn1_oid asn1_aes_siv_cmac_aead_256_oid = {
.oid = { 1, 2, 840, 113549, 1, 9, 16, 3, 22 },
.len = 9
};
const struct asn1_oid asn1_aes_siv_cmac_aead_384_oid = {
.oid = { 1, 2, 840, 113549, 1, 9, 16, 3, 23 },
.len = 9
};
const struct asn1_oid asn1_aes_siv_cmac_aead_512_oid = {
.oid = { 1, 2, 840, 113549, 1, 9, 16, 3, 24 },
.len = 9
};
const struct asn1_oid asn1_pbkdf2_oid = {
.oid = { 1, 2, 840, 113549, 1, 5, 12 },
.len = 7
};
const struct asn1_oid asn1_pbkdf2_hmac_sha256_oid = {
.oid = { 1, 2, 840, 113549, 2, 9 },
.len = 6
};
const struct asn1_oid asn1_pbkdf2_hmac_sha384_oid = {
.oid = { 1, 2, 840, 113549, 2, 10 },
.len = 6
};
const struct asn1_oid asn1_pbkdf2_hmac_sha512_oid = {
.oid = { 1, 2, 840, 113549, 2, 11 },
.len = 6
};
const struct asn1_oid asn1_dpp_config_params_oid = {
.oid = { 1, 3, 6, 1, 4, 1, 40808, 1, 2, 1 },
.len = 10
};
const struct asn1_oid asn1_dpp_asymmetric_key_package_oid = {
.oid = { 1, 3, 6, 1, 4, 1, 40808, 1, 2, 2 },
.len = 10
};
static int asn1_valid_der_boolean(struct asn1_hdr *hdr)
{
/* Enforce DER requirements for a single way of encoding a BOOLEAN */
if (hdr->length != 1) {
wpa_printf(MSG_DEBUG, "ASN.1: Unexpected BOOLEAN length (%u)",
hdr->length);
return 0;
}
if (hdr->payload[0] != 0 && hdr->payload[0] != 0xff) {
wpa_printf(MSG_DEBUG,
"ASN.1: Invalid BOOLEAN value 0x%x (DER requires 0 or 0xff)",
hdr->payload[0]);
return 0;
}
return 1;
}
static int asn1_valid_der(struct asn1_hdr *hdr)
{
if (hdr->class != ASN1_CLASS_UNIVERSAL)
return 1;
if (hdr->tag == ASN1_TAG_BOOLEAN && !asn1_valid_der_boolean(hdr))
return 0;
if (hdr->tag == ASN1_TAG_NULL && hdr->length != 0)
return 0;
/* Check for allowed primitive/constructed values */
if (hdr->constructed &&
(hdr->tag == ASN1_TAG_BOOLEAN ||
hdr->tag == ASN1_TAG_INTEGER ||
hdr->tag == ASN1_TAG_NULL ||
hdr->tag == ASN1_TAG_OID ||
hdr->tag == ANS1_TAG_RELATIVE_OID ||
hdr->tag == ASN1_TAG_REAL ||
hdr->tag == ASN1_TAG_ENUMERATED ||
hdr->tag == ASN1_TAG_BITSTRING ||
hdr->tag == ASN1_TAG_OCTETSTRING ||
hdr->tag == ASN1_TAG_NUMERICSTRING ||
hdr->tag == ASN1_TAG_PRINTABLESTRING ||
hdr->tag == ASN1_TAG_T61STRING ||
hdr->tag == ASN1_TAG_VIDEOTEXSTRING ||
hdr->tag == ASN1_TAG_VISIBLESTRING ||
hdr->tag == ASN1_TAG_IA5STRING ||
hdr->tag == ASN1_TAG_GRAPHICSTRING ||
hdr->tag == ASN1_TAG_GENERALSTRING ||
hdr->tag == ASN1_TAG_UNIVERSALSTRING ||
hdr->tag == ASN1_TAG_UTF8STRING ||
hdr->tag == ASN1_TAG_BMPSTRING ||
hdr->tag == ASN1_TAG_CHARACTERSTRING ||
hdr->tag == ASN1_TAG_UTCTIME ||
hdr->tag == ASN1_TAG_GENERALIZEDTIME ||
hdr->tag == ASN1_TAG_TIME))
return 0;
if (!hdr->constructed &&
(hdr->tag == ASN1_TAG_SEQUENCE ||
hdr->tag == ASN1_TAG_SET))
return 0;
return 1;
}
int asn1_get_next(const u8 *buf, size_t len, struct asn1_hdr *hdr)
{
const u8 *pos, *end;
u8 tmp;
os_memset(hdr, 0, sizeof(*hdr));
pos = buf;
end = buf + len;
if (pos >= end) {
wpa_printf(MSG_DEBUG, "ASN.1: No room for Identifier");
return -1;
}
hdr->identifier = *pos++;
hdr->class = hdr->identifier >> 6;
hdr->constructed = !!(hdr->identifier & (1 << 5));
if ((hdr->identifier & 0x1f) == 0x1f) {
size_t ext_len = 0;
hdr->tag = 0;
if (pos == end || (*pos & 0x7f) == 0) {
wpa_printf(MSG_DEBUG,
"ASN.1: Invalid extended tag (first octet has to be included with at least one nonzero bit for the tag value)");
return -1;
}
do {
if (pos >= end) {
wpa_printf(MSG_DEBUG, "ASN.1: Identifier "
"underflow");
return -1;
}
ext_len++;
tmp = *pos++;
wpa_printf(MSG_MSGDUMP, "ASN.1: Extended tag data: "
"0x%02x", tmp);
hdr->tag = (hdr->tag << 7) | (tmp & 0x7f);
} while (tmp & 0x80);
wpa_printf(MSG_MSGDUMP, "ASN.1: Extended Tag: 0x%x (len=%zu)",
hdr->tag, ext_len);
if ((hdr->class != ASN1_CLASS_PRIVATE && hdr->tag < 31) ||
ext_len * 7 > sizeof(hdr->tag) * 8) {
wpa_printf(MSG_DEBUG,
"ASN.1: Invalid or unsupported (too large) extended Tag: 0x%x (len=%zu)",
hdr->tag, ext_len);
return -1;
}
} else
hdr->tag = hdr->identifier & 0x1f;
if (pos >= end) {
wpa_printf(MSG_DEBUG, "ASN.1: No room for Length");
return -1;
}
tmp = *pos++;
if (tmp & 0x80) {
if (tmp == 0xff) {
wpa_printf(MSG_DEBUG, "ASN.1: Reserved length "
"value 0xff used");
return -1;
}
tmp &= 0x7f; /* number of subsequent octets */
hdr->length = 0;
if (tmp == 0 || pos == end || *pos == 0) {
wpa_printf(MSG_DEBUG,
"ASN.1: Definite long form of the length does not start with a nonzero value");
return -1;
}
if (tmp > 4) {
wpa_printf(MSG_DEBUG, "ASN.1: Too long length field");
return -1;
}
while (tmp--) {
if (pos >= end) {
wpa_printf(MSG_DEBUG, "ASN.1: Length "
"underflow");
return -1;
}
hdr->length = (hdr->length << 8) | *pos++;
}
if (hdr->length < 128) {
wpa_printf(MSG_DEBUG,
"ASN.1: Definite long form of the length used with too short length");
return -1;
}
} else {
/* Short form - length 0..127 in one octet */
hdr->length = tmp;
}
if (end < pos || hdr->length > (unsigned int) (end - pos)) {
wpa_printf(MSG_DEBUG, "ASN.1: Contents underflow");
return -1;
}
hdr->payload = pos;
if (!asn1_valid_der(hdr)) {
asn1_print_hdr(hdr, "ASN.1: Invalid DER encoding: ");
return -1;
}
return 0;
}
void asn1_print_hdr(const struct asn1_hdr *hdr, const char *title)
{
wpa_printf(MSG_DEBUG, "%sclass %d constructed %d tag 0x%x",
title, hdr->class, hdr->constructed, hdr->tag);
}
void asn1_unexpected(const struct asn1_hdr *hdr, const char *title)
{
wpa_printf(MSG_DEBUG, "%s - found class %d constructed %d tag 0x%x",
title, hdr->class, hdr->constructed, hdr->tag);
}
int asn1_parse_oid(const u8 *buf, size_t len, struct asn1_oid *oid)
{
const u8 *pos, *end;
unsigned long val;
u8 tmp;
os_memset(oid, 0, sizeof(*oid));
pos = buf;
end = buf + len;
while (pos < end) {
val = 0;
do {
if (pos >= end)
return -1;
tmp = *pos++;
val = (val << 7) | (tmp & 0x7f);
} while (tmp & 0x80);
if (oid->len >= ASN1_MAX_OID_LEN) {
wpa_printf(MSG_DEBUG, "ASN.1: Too long OID value");
return -1;
}
if (oid->len == 0) {
/*
* The first octet encodes the first two object
* identifier components in (X*40) + Y formula.
* X = 0..2.
*/
oid->oid[0] = val / 40;
if (oid->oid[0] > 2)
oid->oid[0] = 2;
oid->oid[1] = val - oid->oid[0] * 40;
oid->len = 2;
} else
oid->oid[oid->len++] = val;
}
return 0;
}
int asn1_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid,
const u8 **next)
{
struct asn1_hdr hdr;
if (asn1_get_next(buf, len, &hdr) < 0 || hdr.length == 0 ||
!asn1_is_oid(&hdr)) {
asn1_unexpected(&hdr, "ASN.1: Expected OID");
return -1;
}
*next = hdr.payload + hdr.length;
return asn1_parse_oid(hdr.payload, hdr.length, oid);
}
void asn1_oid_to_str(const struct asn1_oid *oid, char *buf, size_t len)
{
char *pos = buf;
size_t i;
int ret;
if (len == 0)
return;
buf[0] = '\0';
for (i = 0; i < oid->len; i++) {
ret = os_snprintf(pos, buf + len - pos,
"%s%lu",
i == 0 ? "" : ".", oid->oid[i]);
if (os_snprintf_error(buf + len - pos, ret))
break;
pos += ret;
}
buf[len - 1] = '\0';
}
static u8 rotate_bits(u8 octet)
{
int i;
u8 res;
res = 0;
for (i = 0; i < 8; i++) {
res <<= 1;
if (octet & 1)
res |= 1;
octet >>= 1;
}
return res;
}
unsigned long asn1_bit_string_to_long(const u8 *buf, size_t len)
{
unsigned long val = 0;
const u8 *pos = buf;
/* BER requires that unused bits are zero, so we can ignore the number
* of unused bits */
pos++;
if (len >= 2)
val |= rotate_bits(*pos++);
if (len >= 3)
val |= ((unsigned long) rotate_bits(*pos++)) << 8;
if (len >= 4)
val |= ((unsigned long) rotate_bits(*pos++)) << 16;
if (len >= 5)
val |= ((unsigned long) rotate_bits(*pos++)) << 24;
if (len >= 6)
wpa_printf(MSG_DEBUG, "X509: %s - some bits ignored "
"(BIT STRING length %lu)",
__func__, (unsigned long) len);
return val;
}
int asn1_oid_equal(const struct asn1_oid *a, const struct asn1_oid *b)
{
size_t i;
if (a->len != b->len)
return 0;
for (i = 0; i < a->len; i++) {
if (a->oid[i] != b->oid[i])
return 0;
}
return 1;
}
int asn1_get_integer(const u8 *buf, size_t len, int *integer, const u8 **next)
{
struct asn1_hdr hdr;
size_t left;
const u8 *pos;
int value;
if (asn1_get_next(buf, len, &hdr) < 0 || hdr.length == 0 ||
!asn1_is_integer(&hdr)) {
asn1_unexpected(&hdr, "ASN.1: Expected INTEGER");
return -1;
}
*next = hdr.payload + hdr.length;
pos = hdr.payload;
left = hdr.length;
if (left > sizeof(value)) {
wpa_printf(MSG_DEBUG, "ASN.1: Too large INTEGER (len %u)",
hdr.length);
return -1;
}
value = 0;
while (left) {
value <<= 8;
value |= *pos++;
left--;
}
*integer = value;
return 0;
}
int asn1_get_sequence(const u8 *buf, size_t len, struct asn1_hdr *hdr,
const u8 **next)
{
if (asn1_get_next(buf, len, hdr) < 0 || !asn1_is_sequence(hdr)) {
asn1_unexpected(hdr, "ASN.1: Expected SEQUENCE");
return -1;
}
if (next)
*next = hdr->payload + hdr->length;
return 0;
}
int asn1_get_alg_id(const u8 *buf, size_t len, struct asn1_oid *oid,
const u8 **params, size_t *params_len, const u8 **next)
{
const u8 *pos = buf, *end = buf + len;
struct asn1_hdr hdr;
/*
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL}
*/
if (asn1_get_sequence(pos, end - pos, &hdr, next) < 0 ||
asn1_get_oid(hdr.payload, hdr.length, oid, &pos) < 0)
return -1;
if (params && params_len) {
*params = pos;
*params_len = hdr.payload + hdr.length - pos;
}
return 0;
}
void asn1_put_integer(struct wpabuf *buf, int val)
{
u8 bin[4];
int zeros;
WPA_PUT_BE32(bin, val);
zeros = 0;
while (zeros < 3 && bin[zeros] == 0)
zeros++;
wpabuf_put_u8(buf, ASN1_TAG_INTEGER);
wpabuf_put_u8(buf, 4 - zeros);
wpabuf_put_data(buf, &bin[zeros], 4 - zeros);
}
static void asn1_put_len(struct wpabuf *buf, size_t len)
{
if (len <= 0x7f) {
wpabuf_put_u8(buf, len);
} else if (len <= 0xff) {
wpabuf_put_u8(buf, 0x80 | 1);
wpabuf_put_u8(buf, len);
} else if (len <= 0xffff) {
wpabuf_put_u8(buf, 0x80 | 2);
wpabuf_put_be16(buf, len);
} else if (len <= 0xffffff) {
wpabuf_put_u8(buf, 0x80 | 3);
wpabuf_put_be24(buf, len);
} else {
wpabuf_put_u8(buf, 0x80 | 4);
wpabuf_put_be32(buf, len);
}
}
void asn1_put_octet_string(struct wpabuf *buf, const struct wpabuf *val)
{
wpabuf_put_u8(buf, ASN1_TAG_OCTETSTRING);
asn1_put_len(buf, wpabuf_len(val));
wpabuf_put_buf(buf, val);
}
void asn1_put_oid(struct wpabuf *buf, const struct asn1_oid *oid)
{
u8 *len;
size_t i;
if (oid->len < 2)
return;
wpabuf_put_u8(buf, ASN1_TAG_OID);
len = wpabuf_put(buf, 1);
wpabuf_put_u8(buf, 40 * oid->oid[0] + oid->oid[1]);
for (i = 2; i < oid->len; i++) {
unsigned long val = oid->oid[i];
u8 bytes[8];
int idx = 0;
while (val) {
bytes[idx] = (idx ? 0x80 : 0x00) | (val & 0x7f);
idx++;
val >>= 7;
}
if (idx == 0) {
bytes[idx] = 0;
idx = 1;
}
while (idx > 0) {
idx--;
wpabuf_put_u8(buf, bytes[idx]);
}
}
*len = (u8 *) wpabuf_put(buf, 0) - len - 1;
}
void asn1_put_hdr(struct wpabuf *buf, u8 class, int constructed, u8 tag,
size_t len)
{
wpabuf_put_u8(buf, class << 6 | (constructed ? 0x20 : 0x00) | tag);
asn1_put_len(buf, len);
}
void asn1_put_sequence(struct wpabuf *buf, const struct wpabuf *payload)
{
asn1_put_hdr(buf, ASN1_CLASS_UNIVERSAL, 1, ASN1_TAG_SEQUENCE,
wpabuf_len(payload));
wpabuf_put_buf(buf, payload);
}
void asn1_put_set(struct wpabuf *buf, const struct wpabuf *payload)
{
asn1_put_hdr(buf, ASN1_CLASS_UNIVERSAL, 1, ASN1_TAG_SET,
wpabuf_len(payload));
wpabuf_put_buf(buf, payload);
}
void asn1_put_utf8string(struct wpabuf *buf, const char *val)
{
asn1_put_hdr(buf, ASN1_CLASS_UNIVERSAL, 0, ASN1_TAG_UTF8STRING,
os_strlen(val));
wpabuf_put_str(buf, val);
}
struct wpabuf * asn1_build_alg_id(const struct asn1_oid *oid,
const struct wpabuf *params)
{
struct wpabuf *buf;
size_t len;
/*
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL}
*/
len = 100;
if (params)
len += wpabuf_len(params);
buf = wpabuf_alloc(len);
if (!buf)
return NULL;
asn1_put_oid(buf, oid);
if (params)
wpabuf_put_buf(buf, params);
return asn1_encaps(buf, ASN1_CLASS_UNIVERSAL, ASN1_TAG_SEQUENCE);
}
struct wpabuf * asn1_encaps(struct wpabuf *buf, u8 class, u8 tag)
{
struct wpabuf *res;
if (!buf)
return NULL;
res = wpabuf_alloc(10 + wpabuf_len(buf));
if (res) {
asn1_put_hdr(res, class, 1, tag, wpabuf_len(buf));
wpabuf_put_buf(res, buf);
}
wpabuf_clear_free(buf);
return res;
}