Coverage Report

Created: 2025-12-07 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/boringssl/pki/verify_name_match.cc
Line
Count
Source
1
// Copyright 2015 The Chromium 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 "verify_name_match.h"
16
17
#include <openssl/base.h>
18
#include <openssl/bytestring.h>
19
20
#include "cert_error_params.h"
21
#include "cert_errors.h"
22
#include "input.h"
23
#include "parse_name.h"
24
#include "parser.h"
25
26
BSSL_NAMESPACE_BEGIN
27
28
DEFINE_CERT_ERROR_ID(kFailedConvertingAttributeValue,
29
                     "Failed converting AttributeValue to string");
30
DEFINE_CERT_ERROR_ID(kFailedNormalizingString, "Failed normalizing string");
31
32
namespace {
33
34
// Types of character set checking that NormalizeDirectoryString can perform.
35
enum CharsetEnforcement {
36
  NO_ENFORCEMENT,
37
  ENFORCE_PRINTABLE_STRING,
38
  ENFORCE_ASCII,
39
};
40
41
// Normalizes |output|, a UTF-8 encoded string, as if it contained
42
// only ASCII characters.
43
//
44
// This could be considered a partial subset of RFC 5280 rules, and
45
// is compatible with RFC 2459/3280.
46
//
47
// In particular, RFC 5280, Section 7.1 describes how UTF8String
48
// and PrintableString should be compared - using the LDAP StringPrep
49
// profile of RFC 4518, with case folding and whitespace compression.
50
// However, because it is optional for 2459/3280 implementations and because
51
// it's desirable to avoid the size cost of the StringPrep tables,
52
// this function treats |output| as if it was composed of ASCII.
53
//
54
// That is, rather than folding all whitespace characters, it only
55
// folds ' '. Rather than case folding using locale-aware handling,
56
// it only folds A-Z to a-z.
57
//
58
// This gives better results than outright rejecting (due to mismatched
59
// encodings), or from doing a strict binary comparison (the minimum
60
// required by RFC 3280), and is sufficient for those certificates
61
// publicly deployed.
62
//
63
// If |charset_enforcement| is not NO_ENFORCEMENT and |output| contains any
64
// characters not allowed in the specified charset, returns false.
65
//
66
// NOTE: |output| will be modified regardless of the return.
67
[[nodiscard]] bool NormalizeDirectoryString(
68
394k
    CharsetEnforcement charset_enforcement, std::string *output) {
69
  // Normalized version will always be equal or shorter than input.
70
  // Normalize in place and then truncate the output if necessary.
71
394k
  std::string::const_iterator read_iter = output->begin();
72
394k
  std::string::iterator write_iter = output->begin();
73
74
411k
  for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) {
75
    // Ignore leading whitespace.
76
17.1k
  }
77
78
35.4M
  for (; read_iter != output->end(); ++read_iter) {
79
35.0M
    const unsigned char c = *read_iter;
80
35.0M
    if (c == ' ') {
81
      // If there are non-whitespace characters remaining in input, compress
82
      // multiple whitespace chars to a single space, otherwise ignore trailing
83
      // whitespace.
84
451k
      std::string::const_iterator next_iter = read_iter + 1;
85
451k
      if (next_iter != output->end() && *next_iter != ' ') {
86
418k
        *(write_iter++) = ' ';
87
418k
      }
88
34.6M
    } else if (c >= 'A' && c <= 'Z') {
89
      // Fold case.
90
88.6k
      *(write_iter++) = c + ('a' - 'A');
91
34.5M
    } else {
92
      // Note that these checks depend on the characters allowed by earlier
93
      // conditions also being valid for the enforced charset.
94
34.5M
      switch (charset_enforcement) {
95
123k
        case ENFORCE_PRINTABLE_STRING:
96
          // See NormalizePrintableStringValue comment for the acceptable list
97
          // of characters.
98
123k
          if (!((c >= 'a' && c <= 'z') || (c >= '\'' && c <= ':') || c == '=' ||
99
1.93k
                c == '?')) {
100
603
            return false;
101
603
          }
102
122k
          break;
103
916k
        case ENFORCE_ASCII:
104
916k
          if (c > 0x7F) {
105
213
            return false;
106
213
          }
107
916k
          break;
108
33.5M
        case NO_ENFORCEMENT:
109
33.5M
          break;
110
34.5M
      }
111
34.5M
      *(write_iter++) = c;
112
34.5M
    }
113
35.0M
  }
114
393k
  if (write_iter != output->end()) {
115
35.8k
    output->erase(write_iter, output->end());
116
35.8k
  }
117
393k
  return true;
118
394k
}
119
120
// Converts the value of X509NameAttribute |attribute| to UTF-8, normalizes it,
121
// and stores in |output|. The type of |attribute| must be one of the types for
122
// which IsNormalizableDirectoryString is true.
123
//
124
// If the value of |attribute| can be normalized, returns true and sets
125
// |output| to the case folded, normalized value. If the value of |attribute|
126
// is invalid, returns false.
127
// NOTE: |output| will be modified regardless of the return.
128
[[nodiscard]] bool NormalizeValue(X509NameAttribute attribute,
129
395k
                                  std::string *output, CertErrors *errors) {
130
395k
  BSSL_CHECK(errors);
131
132
395k
  if (!attribute.ValueAsStringUnsafe(output)) {
133
1.67k
    errors->AddError(kFailedConvertingAttributeValue,
134
1.67k
                     CreateCertErrorParams1SizeT("tag", attribute.value_tag));
135
1.67k
    return false;
136
1.67k
  }
137
138
394k
  bool success = false;
139
394k
  switch (attribute.value_tag) {
140
52.3k
    case CBS_ASN1_PRINTABLESTRING:
141
52.3k
      success = NormalizeDirectoryString(ENFORCE_PRINTABLE_STRING, output);
142
52.3k
      break;
143
142k
    case CBS_ASN1_BMPSTRING:
144
149k
    case CBS_ASN1_UNIVERSALSTRING:
145
310k
    case CBS_ASN1_UTF8STRING:
146
310k
      success = NormalizeDirectoryString(NO_ENFORCEMENT, output);
147
310k
      break;
148
30.6k
    case CBS_ASN1_IA5STRING:
149
30.6k
      success = NormalizeDirectoryString(ENFORCE_ASCII, output);
150
30.6k
      break;
151
0
    default:
152
      // NOTREACHED
153
0
      success = false;
154
0
      break;
155
394k
  }
156
157
394k
  if (!success) {
158
816
    errors->AddError(kFailedNormalizingString,
159
816
                     CreateCertErrorParams1SizeT("tag", attribute.value_tag));
160
816
  }
161
162
394k
  return success;
163
394k
}
164
165
// Returns true if |tag| is a string type that NormalizeValue can handle.
166
418k
bool IsNormalizableDirectoryString(CBS_ASN1_TAG tag) {
167
418k
  switch (tag) {
168
52.5k
    case CBS_ASN1_PRINTABLESTRING:
169
214k
    case CBS_ASN1_UTF8STRING:
170
    // RFC 5280 only requires handling IA5String for comparing domainComponent
171
    // values, but handling it here avoids the need to special case anything.
172
245k
    case CBS_ASN1_IA5STRING:
173
253k
    case CBS_ASN1_UNIVERSALSTRING:
174
397k
    case CBS_ASN1_BMPSTRING:
175
397k
      return true;
176
    // TeletexString isn't normalized. Section 8 of RFC 5280 briefly
177
    // describes the historical confusion between treating TeletexString
178
    // as Latin1String vs T.61, and there are even incompatibilities within
179
    // T.61 implementations. As this time is virtually unused, simply
180
    // treat it with a binary comparison, as permitted by RFC 3280/5280.
181
21.0k
    default:
182
21.0k
      return false;
183
418k
  }
184
418k
}
185
186
// Returns true if the value of X509NameAttribute |a| matches |b|.
187
10.4k
bool VerifyValueMatch(X509NameAttribute a, X509NameAttribute b) {
188
10.4k
  if (IsNormalizableDirectoryString(a.value_tag) &&
189
8.65k
      IsNormalizableDirectoryString(b.value_tag)) {
190
8.10k
    std::string a_normalized, b_normalized;
191
    // TODO(eroman): Plumb this down.
192
8.10k
    CertErrors unused_errors;
193
8.10k
    if (!NormalizeValue(a, &a_normalized, &unused_errors) ||
194
6.98k
        !NormalizeValue(b, &b_normalized, &unused_errors)) {
195
2.20k
      return false;
196
2.20k
    }
197
5.90k
    return a_normalized == b_normalized;
198
8.10k
  }
199
  // Attributes encoded with different types may be assumed to be unequal.
200
2.29k
  if (a.value_tag != b.value_tag) {
201
1.42k
    return false;
202
1.42k
  }
203
  // All other types use binary comparison.
204
871
  return a.value == b.value;
205
2.29k
}
206
207
// Verifies that |a_parser| and |b_parser| are the same length and that every
208
// AttributeTypeAndValue in |a_parser| has a matching AttributeTypeAndValue in
209
// |b_parser|.
210
5.14k
bool VerifyRdnMatch(der::Parser *a_parser, der::Parser *b_parser) {
211
5.14k
  RelativeDistinguishedName a_type_and_values, b_type_and_values;
212
5.14k
  if (!ReadRdn(a_parser, &a_type_and_values) ||
213
4.96k
      !ReadRdn(b_parser, &b_type_and_values)) {
214
225
    return false;
215
225
  }
216
217
  // RFC 5280 section 7.1:
218
  // Two relative distinguished names RDN1 and RDN2 match if they have the same
219
  // number of naming attributes and for each naming attribute in RDN1 there is
220
  // a matching naming attribute in RDN2.
221
4.92k
  if (a_type_and_values.size() != b_type_and_values.size()) {
222
61
    return false;
223
61
  }
224
225
  // The ordering of elements may differ due to denormalized values sorting
226
  // differently in the DER encoding. Since the number of elements should be
227
  // small, a naive linear search for each element should be fine. (Hostile
228
  // certificates already have ways to provoke pathological behavior.)
229
6.62k
  for (const auto &a : a_type_and_values) {
230
6.62k
    auto b_iter = b_type_and_values.begin();
231
13.6k
    for (; b_iter != b_type_and_values.end(); ++b_iter) {
232
10.6k
      const auto &b = *b_iter;
233
10.6k
      if (a.type == b.type && VerifyValueMatch(a, b)) {
234
3.62k
        break;
235
3.62k
      }
236
10.6k
    }
237
6.62k
    if (b_iter == b_type_and_values.end()) {
238
3.00k
      return false;
239
3.00k
    }
240
    // Remove the matched element from b_type_and_values to ensure duplicate
241
    // elements in a_type_and_values can't match the same element in
242
    // b_type_and_values multiple times.
243
3.62k
    b_type_and_values.erase(b_iter);
244
3.62k
  }
245
246
  // Every element in |a_type_and_values| had a matching element in
247
  // |b_type_and_values|.
248
1.85k
  return true;
249
4.85k
}
250
251
enum NameMatchType {
252
  EXACT_MATCH,
253
  SUBTREE_MATCH,
254
};
255
256
// Verify that |a| matches |b|. If |match_type| is EXACT_MATCH, returns true if
257
// they are an exact match as defined by RFC 5280 7.1. If |match_type| is
258
// SUBTREE_MATCH, returns true if |a| is within the subtree defined by |b| as
259
// defined by RFC 5280 7.1.
260
//
261
// |a| and |b| are ASN.1 RDNSequence values (not including the Sequence tag),
262
// defined in RFC 5280 section 4.1.2.4:
263
//
264
// Name ::= CHOICE { -- only one possibility for now --
265
//   rdnSequence  RDNSequence }
266
//
267
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
268
//
269
// RelativeDistinguishedName ::=
270
//   SET SIZE (1..MAX) OF AttributeTypeAndValue
271
bool VerifyNameMatchInternal(der::Input a, der::Input b,
272
6.54k
                             NameMatchType match_type) {
273
  // Empty Names are allowed.  RFC 5280 section 4.1.2.4 requires "The issuer
274
  // field MUST contain a non-empty distinguished name (DN)", while section
275
  // 4.1.2.6 allows for the Subject to be empty in certain cases. The caller is
276
  // assumed to have verified those conditions.
277
278
  // RFC 5280 section 7.1:
279
  // Two distinguished names DN1 and DN2 match if they have the same number of
280
  // RDNs, for each RDN in DN1 there is a matching RDN in DN2, and the matching
281
  // RDNs appear in the same order in both DNs.
282
283
  // As an optimization, first just compare the number of RDNs:
284
6.54k
  der::Parser a_rdn_sequence_counter(a);
285
6.54k
  der::Parser b_rdn_sequence_counter(b);
286
14.2k
  while (a_rdn_sequence_counter.HasMore() && b_rdn_sequence_counter.HasMore()) {
287
10.0k
    if (!a_rdn_sequence_counter.SkipTag(CBS_ASN1_SET) ||
288
7.87k
        !b_rdn_sequence_counter.SkipTag(CBS_ASN1_SET)) {
289
2.30k
      return false;
290
2.30k
    }
291
10.0k
  }
292
  // If doing exact match and either of the sequences has more elements than the
293
  // other, not a match. If doing a subtree match, the first Name may have more
294
  // RDNs than the second.
295
4.23k
  if (b_rdn_sequence_counter.HasMore()) {
296
163
    return false;
297
163
  }
298
4.07k
  if (match_type == EXACT_MATCH && a_rdn_sequence_counter.HasMore()) {
299
64
    return false;
300
64
  }
301
302
  // Verify that RDNs in |a| and |b| match.
303
4.00k
  der::Parser a_rdn_sequence(a);
304
4.00k
  der::Parser b_rdn_sequence(b);
305
5.86k
  while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) {
306
5.14k
    der::Parser a_rdn, b_rdn;
307
5.14k
    if (!a_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &a_rdn) ||
308
5.14k
        !b_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &b_rdn)) {
309
0
      return false;
310
0
    }
