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