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