/src/boringssl/crypto/bytestring/ber.cc
Line | Count | Source |
1 | | // Copyright 2014 The BoringSSL Authors |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include <openssl/bytestring.h> |
16 | | |
17 | | #include <assert.h> |
18 | | #include <string.h> |
19 | | |
20 | | #include "internal.h" |
21 | | |
22 | | |
23 | | // kMaxDepth limits the recursion depth to avoid overflowing the stack. |
24 | | static const uint32_t kMaxDepth = 128; |
25 | | |
26 | | // is_string_type returns one if |tag| is a string type and zero otherwise. It |
27 | | // ignores the constructed bit. |
28 | 45.9k | static int is_string_type(CBS_ASN1_TAG tag) { |
29 | | // While BER supports constructed BIT STRINGS, OpenSSL misparses them. To |
30 | | // avoid acting on an ambiguous input, we do not support constructed BIT |
31 | | // STRINGS. See https://github.com/openssl/openssl/issues/12810. |
32 | 45.9k | switch (tag & ~CBS_ASN1_CONSTRUCTED) { |
33 | 976 | case CBS_ASN1_OCTETSTRING: |
34 | 1.10k | case CBS_ASN1_UTF8STRING: |
35 | 1.51k | case CBS_ASN1_NUMERICSTRING: |
36 | 1.68k | case CBS_ASN1_PRINTABLESTRING: |
37 | 1.90k | case CBS_ASN1_T61STRING: |
38 | 2.15k | case CBS_ASN1_VIDEOTEXSTRING: |
39 | 2.27k | case CBS_ASN1_IA5STRING: |
40 | 2.47k | case CBS_ASN1_GRAPHICSTRING: |
41 | 2.74k | case CBS_ASN1_VISIBLESTRING: |
42 | 3.08k | case CBS_ASN1_GENERALSTRING: |
43 | 3.32k | case CBS_ASN1_UNIVERSALSTRING: |
44 | 3.44k | case CBS_ASN1_BMPSTRING: |
45 | 3.44k | return 1; |
46 | 42.5k | default: |
47 | 42.5k | return 0; |
48 | 45.9k | } |
49 | 45.9k | } |
50 | | |
51 | | // cbs_find_ber walks an ASN.1 structure in |orig_in| and sets |*ber_found| |
52 | | // depending on whether an indefinite length element or constructed string was |
53 | | // found. The value of |orig_in| is not changed. It returns one on success (i.e. |
54 | | // |*ber_found| was set) and zero on error. |
55 | 10.3k | static int cbs_find_ber(const CBS *orig_in, int *ber_found, uint32_t depth) { |
56 | 10.3k | if (depth > kMaxDepth) { |
57 | 0 | return 0; |
58 | 0 | } |
59 | | |
60 | 10.3k | CBS in = *orig_in; |
61 | 10.3k | *ber_found = 0; |
62 | | |
63 | 24.4k | while (CBS_len(&in) > 0) { |
64 | 18.7k | CBS contents; |
65 | 18.7k | CBS_ASN1_TAG tag; |
66 | 18.7k | size_t header_len; |
67 | 18.7k | int indefinite; |
68 | 18.7k | if (!CBS_get_any_ber_asn1_element(&in, &contents, &tag, &header_len, |
69 | 18.7k | ber_found, &indefinite)) { |
70 | 1.18k | return 0; |
71 | 1.18k | } |
72 | 17.5k | if (*ber_found) { |
73 | 1.59k | return 1; |
74 | 1.59k | } |
75 | 15.9k | if (tag & CBS_ASN1_CONSTRUCTED) { |
76 | 6.48k | if (is_string_type(tag)) { |
77 | | // Constructed strings are only legal in BER and require conversion. |
78 | 434 | *ber_found = 1; |
79 | 434 | return 1; |
80 | 434 | } |
81 | 6.04k | if (!CBS_skip(&contents, header_len) || |
82 | 6.04k | !cbs_find_ber(&contents, ber_found, depth + 1)) { |
83 | 301 | return 0; |
84 | 301 | } |
85 | 5.74k | if (*ber_found) { |
86 | | // We already found BER. No need to continue parsing. |
87 | 1.14k | return 1; |
88 | 1.14k | } |
89 | 5.74k | } |
90 | 15.9k | } |
91 | | |
92 | 5.69k | return 1; |
93 | 10.3k | } |
94 | | |
95 | | // cbs_get_eoc returns one if |cbs| begins with an "end of contents" (EOC) value |
96 | | // and zero otherwise. If an EOC was found, it advances |cbs| past it. |
97 | 1.51M | static int cbs_get_eoc(CBS *cbs) { |
98 | 1.51M | if (CBS_len(cbs) >= 2 && |
99 | 1.51M | CBS_data(cbs)[0] == 0 && CBS_data(cbs)[1] == 0) { |
100 | 14.8k | return CBS_skip(cbs, 2); |
101 | 14.8k | } |
102 | 1.50M | return 0; |
103 | 1.51M | } |
104 | | |
105 | | // cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If |
106 | | // |string_tag| is non-zero, then all elements must match |string_tag| up to the |
107 | | // constructed bit and primitive element bodies are written to |out| without |
108 | | // element headers. This is used when concatenating the fragments of a |
109 | | // constructed string. If |looking_for_eoc| is set then any EOC elements found |
110 | | // will cause the function to return after consuming it. It returns one on |
111 | | // success and zero on error. |
112 | | static int cbs_convert_ber(CBS *in, CBB *out, CBS_ASN1_TAG string_tag, |
113 | 42.2k | int looking_for_eoc, uint32_t depth) { |
114 | 42.2k | assert(!(string_tag & CBS_ASN1_CONSTRUCTED)); |
115 | | |
116 | 42.2k | if (depth > kMaxDepth) { |
117 | 3 | return 0; |
118 | 3 | } |
119 | | |
120 | 1.57M | while (CBS_len(in) > 0) { |
121 | 1.55M | if (looking_for_eoc && cbs_get_eoc(in)) { |
122 | 14.8k | return 1; |
123 | 14.8k | } |
124 | | |
125 | 1.54M | CBS contents; |
126 | 1.54M | CBS_ASN1_TAG tag, child_string_tag = string_tag; |
127 | 1.54M | size_t header_len; |
128 | 1.54M | int indefinite; |
129 | 1.54M | CBB *out_contents, out_contents_storage; |
130 | 1.54M | if (!CBS_get_any_ber_asn1_element(in, &contents, &tag, &header_len, |
131 | 1.54M | /*out_ber_found=*/nullptr, &indefinite)) { |
132 | 438 | return 0; |
133 | 438 | } |
134 | | |
135 | 1.54M | if (string_tag != 0) { |
136 | | // This is part of a constructed string. All elements must match |
137 | | // |string_tag| up to the constructed bit and get appended to |out| |
138 | | // without a child element. |
139 | 50.6k | if ((tag & ~CBS_ASN1_CONSTRUCTED) != string_tag) { |
140 | 126 | return 0; |
141 | 126 | } |
142 | 50.5k | out_contents = out; |
143 | 1.49M | } else { |
144 | 1.49M | CBS_ASN1_TAG out_tag = tag; |
145 | 1.49M | if ((tag & CBS_ASN1_CONSTRUCTED) && is_string_type(tag)) { |
146 | | // If a constructed string, clear the constructed bit and inform |
147 | | // children to concatenate bodies. |
148 | 3.00k | out_tag &= ~CBS_ASN1_CONSTRUCTED; |
149 | 3.00k | child_string_tag = out_tag; |
150 | 3.00k | } |
151 | 1.49M | if (!CBB_add_asn1(out, &out_contents_storage, out_tag)) { |
152 | 0 | return 0; |
153 | 0 | } |
154 | 1.49M | out_contents = &out_contents_storage; |
155 | 1.49M | } |
156 | | |
157 | 1.54M | if (indefinite) { |
158 | 20.9k | if (!cbs_convert_ber(in, out_contents, child_string_tag, |
159 | 20.9k | /*looking_for_eoc=*/1, depth + 1) || |
160 | 14.8k | !CBB_flush(out)) { |
161 | 6.12k | return 0; |
162 | 6.12k | } |
163 | 14.8k | continue; |
164 | 20.9k | } |
165 | | |
166 | 1.52M | if (!CBS_skip(&contents, header_len)) { |
167 | 0 | return 0; |
168 | 0 | } |
169 | | |
170 | 1.52M | if (tag & CBS_ASN1_CONSTRUCTED) { |
171 | | // Recurse into children. |
172 | 19.2k | if (!cbs_convert_ber(&contents, out_contents, child_string_tag, |
173 | 19.2k | /*looking_for_eoc=*/0, depth + 1)) { |
174 | 1.48k | return 0; |
175 | 1.48k | } |
176 | 1.50M | } else { |
177 | | // Copy primitive contents as-is. |
178 | 1.50M | if (!CBB_add_bytes(out_contents, CBS_data(&contents), |
179 | 1.50M | CBS_len(&contents))) { |
180 | 0 | return 0; |
181 | 0 | } |
182 | 1.50M | } |
183 | | |
184 | 1.51M | if (!CBB_flush(out)) { |
185 | 0 | return 0; |
186 | 0 | } |
187 | 1.51M | } |
188 | | |
189 | 19.2k | return looking_for_eoc == 0; |
190 | 42.2k | } |
191 | | |
192 | 4.31k | int CBS_asn1_ber_to_der(CBS *in, CBS *out, uint8_t **out_storage) { |
193 | 4.31k | CBB cbb; |
194 | | |
195 | | // First, do a quick walk to find any indefinite-length elements. Most of the |
196 | | // time we hope that there aren't any and thus we can quickly return. |
197 | 4.31k | int conversion_needed; |
198 | 4.31k | if (!cbs_find_ber(in, &conversion_needed, 0)) { |
199 | 1.18k | return 0; |
200 | 1.18k | } |
201 | | |
202 | 3.13k | if (!conversion_needed) { |
203 | 1.10k | if (!CBS_get_any_asn1_element(in, out, nullptr, nullptr)) { |
204 | 0 | return 0; |
205 | 0 | } |
206 | 1.10k | *out_storage = nullptr; |
207 | 1.10k | return 1; |
208 | 1.10k | } |
209 | | |
210 | 2.03k | size_t len; |
211 | 2.03k | if (!CBB_init(&cbb, CBS_len(in)) || |
212 | 2.03k | !cbs_convert_ber(in, &cbb, 0, 0, 0) || |
213 | 1.23k | !CBB_finish(&cbb, out_storage, &len)) { |
214 | 800 | CBB_cleanup(&cbb); |
215 | 800 | return 0; |
216 | 800 | } |
217 | | |
218 | 1.23k | CBS_init(out, *out_storage, len); |
219 | 1.23k | return 1; |
220 | 2.03k | } |
221 | | |
222 | | int CBS_get_asn1_implicit_string(CBS *in, CBS *out, uint8_t **out_storage, |
223 | | CBS_ASN1_TAG outer_tag, |
224 | 6 | CBS_ASN1_TAG inner_tag) { |
225 | 6 | assert(!(outer_tag & CBS_ASN1_CONSTRUCTED)); |
226 | 6 | assert(!(inner_tag & CBS_ASN1_CONSTRUCTED)); |
227 | 6 | assert(is_string_type(inner_tag)); |
228 | | |
229 | 6 | if (CBS_peek_asn1_tag(in, outer_tag)) { |
230 | | // Normal implicitly-tagged string. |
231 | 5 | *out_storage = nullptr; |
232 | 5 | return CBS_get_asn1(in, out, outer_tag); |
233 | 5 | } |
234 | | |
235 | | // Otherwise, try to parse an implicitly-tagged constructed string. |
236 | | // |CBS_asn1_ber_to_der| is assumed to have run, so only allow one level deep |
237 | | // of nesting. |
238 | 1 | CBB result; |
239 | 1 | CBS child; |
240 | 1 | if (!CBB_init(&result, CBS_len(in)) || |
241 | 1 | !CBS_get_asn1(in, &child, outer_tag | CBS_ASN1_CONSTRUCTED)) { |
242 | 0 | goto err; |
243 | 0 | } |
244 | | |
245 | 3 | while (CBS_len(&child) > 0) { |
246 | 2 | CBS chunk; |
247 | 2 | if (!CBS_get_asn1(&child, &chunk, inner_tag) || |
248 | 2 | !CBB_add_bytes(&result, CBS_data(&chunk), CBS_len(&chunk))) { |
249 | 0 | goto err; |
250 | 0 | } |
251 | 2 | } |
252 | | |
253 | 1 | uint8_t *data; |
254 | 1 | size_t len; |
255 | 1 | if (!CBB_finish(&result, &data, &len)) { |
256 | 0 | goto err; |
257 | 0 | } |
258 | | |
259 | 1 | CBS_init(out, data, len); |
260 | 1 | *out_storage = data; |
261 | 1 | return 1; |
262 | | |
263 | 0 | err: |
264 | 0 | CBB_cleanup(&result); |
265 | 0 | return 0; |
266 | 1 | } |