• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "cert_error_params.h"
8 #include "cert_errors.h"
9 #include "parse_name.h"
10 #include "input.h"
11 #include "parser.h"
12 #include "tag.h"
13 #include <openssl/base.h>
14 #include <openssl/bytestring.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,
59     std::string* output) {
60   // Normalized version will always be equal or shorter than input.
61   // Normalize in place and then truncate the output if necessary.
62   std::string::const_iterator read_iter = output->begin();
63   std::string::iterator write_iter = output->begin();
64 
65   for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) {
66     // Ignore leading whitespace.
67   }
68 
69   for (; read_iter != output->end(); ++read_iter) {
70     const unsigned char c = *read_iter;
71     if (c == ' ') {
72       // If there are non-whitespace characters remaining in input, compress
73       // multiple whitespace chars to a single space, otherwise ignore trailing
74       // whitespace.
75       std::string::const_iterator next_iter = read_iter + 1;
76       if (next_iter != output->end() && *next_iter != ' ')
77         *(write_iter++) = ' ';
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           break;
92         case ENFORCE_ASCII:
93           if (c > 0x7F)
94             return false;
95           break;
96         case NO_ENFORCEMENT:
97           break;
98       }
99       *(write_iter++) = c;
100     }
101   }
102   if (write_iter != output->end())
103     output->erase(write_iter, output->end());
104   return true;
105 }
106 
107 // Converts the value of X509NameAttribute |attribute| to UTF-8, normalizes it,
108 // and stores in |output|. The type of |attribute| must be one of the types for
109 // which IsNormalizableDirectoryString is true.
110 //
111 // If the value of |attribute| can be normalized, returns true and sets
112 // |output| to the case folded, normalized value. If the value of |attribute|
113 // is invalid, returns false.
114 // NOTE: |output| will be modified regardless of the return.
NormalizeValue(X509NameAttribute attribute,std::string * output,CertErrors * errors)115 [[nodiscard]] bool NormalizeValue(X509NameAttribute attribute,
116                                   std::string* output,
117                                   CertErrors* errors) {
118   BSSL_CHECK(errors);
119 
120   if (!attribute.ValueAsStringUnsafe(output)) {
121     errors->AddError(kFailedConvertingAttributeValue,
122                      CreateCertErrorParams1SizeT("tag", attribute.value_tag));
123     return false;
124   }
125 
126   bool success = false;
127   switch (attribute.value_tag) {
128     case der::kPrintableString:
129       success = NormalizeDirectoryString(ENFORCE_PRINTABLE_STRING, output);
130       break;
131     case der::kBmpString:
132     case der::kUniversalString:
133     case der::kUtf8String:
134       success = NormalizeDirectoryString(NO_ENFORCEMENT, output);
135       break;
136     case der::kIA5String:
137       success = NormalizeDirectoryString(ENFORCE_ASCII, output);
138       break;
139     default:
140       // NOTREACHED
141       success = false;
142       break;
143   }
144 
145   if (!success) {
146     errors->AddError(kFailedNormalizingString,
147                      CreateCertErrorParams1SizeT("tag", attribute.value_tag));
148   }
149 
150   return success;
151 }
152 
153 // Returns true if |tag| is a string type that NormalizeValue can handle.
IsNormalizableDirectoryString(der::Tag tag)154 bool IsNormalizableDirectoryString(der::Tag tag) {
155   switch (tag) {
156     case der::kPrintableString:
157     case der::kUtf8String:
158     // RFC 5280 only requires handling IA5String for comparing domainComponent
159     // values, but handling it here avoids the need to special case anything.
160     case der::kIA5String:
161     case der::kUniversalString:
162     case der::kBmpString:
163       return true;
164     // TeletexString isn't normalized. Section 8 of RFC 5280 briefly
165     // describes the historical confusion between treating TeletexString
166     // as Latin1String vs T.61, and there are even incompatibilities within
167     // T.61 implementations. As this time is virtually unused, simply
168     // treat it with a binary comparison, as permitted by RFC 3280/5280.
169     default:
170       return false;
171   }
172 }
173 
174 // Returns true if the value of X509NameAttribute |a| matches |b|.
VerifyValueMatch(X509NameAttribute a,X509NameAttribute b)175 bool VerifyValueMatch(X509NameAttribute a, X509NameAttribute b) {
176   if (IsNormalizableDirectoryString(a.value_tag) &&
177       IsNormalizableDirectoryString(b.value_tag)) {
178     std::string a_normalized, b_normalized;
179     // TODO(eroman): Plumb this down.
180     CertErrors unused_errors;
181     if (!NormalizeValue(a, &a_normalized, &unused_errors) ||
182         !NormalizeValue(b, &b_normalized, &unused_errors))
183       return false;
184     return a_normalized == b_normalized;
185   }
186   // Attributes encoded with different types may be assumed to be unequal.
187   if (a.value_tag != b.value_tag)
188     return false;
189   // All other types use binary comparison.
190   return a.value == b.value;
191 }
192 
193 // Verifies that |a_parser| and |b_parser| are the same length and that every
194 // AttributeTypeAndValue in |a_parser| has a matching AttributeTypeAndValue in
195 // |b_parser|.
VerifyRdnMatch(der::Parser * a_parser,der::Parser * b_parser)196 bool VerifyRdnMatch(der::Parser* a_parser, der::Parser* b_parser) {
197   RelativeDistinguishedName a_type_and_values, b_type_and_values;
198   if (!ReadRdn(a_parser, &a_type_and_values) ||
199       !ReadRdn(b_parser, &b_type_and_values))
200     return false;
201 
202   // RFC 5280 section 7.1:
203   // Two relative distinguished names RDN1 and RDN2 match if they have the same
204   // number of naming attributes and for each naming attribute in RDN1 there is
205   // a matching naming attribute in RDN2.
206   if (a_type_and_values.size() != b_type_and_values.size())
207     return false;
208 
209   // The ordering of elements may differ due to denormalized values sorting
210   // differently in the DER encoding. Since the number of elements should be
211   // small, a naive linear search for each element should be fine. (Hostile
212   // certificates already have ways to provoke pathological behavior.)
213   for (const auto& a : a_type_and_values) {
214     auto b_iter = b_type_and_values.begin();
215     for (; b_iter != b_type_and_values.end(); ++b_iter) {
216       const auto& b = *b_iter;
217       if (a.type == b.type && VerifyValueMatch(a, b)) {
218         break;
219       }
220     }
221     if (b_iter == b_type_and_values.end())
222       return false;
223     // Remove the matched element from b_type_and_values to ensure duplicate
224     // elements in a_type_and_values can't match the same element in
225     // b_type_and_values multiple times.
226     b_type_and_values.erase(b_iter);
227   }
228 
229   // Every element in |a_type_and_values| had a matching element in
230   // |b_type_and_values|.
231   return true;
232 }
233 
234 enum NameMatchType {
235   EXACT_MATCH,
236   SUBTREE_MATCH,
237 };
238 
239 // Verify that |a| matches |b|. If |match_type| is EXACT_MATCH, returns true if
240 // they are an exact match as defined by RFC 5280 7.1. If |match_type| is
241 // SUBTREE_MATCH, returns true if |a| is within the subtree defined by |b| as
242 // defined by RFC 5280 7.1.
243 //
244 // |a| and |b| are ASN.1 RDNSequence values (not including the Sequence tag),
245 // defined in RFC 5280 section 4.1.2.4:
246 //
247 // Name ::= CHOICE { -- only one possibility for now --
248 //   rdnSequence  RDNSequence }
249 //
250 // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
251 //
252 // RelativeDistinguishedName ::=
253 //   SET SIZE (1..MAX) OF AttributeTypeAndValue
VerifyNameMatchInternal(const der::Input & a,const der::Input & b,NameMatchType match_type)254 bool VerifyNameMatchInternal(const der::Input& a,
255                              const der::Input& b,
256                              NameMatchType match_type) {
257   // Empty Names are allowed.  RFC 5280 section 4.1.2.4 requires "The issuer
258   // field MUST contain a non-empty distinguished name (DN)", while section
259   // 4.1.2.6 allows for the Subject to be empty in certain cases. The caller is
260   // assumed to have verified those conditions.
261 
262   // RFC 5280 section 7.1:
263   // Two distinguished names DN1 and DN2 match if they have the same number of
264   // RDNs, for each RDN in DN1 there is a matching RDN in DN2, and the matching
265   // RDNs appear in the same order in both DNs.
266 
267   // As an optimization, first just compare the number of RDNs:
268   der::Parser a_rdn_sequence_counter(a);
269   der::Parser b_rdn_sequence_counter(b);
270   while (a_rdn_sequence_counter.HasMore() && b_rdn_sequence_counter.HasMore()) {
271     if (!a_rdn_sequence_counter.SkipTag(der::kSet) ||
272         !b_rdn_sequence_counter.SkipTag(der::kSet)) {
273       return false;
274     }
275   }
276   // If doing exact match and either of the sequences has more elements than the
277   // other, not a match. If doing a subtree match, the first Name may have more
278   // RDNs than the second.
279   if (b_rdn_sequence_counter.HasMore())
280     return false;
281   if (match_type == EXACT_MATCH && a_rdn_sequence_counter.HasMore())
282     return false;
283 
284   // Verify that RDNs in |a| and |b| match.
285   der::Parser a_rdn_sequence(a);
286   der::Parser b_rdn_sequence(b);
287   while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) {
288     der::Parser a_rdn, b_rdn;
289     if (!a_rdn_sequence.ReadConstructed(der::kSet, &a_rdn) ||
290         !b_rdn_sequence.ReadConstructed(der::kSet, &b_rdn)) {
291       return false;
292     }
293     if (!VerifyRdnMatch(&a_rdn, &b_rdn))
294       return false;
295   }
296 
297   return true;
298 }
299 
300 }  // namespace
301 
NormalizeName(const der::Input & name_rdn_sequence,std::string * normalized_rdn_sequence,CertErrors * errors)302 bool NormalizeName(const der::Input& name_rdn_sequence,
303                    std::string* normalized_rdn_sequence,
304                    CertErrors* errors) {
305   BSSL_CHECK(errors);
306 
307   // RFC 5280 section 4.1.2.4
308   // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
309   der::Parser rdn_sequence_parser(name_rdn_sequence);
310 
311   bssl::ScopedCBB cbb;
312   if (!CBB_init(cbb.get(), 0))
313     return false;
314 
315   while (rdn_sequence_parser.HasMore()) {
316     // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
317     der::Parser rdn_parser;
318     if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser))
319       return false;
320     RelativeDistinguishedName type_and_values;
321     if (!ReadRdn(&rdn_parser, &type_and_values))
322       return false;
323 
324     CBB rdn_cbb;
325     if (!CBB_add_asn1(cbb.get(), &rdn_cbb, CBS_ASN1_SET))
326       return false;
327 
328     for (const auto& type_and_value : type_and_values) {
329       // AttributeTypeAndValue ::= SEQUENCE {
330       //   type     AttributeType,
331       //   value    AttributeValue }
332       CBB attribute_type_and_value_cbb, type_cbb, value_cbb;
333       if (!CBB_add_asn1(&rdn_cbb, &attribute_type_and_value_cbb,
334                         CBS_ASN1_SEQUENCE)) {
335         return false;
336       }
337 
338       // AttributeType ::= OBJECT IDENTIFIER
339       if (!CBB_add_asn1(&attribute_type_and_value_cbb, &type_cbb,
340                         CBS_ASN1_OBJECT) ||
341           !CBB_add_bytes(&type_cbb, type_and_value.type.UnsafeData(),
342                          type_and_value.type.Length())) {
343         return false;
344       }
345 
346       // AttributeValue ::= ANY -- DEFINED BY AttributeType
347       if (IsNormalizableDirectoryString(type_and_value.value_tag)) {
348         std::string normalized_value;
349         if (!NormalizeValue(type_and_value, &normalized_value, errors))
350           return false;
351         if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
352                           CBS_ASN1_UTF8STRING) ||
353             !CBB_add_bytes(
354                 &value_cbb,
355                 reinterpret_cast<const uint8_t*>(normalized_value.data()),
356                 normalized_value.size()))
357           return false;
358       } else {
359         if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
360                           type_and_value.value_tag) ||
361             !CBB_add_bytes(&value_cbb, type_and_value.value.UnsafeData(),
362                            type_and_value.value.Length()))
363           return false;
364       }
365 
366       if (!CBB_flush(&rdn_cbb))
367         return false;
368     }
369 
370     // Ensure the encoded AttributeTypeAndValue values in the SET OF are sorted.
371     if (!CBB_flush_asn1_set_of(&rdn_cbb) || !CBB_flush(cbb.get()))
372       return false;
373   }
374 
375   normalized_rdn_sequence->assign(CBB_data(cbb.get()),
376                                   CBB_data(cbb.get()) + CBB_len(cbb.get()));
377   return true;
378 }
379 
VerifyNameMatch(const der::Input & a_rdn_sequence,const der::Input & b_rdn_sequence)380 bool VerifyNameMatch(const der::Input& a_rdn_sequence,
381                      const der::Input& b_rdn_sequence) {
382   return VerifyNameMatchInternal(a_rdn_sequence, b_rdn_sequence, EXACT_MATCH);
383 }
384 
VerifyNameInSubtree(const der::Input & name_rdn_sequence,const der::Input & parent_rdn_sequence)385 bool VerifyNameInSubtree(const der::Input& name_rdn_sequence,
386                          const der::Input& parent_rdn_sequence) {
387   return VerifyNameMatchInternal(name_rdn_sequence, parent_rdn_sequence,
388                                  SUBTREE_MATCH);
389 }
390 
FindEmailAddressesInName(const der::Input & name_rdn_sequence,std::vector<std::string> * contained_email_addresses)391 bool FindEmailAddressesInName(
392     const der::Input& name_rdn_sequence,
393     std::vector<std::string>* contained_email_addresses) {
394   contained_email_addresses->clear();
395 
396   der::Parser rdn_sequence_parser(name_rdn_sequence);
397   while (rdn_sequence_parser.HasMore()) {
398     der::Parser rdn_parser;
399     if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser))
400       return false;
401 
402     RelativeDistinguishedName type_and_values;
403     if (!ReadRdn(&rdn_parser, &type_and_values))
404       return false;
405 
406     for (const auto& type_and_value : type_and_values) {
407       if (type_and_value.type == der::Input(kTypeEmailAddressOid)) {
408         std::string email_address;
409         if (!type_and_value.ValueAsString(&email_address)) {
410           return false;
411         }
412         contained_email_addresses->push_back(std::move(email_address));
413       }
414     }
415   }
416 
417   return true;
418 }
419 
420 }  // namespace net
421