| /* |
| * libwebsockets - small server side websockets and web server implementation |
| * |
| * Copyright (C) 2010 - 2021 Andy Green <[email protected]> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to |
| * deal in the Software without restriction, including without limitation the |
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| * sell copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| * |
| * These are additional apis that belong in mbedtls but do not yet exist there. |
| * Alternaives are provided for lws to use that understand additional standard |
| * v3 tls extensions. Error results are simplified to lws style. |
| * |
| * This file includes code taken from mbedtls and modified, and from an as of |
| * 2021-06-11 unaccepted-upstream patch for mbedtls contributed by Gábor Tóth |
| * <[email protected]>. Gabor has graciously allowed use of his patch with more |
| * liberal terms but to not complicate matters I provide it here under the same |
| * Apache 2.0 terms as the mbedtls pieces. |
| * |
| * Those original pieces are licensed Apache-2.0 as follows |
| * |
| * Copyright The Mbed TLS Contributors |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| * not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "private-lib-core.h" |
| #include "private-lib-tls-mbedtls.h" |
| #include <mbedtls/oid.h> |
| #include <mbedtls/x509.h> |
| |
| /* |
| * This section from mbedtls oid.c |
| */ |
| |
| typedef struct { |
| mbedtls_oid_descriptor_t descriptor; |
| int ext_type; |
| } oid_x509_ext_t; |
| |
| #define ADD_LEN(s) s, MBEDTLS_OID_SIZE(s) |
| |
| #define LWS_MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER MBEDTLS_OID_ID_CE "\x23" /**< id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 } */ |
| #define LWS_MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER MBEDTLS_OID_ID_CE "\x0E" /**< id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 } */ |
| |
| #define LWS_MBEDTLS_OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER (1 << 0) |
| #define LWS_MBEDTLS_OID_X509_EXT_SUBJECT_KEY_IDENTIFIER (1 << 1) |
| |
| #define LWS_MBEDTLS_X509_EXT_AUTHORITY_KEY_IDENTIFIER LWS_MBEDTLS_OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER |
| #define LWS_MBEDTLS_X509_EXT_SUBJECT_KEY_IDENTIFIER LWS_MBEDTLS_OID_X509_EXT_SUBJECT_KEY_IDENTIFIER |
| |
| #define LWS_MBEDTLS_X509_SAN_OTHER_NAME 0 |
| #define LWS_MBEDTLS_X509_SAN_RFC822_NAME 1 |
| #define LWS_MBEDTLS_X509_SAN_DNS_NAME 2 |
| |
| #define LWS_MBEDTLS_ASN1_TAG_CLASS_MASK 0xC0 |
| #define LWS_MBEDTLS_ASN1_TAG_VALUE_MASK 0x1F |
| |
| static const oid_x509_ext_t oid_x509_ext[] = { |
| { {ADD_LEN( LWS_MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER ), |
| "id-ce-subjectKeyIdentifier", |
| "Subject Key Identifier" }, |
| LWS_MBEDTLS_OID_X509_EXT_SUBJECT_KEY_IDENTIFIER, |
| }, |
| { {ADD_LEN( LWS_MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER ), |
| "id-ce-authorityKeyIdentifier", |
| "Authority Key Identifier" }, |
| LWS_MBEDTLS_OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, |
| }, |
| { { NULL, 0, NULL, NULL }, 0 }, |
| }; |
| |
| #define FN_OID_TYPED_FROM_ASN1( TYPE_T, NAME, LIST ) \ |
| static const TYPE_T * oid_ ## NAME ## _from_asn1( \ |
| const mbedtls_asn1_buf *oid ) \ |
| { \ |
| const TYPE_T *p = (LIST); \ |
| const mbedtls_oid_descriptor_t *cur = \ |
| (const mbedtls_oid_descriptor_t *) p; \ |
| if( p == NULL || oid == NULL ) return( NULL ); \ |
| while( cur->MBEDTLS_PRIVATE(asn1) != NULL ) { \ |
| if( cur->MBEDTLS_PRIVATE(asn1_len) == oid->MBEDTLS_PRIVATE(len) && \ |
| memcmp( cur->MBEDTLS_PRIVATE(asn1), oid->MBEDTLS_PRIVATE(p), oid->MBEDTLS_PRIVATE(len) ) == 0 ) { \ |
| return( p ); \ |
| } \ |
| p++; \ |
| cur = (const mbedtls_oid_descriptor_t *) p; \ |
| } \ |
| return( NULL ); \ |
| } |
| |
| |
| #define FN_OID_GET_ATTR1(FN_NAME, TYPE_T, TYPE_NAME, ATTR1_TYPE, ATTR1) \ |
| int FN_NAME( const mbedtls_asn1_buf *oid, ATTR1_TYPE * ATTR1 ) \ |
| { \ |
| const TYPE_T *data = oid_ ## TYPE_NAME ## _from_asn1( oid ); \ |
| if (!data) return 1; \ |
| *ATTR1 = data->ATTR1; \ |
| return 0; \ |
| } |
| |
| FN_OID_TYPED_FROM_ASN1(oid_x509_ext_t, x509_ext, oid_x509_ext) |
| FN_OID_GET_ATTR1(lws_mbedtls_oid_get_x509_ext_type, |
| oid_x509_ext_t, x509_ext, int, ext_type) |
| |
| typedef struct lws_mbedtls_x509_san_other_name |
| { |
| /** |
| * The type_id is an OID as deifned in RFC 5280. |
| * To check the value of the type id, you should use |
| * \p MBEDTLS_OID_CMP with a known OID mbedtls_x509_buf. |
| */ |
| mbedtls_x509_buf type_id; /**< The type id. */ |
| union |
| { |
| /** |
| * From RFC 4108 section 5: |
| * HardwareModuleName ::= SEQUENCE { |
| * hwType OBJECT IDENTIFIER, |
| * hwSerialNum OCTET STRING } |
| */ |
| struct |
| { |
| mbedtls_x509_buf oid; /**< The object identifier. */ |
| mbedtls_x509_buf val; /**< The named value. */ |
| } |
| hardware_module_name; |
| } |
| value; |
| } |
| lws_mbedtls_x509_san_other_name; |
| |
| |
| typedef struct lws_mbedtls_x509_subject_alternative_name |
| { |
| int type; /**< The SAN type, value of LWS_MBEDTLS_X509_SAN_XXX. */ |
| union { |
| lws_mbedtls_x509_san_other_name other_name; /**< The otherName supported type. */ |
| mbedtls_x509_buf unstructured_name; /**< The buffer for the un constructed types. Only dnsName currently supported */ |
| } |
| san; /**< A union of the supported SAN types */ |
| } |
| lws_mbedtls_x509_subject_alternative_name; |
| |
| static int |
| x509_get_skid(uint8_t **p, const uint8_t *end, mbedtls_x509_buf *skid) |
| { |
| int ret = 1; |
| size_t len = 0u; |
| |
| ret = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_OCTET_STRING); |
| if (ret) |
| return ret; |
| |
| skid->MBEDTLS_PRIVATE(len) = len; |
| skid->MBEDTLS_PRIVATE(tag) = MBEDTLS_ASN1_OCTET_STRING; |
| skid->MBEDTLS_PRIVATE(p) = *p; |
| *p += len; |
| |
| return *p != end; |
| } |
| |
| /* |
| * Names may have multiple allocated segments in a linked-list, when the mbedtls |
| * api mbedtls_x509_get_name() fails, it doesn't clean up any already-allocated |
| * segments, wrongly leaving it to the caller to handle. This helper takes care |
| * of the missing cleaning for allocation error path. |
| * |
| * name.next must be set to NULL by user code before calling ...get_name(..., |
| * &name), since not every error exit sets it and it will contain garbage if |
| * defined on stack as is usual. |
| */ |
| |
| static void |
| lws_x509_clean_name(mbedtls_x509_name *name) |
| { |
| mbedtls_x509_name *n1; |
| |
| if (!name) |
| return; |
| |
| n1 = name->MBEDTLS_PRIVATE(next); |
| |
| while (n1) { |
| name = n1->MBEDTLS_PRIVATE(next); |
| free(n1); |
| n1 = name; |
| } |
| } |
| |
| static int |
| lws_mbedtls_x509_parse_general_name(const mbedtls_x509_buf *name_buf, |
| lws_mbedtls_x509_subject_alternative_name *name) |
| { |
| // mbedtls_x509_name_other_name other_name; |
| uint8_t *bufferPointer, **p, *end; |
| mbedtls_x509_name rfc822Name; |
| int ret; |
| |
| switch (name_buf->MBEDTLS_PRIVATE(tag) & |
| (LWS_MBEDTLS_ASN1_TAG_CLASS_MASK | |
| LWS_MBEDTLS_ASN1_TAG_VALUE_MASK)) { |
| |
| #if 0 |
| case MBEDTLS_ASN1_CONTEXT_SPECIFIC | LWS_MBEDTLS_X509_SAN_OTHER_NAME: |
| ret = x509_get_other_name( name_buf, &other_name ); |
| if (ret) |
| return ret; |
| |
| memset(name, 0, sizeof(*name)); |
| name->type = LWS_MBEDTLS_X509_SAN_OTHER_NAME; |
| memcpy(&name->name.other_name, &other_name, sizeof(other_name)); |
| return 0; |
| #endif |
| case MBEDTLS_ASN1_SEQUENCE | LWS_MBEDTLS_X509_SAN_RFC822_NAME: |
| |
| bufferPointer = name_buf->MBEDTLS_PRIVATE(p); |
| p = &bufferPointer; |
| end = name_buf->MBEDTLS_PRIVATE(p) + |
| name_buf->MBEDTLS_PRIVATE(len); |
| |
| /* The leading ASN1 tag and length has been processed. |
| * Stepping back with 2 bytes, because mbedtls_x509_get_name |
| * expects the beginning of the SET tag */ |
| *p = *p - 2; |
| |
| rfc822Name.MBEDTLS_PRIVATE(next) = NULL; |
| ret = mbedtls_x509_get_name( p, end, &rfc822Name ); |
| if (ret) { |
| lws_x509_clean_name(&rfc822Name); |
| return ret; |
| } |
| |
| memset(name, 0, sizeof(*name)); |
| name->type = LWS_MBEDTLS_X509_SAN_OTHER_NAME; |
| memcpy(&name->san.other_name, |
| &rfc822Name, sizeof(rfc822Name)); |
| return 0; |
| |
| case MBEDTLS_ASN1_CONTEXT_SPECIFIC | LWS_MBEDTLS_X509_SAN_DNS_NAME: |
| memset(name, 0, sizeof(*name)); |
| name->type = LWS_MBEDTLS_X509_SAN_DNS_NAME; |
| |
| memcpy(&name->san.unstructured_name, |
| name_buf, sizeof(*name_buf) ); |
| return 0; |
| |
| default: |
| return 1; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| lws_x509_get_general_names(uint8_t **p, const uint8_t *end, |
| mbedtls_x509_sequence *name ) |
| { |
| mbedtls_asn1_sequence *cur = name; |
| mbedtls_asn1_buf *buf; |
| size_t len, tag_len; |
| unsigned char tag; |
| int r; |
| |
| /* Get main sequence tag */ |
| r = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | |
| MBEDTLS_ASN1_SEQUENCE); |
| if (r) |
| return r; |
| |
| if (*p + len != end) |
| return 1; |
| |
| while (*p < end) { |
| lws_mbedtls_x509_subject_alternative_name dnb; |
| memset(&dnb, 0, sizeof(dnb)); |
| |
| tag = **p; |
| (*p)++; |
| |
| r = mbedtls_asn1_get_len(p, end, &tag_len); |
| if (r) |
| return r; |
| |
| /* Tag shall be CONTEXT_SPECIFIC or SET */ |
| if ((tag & LWS_MBEDTLS_ASN1_TAG_CLASS_MASK) != |
| MBEDTLS_ASN1_CONTEXT_SPECIFIC && |
| (tag & (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != |
| (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) |
| return 1; |
| |
| /* |
| * Check that the name is structured correctly. |
| */ |
| r = lws_mbedtls_x509_parse_general_name( |
| &cur->MBEDTLS_PRIVATE(buf), &dnb); |
| /* |
| * In case the extension is malformed, return an error, |
| * and clear the allocated sequences. |
| */ |
| if (r && r != MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE) { |
| mbedtls_x509_sequence *seq_cur = name->MBEDTLS_PRIVATE(next); |
| mbedtls_x509_sequence *seq_prv; |
| |
| while( seq_cur != NULL ) { |
| seq_prv = seq_cur; |
| seq_cur = seq_cur->MBEDTLS_PRIVATE(next); |
| lws_explicit_bzero(seq_prv, sizeof(*seq_cur)); |
| lws_free(seq_prv); |
| } |
| |
| name->MBEDTLS_PRIVATE(next) = NULL; |
| |
| return r; |
| } |
| |
| /* Allocate and assign next pointer */ |
| if (cur->MBEDTLS_PRIVATE(buf).MBEDTLS_PRIVATE(p)) { |
| if (cur->MBEDTLS_PRIVATE(next)) |
| return 1; |
| |
| cur->MBEDTLS_PRIVATE(next) = |
| lws_zalloc(sizeof(*cur), __func__); |
| |
| if (!cur->MBEDTLS_PRIVATE(next)) |
| return 1; |
| |
| cur = cur->MBEDTLS_PRIVATE(next); |
| } |
| |
| buf = &(cur->MBEDTLS_PRIVATE(buf)); |
| buf->MBEDTLS_PRIVATE(tag) = tag; |
| buf->MBEDTLS_PRIVATE(p) = *p; |
| buf->MBEDTLS_PRIVATE(len) = tag_len; |
| |
| *p += buf->MBEDTLS_PRIVATE(len); |
| } |
| |
| /* Set final sequence entry's next pointer to NULL */ |
| cur->MBEDTLS_PRIVATE(next) = NULL; |
| |
| return *p != end; |
| } |
| |
| static int |
| x509_get_akid(uint8_t **p, uint8_t *end, lws_mbedtls_x509_authority *akid) |
| { |
| size_t len = 0u; |
| int r; |
| |
| r = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | |
| MBEDTLS_ASN1_SEQUENCE); |
| if (r) |
| return r; |
| |
| r = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_CONTEXT_SPECIFIC); |
| if (!r) { |
| akid->keyIdentifier.MBEDTLS_PRIVATE(len) = len; |
| akid->keyIdentifier.MBEDTLS_PRIVATE(p) = *p; |
| akid->keyIdentifier.MBEDTLS_PRIVATE(tag) = MBEDTLS_ASN1_OCTET_STRING; |
| |
| *p += len; |
| } |
| |
| if (*p < end) { |
| /* Getting authorityCertIssuer using the required specific |
| * class tag [1] */ |
| r = mbedtls_asn1_get_tag(p, end, &len, |
| MBEDTLS_ASN1_CONTEXT_SPECIFIC | |
| MBEDTLS_ASN1_CONSTRUCTED | 1 ); |
| if (!r) { |
| /* Getting directoryName using the required specific |
| * class tag [4] */ |
| r = mbedtls_asn1_get_tag(p, end, &len, |
| MBEDTLS_ASN1_CONTEXT_SPECIFIC | |
| MBEDTLS_ASN1_CONSTRUCTED | 4); |
| if (r) |
| return(r); |
| |
| /* "end" also includes the CertSerialNumber field |
| * so "len" shall be used */ |
| r = lws_x509_get_general_names(p, (*p + len), |
| &akid->authorityCertIssuer); |
| } |
| } |
| |
| if (*p < end) { |
| r = mbedtls_asn1_get_tag(p, end, &len, |
| MBEDTLS_ASN1_CONTEXT_SPECIFIC | |
| MBEDTLS_ASN1_INTEGER ); |
| if (r) |
| return r; |
| |
| akid->authorityCertSerialNumber.MBEDTLS_PRIVATE(len) = len; |
| akid->authorityCertSerialNumber.MBEDTLS_PRIVATE(p) = *p; |
| akid->authorityCertSerialNumber.MBEDTLS_PRIVATE(tag) = MBEDTLS_ASN1_OCTET_STRING; |
| *p += len; |
| } |
| |
| return *p != end; |
| } |
| |
| /* |
| * Work around lack of this in mbedtls... we don't need to do sanity checks |
| * sanity checks because they will be done at x509 validation time |
| */ |
| |
| int |
| lws_x509_get_crt_ext(mbedtls_x509_crt *crt, mbedtls_x509_buf *skid, |
| lws_mbedtls_x509_authority *akid) |
| { |
| uint8_t *p = crt->MBEDTLS_PRIVATE(v3_ext).MBEDTLS_PRIVATE(p), |
| *end_ext_data, *end_ext_octet; |
| const uint8_t *end = p + crt->MBEDTLS_PRIVATE(v3_ext).MBEDTLS_PRIVATE(len); |
| size_t len; |
| int r = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | |
| MBEDTLS_ASN1_SEQUENCE); |
| if (r) |
| return r; |
| |
| while (p < end) { |
| mbedtls_x509_buf extn_oid = { 0, 0, NULL }; |
| int is_critical = 0; /* DEFAULT FALSE */ |
| int ext_type = 0; |
| |
| r = mbedtls_asn1_get_tag(&p, end, &len, |
| MBEDTLS_ASN1_CONSTRUCTED | |
| MBEDTLS_ASN1_SEQUENCE); |
| if (r) |
| return r; |
| |
| end_ext_data = p + len; |
| |
| /* Get extension ID */ |
| r = mbedtls_asn1_get_tag(&p, end_ext_data, &extn_oid.MBEDTLS_PRIVATE(len), |
| MBEDTLS_ASN1_OID); |
| if (r) |
| return r; |
| |
| extn_oid.MBEDTLS_PRIVATE(tag) = MBEDTLS_ASN1_OID; |
| extn_oid.MBEDTLS_PRIVATE(p) = p; |
| p += extn_oid.MBEDTLS_PRIVATE(len); |
| |
| /* Get optional critical */ |
| r = mbedtls_asn1_get_bool(&p, end_ext_data, &is_critical); |
| if (r && r != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) |
| return r; |
| |
| /* Data should be octet string type */ |
| r = mbedtls_asn1_get_tag(&p, end_ext_data, &len, |
| MBEDTLS_ASN1_OCTET_STRING); |
| if (r) |
| return r; |
| |
| end_ext_octet = p + len; |
| |
| if (end_ext_octet != end_ext_data) |
| return 1; |
| |
| r = lws_mbedtls_oid_get_x509_ext_type(&extn_oid, &ext_type); |
| if (r) { |
| p = end_ext_octet; |
| continue; |
| } |
| |
| switch (ext_type) { |
| case LWS_MBEDTLS_X509_EXT_SUBJECT_KEY_IDENTIFIER: |
| /* Parse subject key identifier */ |
| r = x509_get_skid(&p, end_ext_data, skid); |
| if (r) |
| return r; |
| break; |
| |
| case LWS_MBEDTLS_X509_EXT_AUTHORITY_KEY_IDENTIFIER: |
| /* Parse authority key identifier */ |
| r = x509_get_akid(&p, end_ext_octet, akid); |
| if (r) |
| return r; |
| break; |
| |
| default: |
| p = end_ext_octet; |
| } |
| } |
| |
| return 0; |
| } |