OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/strings/string16.h" | |
6 #include "base/strings/string_util.h" | |
7 #include "base/strings/utf_string_conversion_utils.h" | |
8 #include "base/strings/utf_string_conversions.h" | |
9 #include "base/sys_byteorder.h" | |
10 #include "base/third_party/icu/icu_utf.h" | |
5 #include "net/cert/internal/verify_name_match.h" | 11 #include "net/cert/internal/verify_name_match.h" |
6 #include "net/der/input.h" | 12 #include "net/der/input.h" |
13 #include "net/der/parser.h" | |
14 #include "net/der/tag.h" | |
7 | 15 |
8 namespace net { | 16 namespace net { |
9 | 17 |
18 namespace { | |
19 | |
20 // Normalize a PrintableString value according to RFC 2459 section 4.1.2.4. | |
21 bool NormalizePrintableStringValue(const der::Input& in, std::string* output) { | |
22 // Normalized version will always be equal or shorter than input. | |
23 // Copy to output and then normalize and truncate the output if necessary. | |
24 output->assign(reinterpret_cast<const char*>(in.UnsafeData()), in.Length()); | |
25 | |
26 std::string::const_iterator read_iter = output->begin(); | |
27 std::string::iterator write_iter = output->begin(); | |
28 | |
29 for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) { | |
Ryan Sleevi
2015/06/18 01:30:12
COMMENT: I think it's important to explain why iss
| |
30 // Ignore leading whitespace. | |
31 } | |
32 | |
33 for (; read_iter != output->end(); ++read_iter) { | |
34 const char c = *read_iter; | |
35 if (c == ' ') { | |
36 // If there are non-whitespace characters remaining in input, compress | |
37 // multiple whitespace chars to a single space, otherwise ignore trailing | |
38 // whitespace. | |
39 std::string::const_iterator next_iter = read_iter + 1; | |
40 if (next_iter != output->end() && *next_iter != ' ') | |
41 *(write_iter++) = ' '; | |
42 } else if (c >= 'A' && c <= 'Z') { | |
43 // Fold case. | |
44 *(write_iter++) = c + ('a' - 'A'); | |
45 } else if ((c >= 'a' && c <= 'z') || (c >= '\'' && c <= ':') || c == '=' || | |
46 c == '?') { | |
47 // Accept remaining allowed characters (Note that * is not allowed by the | |
48 // spec, but openssl allows it, and so there are a number of certs that | |
49 // use it): | |
50 // a-z | |
51 // ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : | |
52 // = ? | |
Ryan Sleevi
2015/06/18 01:30:12
I feel that this description (and the comment I su
mattm
2015/06/19 22:04:24
Done.
| |
53 *(write_iter++) = c; | |
54 } else { | |
55 // Fail on any characters that are not valid for PrintableString. | |
56 return false; | |
57 } | |
58 } | |
59 if (write_iter != output->end()) | |
60 output->erase(write_iter, output->end()); | |
61 return true; | |
62 } | |
63 | |
64 // Normalize a UTF-8 encoded string in a manner compatible with RFC 2459. This | |
65 // could also be thought of as a small subset of RFC 5280 rules. Only ASCII | |
66 // case folding and whitespace folding is performed. | |
Ryan Sleevi
2015/06/18 01:30:12
Reword:
// Normalizes |output|, a UTF-8 encoded s
mattm
2015/06/19 22:04:24
done.
| |
67 bool NormalizeUtf8String(std::string* output) { | |
68 std::string::const_iterator read_iter = output->begin(); | |
69 std::string::iterator write_iter = output->begin(); | |
70 | |
71 for (; read_iter != output->end() && *read_iter == ' '; ++read_iter) { | |
72 // Ignore leading whitespace. | |
73 } | |
74 | |
75 for (; read_iter != output->end(); ++read_iter) { | |
76 const char c = *read_iter; | |
77 if (c == ' ') { | |
78 // If there are non-whitespace characters remaining in input, compress | |
79 // multiple whitespace chars to a single space, otherwise ignore trailing | |
80 // whitespace. | |
81 std::string::const_iterator next_iter = read_iter + 1; | |
82 if (next_iter != output->end() && *next_iter != ' ') | |
83 *(write_iter++) = ' '; | |
84 } else if (c >= 'A' && c <= 'Z') { | |
85 // Fold case. | |
86 *(write_iter++) = c + ('a' - 'A'); | |
87 } else { | |
88 *(write_iter++) = c; | |
89 } | |
90 } | |
91 if (write_iter != output->end()) | |
92 output->erase(write_iter, output->end()); | |
93 return true; | |
94 } | |
Ryan Sleevi
2015/06/18 01:30:12
Is there any reason not to combine NormalizeUtf8St
mattm
2015/06/19 22:04:24
Done.
| |
95 | |
96 // Convert a UTF8String value to string object and then normalize it. | |
97 bool NormalizeUtf8StringValue(const der::Input& in, std::string* output) { | |
98 output->assign(reinterpret_cast<const char*>(in.UnsafeData()), in.Length()); | |
99 return NormalizeUtf8String(output); | |
100 } | |
101 | |
102 // Convert BMPString value to UTF-8 and then normalize it. | |
Ryan Sleevi
2015/06/18 01:30:12
STYLE: Per http://google-styleguide.googlecode.com
mattm
2015/06/19 22:04:23
Done.
| |
103 bool NormalizeBmpStringValue(const der::Input& in, std::string* output) { | |
104 if (in.Length() % 2 != 0) | |
105 return false; | |
106 | |
107 base::string16 s16(reinterpret_cast<const base::char16*>(in.UnsafeData()), | |
Ryan Sleevi
2015/06/18 01:30:12
naming nit: My gut is that |s16| violates the nami
mattm
2015/06/19 22:04:23
yeah... maybe "in_16bit"? Trying to avoid somethin
| |
108 in.Length() / 2); | |
109 for (base::string16::iterator i = s16.begin(); i != s16.end(); ++i) { | |
110 // BMPString is UCS-2 in big-endian order. | |
111 *i = base::NetToHost16(*i); | |
112 | |
113 // BMPString only supports codepoints in the Basic Multilingual Plane, | |
114 // surrogates are not allowed. | |
Ryan Sleevi
2015/06/18 01:30:12
grammar nit:
either ',' -> ';' or ', surrogates'
mattm
2015/06/19 22:04:23
Done.
| |
115 if (CBU_IS_SURROGATE(*i)) | |
116 return false; | |
117 } | |
118 if (!base::UTF16ToUTF8(s16.data(), s16.size(), output)) | |
119 return false; | |
120 return NormalizeUtf8String(output); | |
121 } | |
122 | |
123 // Convert UniversalString value to UTF-8 and then normalize it. | |
124 bool NormalizeUniversalStringValue(const der::Input& in, std::string* output) { | |
125 if (in.Length() % 4 != 0) | |
126 return false; | |
127 | |
128 std::vector<uint32_t> s32( | |
129 reinterpret_cast<const uint32_t*>(in.UnsafeData()), | |
130 reinterpret_cast<const uint32_t*>(in.UnsafeData()) + in.Length() / 4); | |
131 for (std::vector<uint32_t>::const_iterator i = s32.begin(); i != s32.end(); | |
132 ++i) { | |
133 // UniversalString is UCS-4 in big-endian order. | |
134 uint32_t codepoint = base::NetToHost32(*i); | |
135 if (!CBU_IS_UNICODE_CHAR(codepoint)) | |
136 return false; | |
137 | |
138 base::WriteUnicodeCharacter(codepoint, output); | |
139 } | |
140 return NormalizeUtf8String(output); | |
141 } | |
142 | |
143 // Convert the string |value| to UTF-8, normalize it, and store in |output|. | |
144 bool NormalizeValue(const der::Tag tag, | |
145 const der::Input& value, | |
146 std::string* output) { | |
147 switch (tag) { | |
148 case der::kPrintableString: | |
149 return NormalizePrintableStringValue(value, output); | |
150 case der::kUtf8String: | |
151 return NormalizeUtf8StringValue(value, output); | |
152 case der::kUniversalString: | |
153 return NormalizeUniversalStringValue(value, output); | |
154 case der::kBmpString: | |
155 return NormalizeBmpStringValue(value, output); | |
156 default: | |
157 NOTREACHED(); | |
158 return false; | |
159 } | |
160 } | |
161 | |
162 // Return true if |tag| is a string type that NormalizeValue can handle. | |
163 bool IsNormalizable(der::Tag tag) { | |
Ryan Sleevi
2015/06/18 01:30:12
IsNormalizableDirectoryString ?
mattm
2015/06/19 22:04:23
Done.
| |
164 switch (tag) { | |
165 case der::kPrintableString: | |
166 case der::kUtf8String: | |
167 case der::kUniversalString: | |
168 case der::kBmpString: | |
nharper
2015/06/17 19:16:55
Do we care about TeletexStrings?
Ryan Sleevi
2015/06/18 01:30:12
IA5String as well, which comes up with domainCompo
Ryan Sleevi
2015/06/18 01:30:12
Ooops, botched commenting. No. But definitely shou
mattm
2015/06/19 22:04:23
Done.
mattm
2015/06/19 22:04:24
Done.
| |
169 return true; | |
170 default: | |
171 return false; | |
172 } | |
173 return false; | |
174 } | |
175 | |
176 bool VerifyAttributeValueMatch(der::Parser* a, der::Parser* b) { | |
177 der::Input a_value, b_value; | |
178 | |
179 // Read the attribute types, which must be OBJECT IDENTIFIERs. | |
180 if (!a->ReadTag(der::kOid, &a_value)) | |
181 return false; | |
182 if (!b->ReadTag(der::kOid, &b_value)) | |
183 return false; | |
184 // Attribute types must be equal. | |
185 if (!a_value.Equals(b_value)) | |
186 return false; | |
187 | |
188 // Read the attribute value. | |
189 der::Tag a_tag, b_tag; | |
190 if (!a->ReadTagAndValue(&a_tag, &a_value)) | |
191 return false; | |
192 if (!b->ReadTagAndValue(&b_tag, &b_value)) | |
193 return false; | |
194 | |
195 // There should be no more elements in the sequence after reading the | |
196 // attribute type and value. | |
197 if (a->HasMore() || b->HasMore()) | |
198 return false; | |
199 | |
200 if (IsNormalizable(a_tag) && IsNormalizable(b_tag)) { | |
201 std::string a_normalized, b_normalized; | |
202 if (!NormalizeValue(a_tag, a_value, &a_normalized) || | |
203 !NormalizeValue(b_tag, b_value, &b_normalized)) | |
204 return false; | |
205 return a_normalized == b_normalized; | |
206 } | |
207 // Attributes encoded with different types may be assumed to be unequal. | |
208 if (a_tag != b_tag) | |
209 return false; | |
210 // All other types use binary comparison. | |
211 return a_value.Equals(b_value); | |
212 } | |
213 | |
214 bool VerifyRDNMatch(der::Parser* a, der::Parser* b) { | |
215 // Must have at least one AttributeTypeAndValue. | |
216 if (!a->HasMore() || !b->HasMore()) | |
217 return false; | |
218 | |
219 while (a->HasMore() && b->HasMore()) { | |
davidben
2015/06/17 13:33:46
Since these are SETs, the order of the elements ma
Ryan Sleevi
2015/06/18 00:28:44
(is that guaranteed) In theory, yes. In practice,
davidben
2015/06/18 01:58:27
I'm quite aware of that. I think you missed the po
Ryan Sleevi
2015/06/18 03:30:01
No, because this code doesn't return the canonical
mattm
2015/06/19 22:04:23
Did the "ingest into two vectors and match" thing.
mattm
2015/06/19 22:04:24
Good catch.
| |
220 der::Parser a_attr_type_and_value; | |
221 der::Parser b_attr_type_and_value; | |
222 if (!a->ReadSequence(&a_attr_type_and_value) || | |
223 !b->ReadSequence(&b_attr_type_and_value)) | |
224 return false; | |
225 if (!VerifyAttributeValueMatch(&a_attr_type_and_value, | |
226 &b_attr_type_and_value)) | |
227 return false; | |
228 } | |
229 | |
230 // If one of the RDNs has more elements than the other, not a match. | |
231 if (a->HasMore() || b->HasMore()) | |
232 return false; | |
233 | |
234 return true; | |
235 } | |
236 | |
237 } // namespace | |
238 | |
239 // TODO(mattm): is returning false on parsing errors ok, or should it try to | |
240 // fall back to binary comparison on unexpected input? | |
10 bool VerifyNameMatch(const der::Input& a, const der::Input& b) { | 241 bool VerifyNameMatch(const der::Input& a, const der::Input& b) { |
11 // TODO(mattm): use normalization as specified in RFC 5280 section 7. | 242 der::Parser a_parser(a); |
12 return a.Equals(b); | 243 der::Parser b_parser(b); |
244 der::Parser a_rdn_sequence; | |
245 der::Parser b_rdn_sequence; | |
246 | |
247 if (!a_parser.ReadSequence(&a_rdn_sequence) || | |
248 !b_parser.ReadSequence(&b_rdn_sequence)) { | |
249 return false; | |
250 } | |
251 | |
252 // No data should remain in the inputs after the RDN sequence. | |
253 if (a_parser.HasMore() || b_parser.HasMore()) | |
254 return false; | |
255 | |
256 // Must have at least one RDN. | |
257 if (!a_rdn_sequence.HasMore() || !b_rdn_sequence.HasMore()) | |
258 return false; | |
259 | |
260 while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) { | |
261 der::Parser a_rdn, b_rdn; | |
262 if (!a_rdn_sequence.ReadConstructed(der::kSet, &a_rdn) || | |
263 !b_rdn_sequence.ReadConstructed(der::kSet, &b_rdn)) { | |
264 return false; | |
265 } | |
266 if (!VerifyRDNMatch(&a_rdn, &b_rdn)) | |
267 return false; | |
268 } | |
269 | |
270 // If one of the sequences has more elements than the other, not a match. | |
271 if (a_rdn_sequence.HasMore() || b_rdn_sequence.HasMore()) | |
272 return false; | |
273 | |
274 return true; | |
13 } | 275 } |
14 | 276 |
15 } // namespace net | 277 } // namespace net |
OLD | NEW |