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.
NormalizeDirectoryString(CharsetEnforcement charset_enforcement,std::string * output)67 [[nodiscard]] bool NormalizeDirectoryString(
68     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   std::string::const_iterator read_iter = output->begin();
72   std::string::iterator write_iter = output->begin();
73 
74   for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) {
75     // Ignore leading whitespace.
76   }
77 
78   for (; read_iter != output->end(); ++read_iter) {
79     const unsigned char c = *read_iter;
80     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       std::string::const_iterator next_iter = read_iter + 1;
85       if (next_iter != output->end() && *next_iter != ' ') {
86         *(write_iter++) = ' ';
87       }
88     } else if (c >= 'A' && c <= 'Z') {
89       // Fold case.
90       *(write_iter++) = c + ('a' - 'A');
91     } else {
92       // Note that these checks depend on the characters allowed by earlier
93       // conditions also being valid for the enforced charset.
94       switch (charset_enforcement) {
95         case ENFORCE_PRINTABLE_STRING:
96           // See NormalizePrintableStringValue comment for the acceptable list
97           // of characters.
98           if (!((c >= 'a' && c <= 'z') || (c >= '\'' && c <= ':') || c == '=' ||
99                 c == '?')) {
100             return false;
101           }
102           break;
103         case ENFORCE_ASCII:
104           if (c > 0x7F) {
105             return false;
106           }
107           break;
108         case NO_ENFORCEMENT:
109           break;
110       }
111       *(write_iter++) = c;
112     }
113   }
114   if (write_iter != output->end()) {
115     output->erase(write_iter, output->end());
116   }
117   return true;
118 }
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.
NormalizeValue(X509NameAttribute attribute,std::string * output,CertErrors * errors)128 [[nodiscard]] bool NormalizeValue(X509NameAttribute attribute,
129                                   std::string *output, CertErrors *errors) {
130   BSSL_CHECK(errors);
131 
132   if (!attribute.ValueAsStringUnsafe(output)) {
133     errors->AddError(kFailedConvertingAttributeValue,
134                      CreateCertErrorParams1SizeT("tag", attribute.value_tag));
135     return false;
136   }
137 
138   bool success = false;
139   switch (attribute.value_tag) {
140     case CBS_ASN1_PRINTABLESTRING:
141       success = NormalizeDirectoryString(ENFORCE_PRINTABLE_STRING, output);
142       break;
143     case CBS_ASN1_BMPSTRING:
144     case CBS_ASN1_UNIVERSALSTRING:
145     case CBS_ASN1_UTF8STRING:
146       success = NormalizeDirectoryString(NO_ENFORCEMENT, output);
147       break;
148     case CBS_ASN1_IA5STRING:
149       success = NormalizeDirectoryString(ENFORCE_ASCII, output);
150       break;
151     default:
152       // NOTREACHED
153       success = false;
154       break;
155   }
156 
157   if (!success) {
158     errors->AddError(kFailedNormalizingString,
159                      CreateCertErrorParams1SizeT("tag", attribute.value_tag));
160   }
161 
162   return success;
163 }
164 
165 // Returns true if |tag| is a string type that NormalizeValue can handle.
IsNormalizableDirectoryString(CBS_ASN1_TAG tag)166 bool IsNormalizableDirectoryString(CBS_ASN1_TAG tag) {
167   switch (tag) {
168     case CBS_ASN1_PRINTABLESTRING:
169     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     case CBS_ASN1_IA5STRING:
173     case CBS_ASN1_UNIVERSALSTRING:
174     case CBS_ASN1_BMPSTRING:
175       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     default:
182       return false;
183   }
184 }
185 
186 // Returns true if the value of X509NameAttribute |a| matches |b|.
VerifyValueMatch(X509NameAttribute a,X509NameAttribute b)187 bool VerifyValueMatch(X509NameAttribute a, X509NameAttribute b) {
188   if (IsNormalizableDirectoryString(a.value_tag) &&
189       IsNormalizableDirectoryString(b.value_tag)) {
190     std::string a_normalized, b_normalized;
191     // TODO(eroman): Plumb this down.
192     CertErrors unused_errors;
193     if (!NormalizeValue(a, &a_normalized, &unused_errors) ||
194         !NormalizeValue(b, &b_normalized, &unused_errors)) {
195       return false;
196     }
197     return a_normalized == b_normalized;
198   }
199   // Attributes encoded with different types may be assumed to be unequal.
200   if (a.value_tag != b.value_tag) {
201     return false;
202   }
203   // All other types use binary comparison.
204   return a.value == b.value;
205 }
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|.
VerifyRdnMatch(der::Parser * a_parser,der::Parser * b_parser)210 bool VerifyRdnMatch(der::Parser *a_parser, der::Parser *b_parser) {
211   RelativeDistinguishedName a_type_and_values, b_type_and_values;
212   if (!ReadRdn(a_parser, &a_type_and_values) ||
213       !ReadRdn(b_parser, &b_type_and_values)) {
214     return false;
215   }
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   if (a_type_and_values.size() != b_type_and_values.size()) {
222     return false;
223   }
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   for (const auto &a : a_type_and_values) {
230     auto b_iter = b_type_and_values.begin();
231     for (; b_iter != b_type_and_values.end(); ++b_iter) {
232       const auto &b = *b_iter;
233       if (a.type == b.type && VerifyValueMatch(a, b)) {
234         break;
235       }
236     }
237     if (b_iter == b_type_and_values.end()) {
238       return false;
239     }
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     b_type_and_values.erase(b_iter);
244   }
245 
246   // Every element in |a_type_and_values| had a matching element in
247   // |b_type_and_values|.
248   return true;
249 }
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
VerifyNameMatchInternal(der::Input a,der::Input b,NameMatchType match_type)271 bool VerifyNameMatchInternal(der::Input a, der::Input b,
272                              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   der::Parser a_rdn_sequence_counter(a);
285   der::Parser b_rdn_sequence_counter(b);
286   while (a_rdn_sequence_counter.HasMore() && b_rdn_sequence_counter.HasMore()) {
287     if (!a_rdn_sequence_counter.SkipTag(CBS_ASN1_SET) ||
288         !b_rdn_sequence_counter.SkipTag(CBS_ASN1_SET)) {
289       return false;
290     }
291   }
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   if (b_rdn_sequence_counter.HasMore()) {
296     return false;
297   }
298   if (match_type == EXACT_MATCH && a_rdn_sequence_counter.HasMore()) {
299     return false;
300   }
301 
302   // Verify that RDNs in |a| and |b| match.
303   der::Parser a_rdn_sequence(a);
304   der::Parser b_rdn_sequence(b);
305   while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) {
306     der::Parser a_rdn, b_rdn;
307     if (!a_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &a_rdn) ||
308         !b_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &b_rdn)) {
309       return false;
310     }
311     if (!VerifyRdnMatch(&a_rdn, &b_rdn)) {
312       return false;
313     }
314   }
315 
316   return true;
317 }
318 
319 }  // namespace
320 
NormalizeName(der::Input name_rdn_sequence,std::string * normalized_rdn_sequence,CertErrors * errors)321 bool NormalizeName(der::Input name_rdn_sequence,
322                    std::string *normalized_rdn_sequence, CertErrors *errors) {
323   BSSL_CHECK(errors);
324 
325   // RFC 5280 section 4.1.2.4
326   // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
327   der::Parser rdn_sequence_parser(name_rdn_sequence);
328 
329   bssl::ScopedCBB cbb;
330   if (!CBB_init(cbb.get(), 0)) {
331     return false;
332   }
333 
334   while (rdn_sequence_parser.HasMore()) {
335     // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
336     der::Parser rdn_parser;
337     if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
338       return false;
339     }
340     RelativeDistinguishedName type_and_values;
341     if (!ReadRdn(&rdn_parser, &type_and_values)) {
342       return false;
343     }
344 
345     CBB rdn_cbb;
346     if (!CBB_add_asn1(cbb.get(), &rdn_cbb, CBS_ASN1_SET)) {
347       return false;
348     }
349 
350     for (const auto &type_and_value : type_and_values) {
351       // AttributeTypeAndValue ::= SEQUENCE {
352       //   type     AttributeType,
353       //   value    AttributeValue }
354       CBB attribute_type_and_value_cbb, type_cbb, value_cbb;
355       if (!CBB_add_asn1(&rdn_cbb, &attribute_type_and_value_cbb,
356                         CBS_ASN1_SEQUENCE)) {
357         return false;
358       }
359 
360       // AttributeType ::= OBJECT IDENTIFIER
361       if (!CBB_add_asn1(&attribute_type_and_value_cbb, &type_cbb,
362                         CBS_ASN1_OBJECT) ||
363           !CBB_add_bytes(&type_cbb, type_and_value.type.data(),
364                          type_and_value.type.size())) {
365         return false;
366       }
367 
368       // AttributeValue ::= ANY -- DEFINED BY AttributeType
369       if (IsNormalizableDirectoryString(type_and_value.value_tag)) {
370         std::string normalized_value;
371         if (!NormalizeValue(type_and_value, &normalized_value, errors)) {
372           return false;
373         }
374         if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
375                           CBS_ASN1_UTF8STRING) ||
376             !CBB_add_bytes(
377                 &value_cbb,
378                 reinterpret_cast<const uint8_t *>(normalized_value.data()),
379                 normalized_value.size())) {
380           return false;
381         }
382       } else {
383         if (!CBB_add_asn1(&attribute_type_and_value_cbb, &value_cbb,
384                           type_and_value.value_tag) ||
385             !CBB_add_bytes(&value_cbb, type_and_value.value.data(),
386                            type_and_value.value.size())) {
387           return false;
388         }
389       }
390 
391       if (!CBB_flush(&rdn_cbb)) {
392         return false;
393       }
394     }
395 
396     // Ensure the encoded AttributeTypeAndValue values in the SET OF are sorted.
397     if (!CBB_flush_asn1_set_of(&rdn_cbb) || !CBB_flush(cbb.get())) {
398       return false;
399     }
400   }
401 
402   normalized_rdn_sequence->assign(CBB_data(cbb.get()),
403                                   CBB_data(cbb.get()) + CBB_len(cbb.get()));
404   return true;
405 }
406 
VerifyNameMatch(der::Input a_rdn_sequence,der::Input b_rdn_sequence)407 bool VerifyNameMatch(der::Input a_rdn_sequence, der::Input b_rdn_sequence) {
408   return VerifyNameMatchInternal(a_rdn_sequence, b_rdn_sequence, EXACT_MATCH);
409 }
410 
VerifyNameInSubtree(der::Input name_rdn_sequence,der::Input parent_rdn_sequence)411 bool VerifyNameInSubtree(der::Input name_rdn_sequence,
412                          der::Input parent_rdn_sequence) {
413   return VerifyNameMatchInternal(name_rdn_sequence, parent_rdn_sequence,
414                                  SUBTREE_MATCH);
415 }
416 
FindEmailAddressesInName(der::Input name_rdn_sequence,std::vector<std::string> * contained_email_addresses)417 bool FindEmailAddressesInName(
418     der::Input name_rdn_sequence,
419     std::vector<std::string> *contained_email_addresses) {
420   contained_email_addresses->clear();
421 
422   der::Parser rdn_sequence_parser(name_rdn_sequence);
423   while (rdn_sequence_parser.HasMore()) {
424     der::Parser rdn_parser;
425     if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
426       return false;
427     }
428 
429     RelativeDistinguishedName type_and_values;
430     if (!ReadRdn(&rdn_parser, &type_and_values)) {
431       return false;
432     }
433 
434     for (const auto &type_and_value : type_and_values) {
435       if (type_and_value.type == der::Input(kTypeEmailAddressOid)) {
436         std::string email_address;
437         if (!type_and_value.ValueAsString(&email_address)) {
438           return false;
439         }
440         contained_email_addresses->push_back(std::move(email_address));
441       }
442     }
443   }
444 
445   return true;
446 }
447 
448 BSSL_NAMESPACE_END
449