1 /* $OpenBSD: bs_ber.c,v 1.2 2021/12/15 18:02:39 jsing Exp $ */ 2 /* 3 * Copyright (c) 2014, Google Inc. 4 * 5 * Permission to use, copy, modify, and/or distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 12 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 14 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <string.h> 19 20 #include "bytestring.h" 21 22 /* 23 * kMaxDepth is a just a sanity limit. The code should be such that the length 24 * of the input being processes always decreases. None the less, a very large 25 * input could otherwise cause the stack to overflow. 26 */ 27 static const unsigned int kMaxDepth = 2048; 28 29 /* Non-strict version that allows a relaxed DER with indefinite form. */ 30 static int 31 cbs_nonstrict_get_any_asn1_element(CBS *cbs, CBS *out, unsigned int *out_tag, 32 size_t *out_header_len) 33 { 34 return cbs_get_any_asn1_element_internal(cbs, out, 35 out_tag, out_header_len, 0); 36 } 37 38 /* 39 * cbs_find_indefinite walks an ASN.1 structure in |orig_in| and sets 40 * |*indefinite_found| depending on whether an indefinite length element was 41 * found. The value of |orig_in| is not modified. 42 * 43 * Returns one on success (i.e. |*indefinite_found| was set) and zero on error. 44 */ 45 static int 46 cbs_find_indefinite(const CBS *orig_in, char *indefinite_found, 47 unsigned int depth) 48 { 49 CBS in; 50 51 if (depth > kMaxDepth) 52 return 0; 53 54 CBS_init(&in, CBS_data(orig_in), CBS_len(orig_in)); 55 56 while (CBS_len(&in) > 0) { 57 CBS contents; 58 unsigned int tag; 59 size_t header_len; 60 61 if (!cbs_nonstrict_get_any_asn1_element(&in, &contents, &tag, 62 &header_len)) 63 return 0; 64 65 /* Indefinite form not allowed by DER. */ 66 if (CBS_len(&contents) == header_len && header_len > 0 && 67 CBS_data(&contents)[header_len - 1] == 0x80) { 68 *indefinite_found = 1; 69 return 1; 70 } 71 if (tag & CBS_ASN1_CONSTRUCTED) { 72 if (!CBS_skip(&contents, header_len) || 73 !cbs_find_indefinite(&contents, indefinite_found, 74 depth + 1)) 75 return 0; 76 } 77 } 78 79 *indefinite_found = 0; 80 return 1; 81 } 82 83 /* 84 * is_primitive_type returns true if |tag| likely a primitive type. Normally 85 * one can just test the "constructed" bit in the tag but, in BER, even 86 * primitive tags can have the constructed bit if they have indefinite 87 * length. 88 */ 89 static char 90 is_primitive_type(unsigned int tag) 91 { 92 return (tag & 0xc0) == 0 && 93 (tag & 0x1f) != (CBS_ASN1_SEQUENCE & 0x1f) && 94 (tag & 0x1f) != (CBS_ASN1_SET & 0x1f); 95 } 96 97 /* 98 * is_eoc returns true if |header_len| and |contents|, as returned by 99 * |cbs_nonstrict_get_any_asn1_element|, indicate an "end of contents" (EOC) 100 * value. 101 */ 102 static char 103 is_eoc(size_t header_len, CBS *contents) 104 { 105 const unsigned char eoc[] = {0x0, 0x0}; 106 107 return header_len == 2 && CBS_mem_equal(contents, eoc, 2); 108 } 109 110 /* 111 * cbs_convert_indefinite reads data with DER encoding (but relaxed to allow 112 * indefinite form) from |in| and writes definite form DER data to |out|. If 113 * |squash_header| is set then the top-level of elements from |in| will not 114 * have their headers written. This is used when concatenating the fragments of 115 * an indefinite length, primitive value. If |looking_for_eoc| is set then any 116 * EOC elements found will cause the function to return after consuming it. 117 * It returns one on success and zero on error. 118 */ 119 static int 120 cbs_convert_indefinite(CBS *in, CBB *out, char squash_header, 121 char looking_for_eoc, unsigned int depth) 122 { 123 if (depth > kMaxDepth) 124 return 0; 125 126 while (CBS_len(in) > 0) { 127 CBS contents; 128 unsigned int tag; 129 size_t header_len; 130 CBB *out_contents, out_contents_storage; 131 132 if (!cbs_nonstrict_get_any_asn1_element(in, &contents, &tag, 133 &header_len)) 134 return 0; 135 136 out_contents = out; 137 138 if (CBS_len(&contents) == header_len) { 139 if (is_eoc(header_len, &contents)) 140 return looking_for_eoc; 141 142 if (header_len > 0 && 143 CBS_data(&contents)[header_len - 1] == 0x80) { 144 /* 145 * This is an indefinite length element. If 146 * it's a SEQUENCE or SET then we just need to 147 * write the out the contents as normal, but 148 * with a concrete length prefix. 149 * 150 * If it's a something else then the contents 151 * will be a series of DER elements of the same 152 * type which need to be concatenated. 153 */ 154 const char context_specific = (tag & 0xc0) 155 == 0x80; 156 char squash_child_headers = 157 is_primitive_type(tag); 158 159 /* 160 * This is a hack, but it sufficies to handle 161 * NSS's output. If we find an indefinite 162 * length, context-specific tag with a definite, 163 * primtive tag inside it, then we assume that 164 * the context-specific tag is implicit and the 165 * tags within are fragments of a primitive type 166 * that need to be concatenated. 167 */ 168 if (context_specific && 169 (tag & CBS_ASN1_CONSTRUCTED)) { 170 CBS in_copy, inner_contents; 171 unsigned int inner_tag; 172 size_t inner_header_len; 173 174 CBS_init(&in_copy, CBS_data(in), 175 CBS_len(in)); 176 if (!cbs_nonstrict_get_any_asn1_element( 177 &in_copy, &inner_contents, 178 &inner_tag, &inner_header_len)) 179 return 0; 180 181 if (CBS_len(&inner_contents) > 182 inner_header_len && 183 is_primitive_type(inner_tag)) 184 squash_child_headers = 1; 185 } 186 187 if (!squash_header) { 188 unsigned int out_tag = tag; 189 190 if (squash_child_headers) 191 out_tag &= 192 ~CBS_ASN1_CONSTRUCTED; 193 194 if (!CBB_add_asn1(out, 195 &out_contents_storage, out_tag)) 196 return 0; 197 198 out_contents = &out_contents_storage; 199 } 200 201 if (!cbs_convert_indefinite(in, out_contents, 202 squash_child_headers, 203 1 /* looking for eoc */, depth + 1)) 204 return 0; 205 206 if (out_contents != out && !CBB_flush(out)) 207 return 0; 208 209 continue; 210 } 211 } 212 213 if (!squash_header) { 214 if (!CBB_add_asn1(out, &out_contents_storage, tag)) 215 return 0; 216 217 out_contents = &out_contents_storage; 218 } 219 220 if (!CBS_skip(&contents, header_len)) 221 return 0; 222 223 if (tag & CBS_ASN1_CONSTRUCTED) { 224 if (!cbs_convert_indefinite(&contents, out_contents, 225 0 /* don't squash header */, 226 0 /* not looking for eoc */, depth + 1)) 227 return 0; 228 } else { 229 if (!CBB_add_bytes(out_contents, CBS_data(&contents), 230 CBS_len(&contents))) 231 return 0; 232 } 233 234 if (out_contents != out && !CBB_flush(out)) 235 return 0; 236 } 237 238 return looking_for_eoc == 0; 239 } 240 241 int 242 CBS_asn1_indefinite_to_definite(CBS *in, uint8_t **out, size_t *out_len) 243 { 244 CBB cbb; 245 246 /* 247 * First, do a quick walk to find any indefinite-length elements. Most 248 * of the time we hope that there aren't any and thus we can quickly 249 * return. 250 */ 251 char conversion_needed; 252 if (!cbs_find_indefinite(in, &conversion_needed, 0)) 253 return 0; 254 255 if (!conversion_needed) { 256 *out = NULL; 257 *out_len = 0; 258 return 1; 259 } 260 261 if (!CBB_init(&cbb, CBS_len(in))) 262 return 0; 263 if (!cbs_convert_indefinite(in, &cbb, 0, 0, 0)) { 264 CBB_cleanup(&cbb); 265 return 0; 266 } 267 268 return CBB_finish(&cbb, out, out_len); 269 } 270