/* * ASN.1 DER parsing * Copyright (c) 2006-2014, Jouni Malinen * * 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; }