311
5.14k
    if (!VerifyRdnMatch(&a_rdn, &b_rdn)) {
312
3.28k
      return false;
313
3.28k
    }
314
5.14k
  }
315
316
720
  return true;
317
4.00k
}
318
319
}  // namespace
320
321
bool NormalizeName(der::Input name_rdn_sequence,
322
5.29k
                   std::string *normalized_rdn_sequence, CertErrors *errors) {
323
5.29k
  BSSL_CHECK(errors);
324
325
  // RFC 5280 section 4.1.2.4
326
  // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
327
5.29k
  der::Parser rdn_sequence_parser(name_rdn_sequence);
328
329
5.29k
  bssl::ScopedCBB cbb;
330
5.29k
  if (!CBB_init(cbb.get(), 0)) {
331
0
    return false;
332
0
  }
333
334
343k
  while (rdn_sequence_parser.HasMore()) {
335
    // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
336
338k
    der::Parser rdn_parser;
337
338k
    if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
338
767
      return false;
339
767
    }
340
338k
    RelativeDistinguishedName type_and_values;
341
338k
    if (!ReadRdn(&rdn_parser, &type_and_values)) {
342
116
      return false;
343
116
    }
344
345
337k
    CBB rdn_cbb;
346
337k
    if (!CBB_add_asn1(cbb.get(), &rdn_cbb, CBS_ASN1_SET)) {
347
0
      return false;
348
0
    }
349
350
399k
    for (const auto &type_and_value : type_and_values) {
351
      // AttributeTypeAndValue ::= SEQUENCE {
352
      //   type     AttributeType,
353
      //   value    AttributeValue }
354
399k
      CBB attribute_type_and_value_cbb, type_cbb, value_cbb;
355
399k
      if (!CBB_add_asn1(&rdn_cbb, &attribute_type_and_value_cbb,
356
399k
                        CBS_ASN1_SEQUENCE)) {
357
0
        return false;
358
0
      }
359
360
      // AttributeType ::= OBJECT IDENTIFIER
361
399k
      if (!CBB_add_asn1(&attribute_type_and_value_cbb, &type_cbb,
362
399k
                        CBS_ASN1_OBJECT) ||
363
399k
          !CBB_add_bytes(&type_cbb, type_and_value.type.data(),
364
399k
                         type_and_value.type.size())) {
365
0
        return false;
366
0
      }
367
368
      // AttributeValue ::= ANY -- DEFINED BY AttributeType
369
399k
      if (IsNormalizableDirectoryString(type_and_value.value_tag)) {
370
380k
        std::string normalized_value;
371
380k
        if (!NormalizeValue(type_and_value, &normalized_value, errors)) {
372
284
          return false;
373
284
        }
374
380k
        if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
375
380k
                          CBS_ASN1_UTF8STRING) ||
376
380k
            !CBB_add_bytes(
377
380k
                &value_cbb,
378
380k
                reinterpret_cast<const uint8_t *>(normalized_value.data()),
379
380k
                normalized_value.size())) {
380
0
          return false;
381
0
        }
382
380k
      } else {
383
18.7k
        if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
384
18.7k
                          type_and_value.value_tag) ||
