Coverage Report

Created: 2025-11-17 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}