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