385
18.7k
            !CBB_add_bytes(&value_cbb, type_and_value.value.data(),
386
18.7k
                           type_and_value.value.size())) {
387
0
          return false;
388
0
        }
389
18.7k
      }
390
391
399k
      if (!CBB_flush(&rdn_cbb)) {
392
0
        return false;
393
0
      }
394
399k
    }
395
396
    // Ensure the encoded AttributeTypeAndValue values in the SET OF are sorted.
397
337k
    if (!CBB_flush_asn1_set_of(&rdn_cbb) || !CBB_flush(cbb.get())) {
398
0
      return false;
399
0
    }
400
337k
  }
401
402
4.12k
  normalized_rdn_sequence->assign(CBB_data(cbb.get()),
403
4.12k
                                  CBB_data(cbb.get()) + CBB_len(cbb.get()));
404
4.12k
  return true;
405
5.29k
}
406
407
3.33k
bool VerifyNameMatch(der::Input a_rdn_sequence, der::Input b_rdn_sequence) {
408
3.33k
  return VerifyNameMatchInternal(a_rdn_sequence, b_rdn_sequence, EXACT_MATCH);
409
3.33k
}
410
411
bool VerifyNameInSubtree(der::Input name_rdn_sequence,
412
3.21k
                         der::Input parent_rdn_sequence) {
413
3.21k
  return VerifyNameMatchInternal(name_rdn_sequence, parent_rdn_sequence,
414
3.21k
                                 SUBTREE_MATCH);
415
3.21k
}
416
417
bool FindEmailAddressesInName(
418
    der::Input name_rdn_sequence,
419
0
    std::vector<std::string> *contained_email_addresses) {
420
0
  contained_email_addresses->clear();
421
422
0
  der::Parser rdn_sequence_parser(name_rdn_sequence);
423
0
  while (rdn_sequence_parser.HasMore()) {
424
0
    der::Parser rdn_parser;
425
0
    if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
426
0
      return false;
427
0
    }
428
429
0
    RelativeDistinguishedName type_and_values;
430
0
    if (!ReadRdn(&rdn_parser, &type_and_values)) {
431
0
      return false;
432
0
    }
433
434
0
    for (const auto &type_and_value : type_and_values) {
435
0
      if (type_and_value.type == der::Input(kTypeEmailAddressOid)) {
436
0
        std::string email_address;
437
0
        if (!type_and_value.ValueAsString(&email_address)) {
438
0
          return false;
439
0
        }
440
0
        contained_email_addresses->push_back(std::move(email_address));
441
0
      }
442
0
    }
443
0
  }
444
445
0
  return true;
446
0
}
447
448
BSSL_NAMESPACE_END