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