Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(147)

Side by Side Diff: net/cert/internal/verify_name_match.cc

Issue 1125333005: RFC 2459 name comparison. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: review changes, implement unicode transcoding Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | net/cert/internal/verify_name_match_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « no previous file | net/cert/internal/verify_name_match_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698