1 /*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include "src/core/tsi/transport_security.h"
20
21 #include <string.h>
22
23 #include <string>
24
25 #include "absl/strings/str_format.h"
26
27 #include <grpc/support/alloc.h>
28 #include <grpc/support/log.h>
29 #include <grpc/support/string_util.h>
30
31 #include <openssl/crypto.h>
32
33 #include "src/core/lib/gpr/string.h"
34 #include "src/core/lib/gpr/useful.h"
35 #include "src/core/tsi/fake_transport_security.h"
36 #include "src/core/tsi/ssl_transport_security.h"
37 #include "test/core/util/test_config.h"
38
39 typedef struct {
40 /* 1 if success, 0 if failure. */
41 int expected;
42
43 /* Host name to match. */
44 const char* host_name;
45
46 /* Common name (CN). */
47 const char* common_name;
48
49 /* Comma separated list of certificate names to match against. Any occurrence
50 of '#' will be replaced with a null character before processing. */
51 const char* dns_names;
52
53 /* Comma separated list of IP SANs to match aggainst */
54 const char* ip_names;
55 } cert_name_test_entry;
56
57 /* Largely inspired from:
58 chromium/src/net/cert/x509_certificate_unittest.cc.
59 TODO(jboeuf) uncomment test cases as we fix tsi_ssl_peer_matches_name. */
60 const cert_name_test_entry cert_name_test_entries[] = {
61 {1, "foo.com", "foo.com", nullptr, nullptr},
62 {1, "f", "f", nullptr, nullptr},
63 {0, "h", "i", nullptr, nullptr},
64 {1, "bar.foo.com", "*.foo.com", nullptr, nullptr},
65 {1, "www.test.fr", "common.name",
66 "*.test.com,*.test.co.uk,*.test.de,*.test.fr", nullptr},
67 /*
68 {1, "wwW.tESt.fr", "common.name", ",*.*,*.test.de,*.test.FR,www"},
69 */
70 {0, "f.uk", ".uk", nullptr, nullptr},
71 {0, "w.bar.foo.com", "?.bar.foo.com", nullptr, nullptr},
72 {0, "www.foo.com", "(www|ftp).foo.com", nullptr, nullptr},
73 {0, "www.foo.com", "www.foo.com#", nullptr, nullptr}, /* # = null char. */
74 {0, "www.foo.com", "", "www.foo.com#*.foo.com,#,#", nullptr},
75 {0, "www.house.example", "ww.house.example", nullptr, nullptr},
76 {0, "test.org", "", "www.test.org,*.test.org,*.org", nullptr},
77 {0, "w.bar.foo.com", "w*.bar.foo.com", nullptr, nullptr},
78 {0, "www.bar.foo.com", "ww*ww.bar.foo.com", nullptr, nullptr},
79 {0, "wwww.bar.foo.com", "ww*ww.bar.foo.com", nullptr, nullptr},
80 {0, "wwww.bar.foo.com", "w*w.bar.foo.com", nullptr, nullptr},
81 {0, "wwww.bar.foo.com", "w*w.bar.foo.c0m", nullptr, nullptr},
82 {0, "WALLY.bar.foo.com", "wa*.bar.foo.com", nullptr, nullptr},
83 {0, "wally.bar.foo.com", "*Ly.bar.foo.com", nullptr, nullptr},
84 /*
85 {1, "ww%57.foo.com", "", "www.foo.com"},
86 {1, "www&.foo.com", "www%26.foo.com", NULL},
87 */
88
89 /* Common name must not be used if subject alternative name was provided. */
90 {0, "www.test.co.jp", "www.test.co.jp",
91 "*.test.de,*.jp,www.test.co.uk,www.*.co.jp", nullptr},
92 {0, "www.bar.foo.com", "www.bar.foo.com",
93 "*.foo.com,*.*.foo.com,*.*.bar.foo.com,*..bar.foo.com,", nullptr},
94
95 /* IDN tests */
96 {1, "xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br", nullptr, nullptr},
97 {1, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br", nullptr,
98 nullptr},
99 {0, "xn--poema-9qae5a.com.br", "",
100 "*.xn--poema-9qae5a.com.br,"
101 "xn--poema-*.com.br,"
102 "xn--*-9qae5a.com.br,"
103 "*--poema-9qae5a.com.br",
104 nullptr},
105
106 /* The following are adapted from the examples quoted from
107 http://tools.ietf.org/html/rfc6125#section-6.4.3
108 (e.g., *.example.com would match foo.example.com but
109 not bar.foo.example.com or example.com). */
110 {1, "foo.example.com", "*.example.com", nullptr, nullptr},
111 {0, "bar.foo.example.com", "*.example.com", nullptr, nullptr},
112 {0, "example.com", "*.example.com", nullptr, nullptr},
113
114 /* Partial wildcards are disallowed, though RFC 2818 rules allow them.
115 That is, forms such as baz*.example.net, *baz.example.net, and
116 b*z.example.net should NOT match domains. Instead, the wildcard must
117 always be the left-most label, and only a single label. */
118 {0, "baz1.example.net", "baz*.example.net", nullptr, nullptr},
119 {0, "foobaz.example.net", "*baz.example.net", nullptr, nullptr},
120 {0, "buzz.example.net", "b*z.example.net", nullptr, nullptr},
121 {0, "www.test.example.net", "www.*.example.net", nullptr, nullptr},
122
123 /* Wildcards should not be valid for public registry controlled domains,
124 and unknown/unrecognized domains, at least three domain components must
125 be present. */
126 {1, "www.test.example", "*.test.example", nullptr, nullptr},
127 {1, "test.example.co.uk", "*.example.co.uk", nullptr, nullptr},
128 {0, "test.example", "*.example", nullptr, nullptr},
129 /*
130 {0, "example.co.uk", "*.co.uk", NULL},
131 */
132 {0, "foo.com", "*.com", nullptr, nullptr},
133 {0, "foo.us", "*.us", nullptr, nullptr},
134 {0, "foo", "*", nullptr, nullptr},
135
136 /* IDN variants of wildcards and registry controlled domains. */
137 {1, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br", nullptr,
138 nullptr},
139 {1, "test.example.xn--mgbaam7a8h", "*.example.xn--mgbaam7a8h", nullptr,
140 nullptr},
141 /*
142 {0, "xn--poema-9qae5a.com.br", "*.com.br", NULL},
143 */
144 {0, "example.xn--mgbaam7a8h", "*.xn--mgbaam7a8h", nullptr, nullptr},
145
146 /* Wildcards should be permissible for 'private' registry controlled
147 domains. */
148 {1, "www.appspot.com", "*.appspot.com", nullptr, nullptr},
149 {1, "foo.s3.amazonaws.com", "*.s3.amazonaws.com", nullptr, nullptr},
150
151 /* Multiple wildcards are not valid. */
152 {0, "foo.example.com", "*.*.com", nullptr, nullptr},
153 {0, "foo.bar.example.com", "*.bar.*.com", nullptr, nullptr},
154
155 /* Absolute vs relative DNS name tests. Although not explicitly specified
156 in RFC 6125, absolute reference names (those ending in a .) should
157 match either absolute or relative presented names. */
158 {1, "foo.com", "foo.com.", nullptr, nullptr},
159 {1, "foo.com.", "foo.com", nullptr, nullptr},
160 {1, "foo.com.", "foo.com.", nullptr, nullptr},
161 {1, "f", "f.", nullptr, nullptr},
162 {1, "f.", "f", nullptr, nullptr},
163 {1, "f.", "f.", nullptr, nullptr},
164 {1, "www-3.bar.foo.com", "*.bar.foo.com.", nullptr, nullptr},
165 {1, "www-3.bar.foo.com.", "*.bar.foo.com", nullptr, nullptr},
166 {1, "www-3.bar.foo.com.", "*.bar.foo.com.", nullptr, nullptr},
167 {0, ".", ".", nullptr, nullptr},
168 {0, "example.com", "*.com.", nullptr, nullptr},
169 {0, "example.com.", "*.com", nullptr, nullptr},
170 {0, "example.com.", "*.com.", nullptr, nullptr},
171 {0, "foo.", "*.", nullptr, nullptr},
172 {0, "foo", "*.", nullptr, nullptr},
173 /*
174 {0, "foo.co.uk", "*.co.uk.", NULL},
175 {0, "foo.co.uk.", "*.co.uk.", NULL},
176 */
177
178 /* An empty CN is OK. */
179 {1, "test.foo.com", "", "test.foo.com", nullptr},
180
181 /* An IP should not be used for the CN. */
182 {0, "173.194.195.139", "173.194.195.139", nullptr, nullptr},
183 /* An IP can be used if the SAN IP is present */
184 {1, "173.194.195.139", "foo.example.com", nullptr, "173.194.195.139"},
185 {0, "173.194.195.139", "foo.example.com", nullptr, "8.8.8.8"},
186 {0, "173.194.195.139", "foo.example.com", nullptr, "8.8.8.8,8.8.4.4"},
187 {1, "173.194.195.139", "foo.example.com", nullptr,
188 "8.8.8.8,173.194.195.139"},
189 {0, "173.194.195.139", "foo.example.com", nullptr, "173.194.195.13"},
190 {0, "2001:db8:a0b:12f0::1", "foo.example.com", nullptr, "173.194.195.13"},
191 {1, "2001:db8:a0b:12f0::1", "foo.example.com", nullptr,
192 "2001:db8:a0b:12f0::1"},
193 {0, "2001:db8:a0b:12f0::1", "foo.example.com", nullptr,
194 "2001:db8:a0b:12f0::2"},
195 {1, "2001:db8:a0b:12f0::1", "foo.example.com", nullptr,
196 "2001:db8:a0b:12f0::2,2001:db8:a0b:12f0::1,8.8.8.8"},
197 };
198
199 typedef struct name_list {
200 const char* name;
201 struct name_list* next;
202 } name_list;
203
204 typedef struct {
205 size_t name_count;
206 char* buffer;
207 name_list* names;
208 } parsed_names;
209
name_list_add(const char * n)210 name_list* name_list_add(const char* n) {
211 name_list* result = static_cast<name_list*>(gpr_malloc(sizeof(name_list)));
212 result->name = n;
213 result->next = nullptr;
214 return result;
215 }
216
parse_names(const char * names_str)217 static parsed_names parse_names(const char* names_str) {
218 parsed_names result;
219 name_list* current_nl;
220 size_t i;
221 memset(&result, 0, sizeof(parsed_names));
222 if (names_str == nullptr) return result;
223 result.name_count = 1;
224 result.buffer = gpr_strdup(names_str);
225 result.names = name_list_add(result.buffer);
226 current_nl = result.names;
227 for (i = 0; i < strlen(names_str); i++) {
228 if (names_str[i] == ',') {
229 result.buffer[i] = '\0';
230 result.name_count++;
231 i++;
232 current_nl->next = name_list_add(result.buffer + i);
233 current_nl = current_nl->next;
234 }
235 }
236 return result;
237 }
238
destruct_parsed_names(parsed_names * pdn)239 static void destruct_parsed_names(parsed_names* pdn) {
240 name_list* nl = pdn->names;
241 if (pdn->buffer != nullptr) gpr_free(pdn->buffer);
242 while (nl != nullptr) {
243 name_list* to_be_free = nl;
244 nl = nl->next;
245 gpr_free(to_be_free);
246 }
247 }
248
processed_name(const char * name)249 static char* processed_name(const char* name) {
250 char* result = gpr_strdup(name);
251 size_t i;
252 for (i = 0; i < strlen(result); i++) {
253 if (result[i] == '#') {
254 result[i] = '\0';
255 }
256 }
257 return result;
258 }
259
peer_from_cert_name_test_entry(const cert_name_test_entry * entry)260 static tsi_peer peer_from_cert_name_test_entry(
261 const cert_name_test_entry* entry) {
262 size_t i;
263 tsi_peer peer;
264 name_list* nl;
265 parsed_names dns_entries = parse_names(entry->dns_names);
266 parsed_names ip_entries = parse_names(entry->ip_names);
267 nl = dns_entries.names;
268 GPR_ASSERT(tsi_construct_peer(
269 1 + dns_entries.name_count + ip_entries.name_count, &peer) ==
270 TSI_OK);
271 GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
272 TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY, entry->common_name,
273 &peer.properties[0]) == TSI_OK);
274 i = 1;
275 while (nl != nullptr) {
276 char* processed = processed_name(nl->name);
277 GPR_ASSERT(tsi_construct_string_peer_property(
278 TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, processed,
279 strlen(nl->name), &peer.properties[i++]) == TSI_OK);
280 nl = nl->next;
281 gpr_free(processed);
282 }
283
284 nl = ip_entries.names;
285 while (nl != nullptr) {
286 char* processed = processed_name(nl->name);
287 GPR_ASSERT(tsi_construct_string_peer_property(
288 TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, processed,
289 strlen(nl->name), &peer.properties[i++]) == TSI_OK);
290 nl = nl->next;
291 gpr_free(processed);
292 }
293 destruct_parsed_names(&dns_entries);
294 destruct_parsed_names(&ip_entries);
295 return peer;
296 }
297
cert_name_test_entry_to_string(const cert_name_test_entry * entry)298 std::string cert_name_test_entry_to_string(const cert_name_test_entry* entry) {
299 return absl::StrFormat(
300 "{ success = %s, host_name = %s, common_name = %s, dns_names = "
301 "%s, ip_names = %s}",
302 entry->expected ? "true" : "false", entry->host_name, entry->common_name,
303 entry->dns_names != nullptr ? entry->dns_names : "",
304 entry->ip_names != nullptr ? entry->ip_names : "");
305 }
306
test_peer_matches_name(void)307 static void test_peer_matches_name(void) {
308 size_t i = 0;
309 for (i = 0; i < GPR_ARRAY_SIZE(cert_name_test_entries); i++) {
310 const cert_name_test_entry* entry = &cert_name_test_entries[i];
311 tsi_peer peer = peer_from_cert_name_test_entry(entry);
312 int result = tsi_ssl_peer_matches_name(&peer, entry->host_name);
313 if (result != entry->expected) {
314 gpr_log(GPR_ERROR, "%s", cert_name_test_entry_to_string(entry).c_str());
315 GPR_ASSERT(0); /* Unexpected result. */
316 }
317 tsi_peer_destruct(&peer);
318 }
319 }
320
321 typedef struct {
322 tsi_result res;
323 const char* str;
324 } tsi_result_string_pair;
325
test_result_strings(void)326 static void test_result_strings(void) {
327 const tsi_result_string_pair results[] = {
328 {TSI_OK, "TSI_OK"},
329 {TSI_UNKNOWN_ERROR, "TSI_UNKNOWN_ERROR"},
330 {TSI_INVALID_ARGUMENT, "TSI_INVALID_ARGUMENT"},
331 {TSI_PERMISSION_DENIED, "TSI_PERMISSION_DENIED"},
332 {TSI_INCOMPLETE_DATA, "TSI_INCOMPLETE_DATA"},
333 {TSI_FAILED_PRECONDITION, "TSI_FAILED_PRECONDITION"},
334 {TSI_UNIMPLEMENTED, "TSI_UNIMPLEMENTED"},
335 {TSI_INTERNAL_ERROR, "TSI_INTERNAL_ERROR"},
336 {TSI_DATA_CORRUPTED, "TSI_DATA_CORRUPTED"},
337 {TSI_NOT_FOUND, "TSI_NOT_FOUND"},
338 {TSI_PROTOCOL_FAILURE, "TSI_PROTOCOL_FAILURE"},
339 {TSI_HANDSHAKE_IN_PROGRESS, "TSI_HANDSHAKE_IN_PROGRESS"},
340 {TSI_OUT_OF_RESOURCES, "TSI_OUT_OF_RESOURCES"}};
341 size_t i;
342 for (i = 0; i < GPR_ARRAY_SIZE(results); i++) {
343 GPR_ASSERT(strcmp(results[i].str, tsi_result_to_string(results[i].res)) ==
344 0);
345 }
346 GPR_ASSERT(strcmp("UNKNOWN", tsi_result_to_string((tsi_result)42)) == 0);
347 }
348
test_protector_invalid_args(void)349 static void test_protector_invalid_args(void) {
350 GPR_ASSERT(tsi_frame_protector_protect(nullptr, nullptr, nullptr, nullptr,
351 nullptr) == TSI_INVALID_ARGUMENT);
352 GPR_ASSERT(tsi_frame_protector_protect_flush(
353 nullptr, nullptr, nullptr, nullptr) == TSI_INVALID_ARGUMENT);
354 GPR_ASSERT(tsi_frame_protector_unprotect(nullptr, nullptr, nullptr, nullptr,
355 nullptr) == TSI_INVALID_ARGUMENT);
356 }
357
test_handshaker_invalid_args(void)358 static void test_handshaker_invalid_args(void) {
359 GPR_ASSERT(tsi_handshaker_get_result(nullptr) == TSI_INVALID_ARGUMENT);
360 GPR_ASSERT(tsi_handshaker_extract_peer(nullptr, nullptr) ==
361 TSI_INVALID_ARGUMENT);
362 GPR_ASSERT(tsi_handshaker_create_frame_protector(nullptr, nullptr, nullptr) ==
363 TSI_INVALID_ARGUMENT);
364 GPR_ASSERT(tsi_handshaker_process_bytes_from_peer(
365 nullptr, nullptr, nullptr) == TSI_INVALID_ARGUMENT);
366 GPR_ASSERT(tsi_handshaker_get_bytes_to_send_to_peer(
367 nullptr, nullptr, nullptr) == TSI_INVALID_ARGUMENT);
368 GPR_ASSERT(tsi_handshaker_next(nullptr, nullptr, 0, nullptr, nullptr, nullptr,
369 nullptr, nullptr) == TSI_INVALID_ARGUMENT);
370 }
371
test_handshaker_invalid_state(void)372 static void test_handshaker_invalid_state(void) {
373 tsi_handshaker* h = tsi_create_fake_handshaker(0);
374 tsi_peer peer;
375 tsi_frame_protector* p;
376 GPR_ASSERT(tsi_handshaker_extract_peer(h, &peer) == TSI_FAILED_PRECONDITION);
377 GPR_ASSERT(tsi_handshaker_create_frame_protector(h, nullptr, &p) ==
378 TSI_FAILED_PRECONDITION);
379 tsi_handshaker_destroy(h);
380 }
381
main(int argc,char ** argv)382 int main(int argc, char** argv) {
383 grpc::testing::TestEnvironment env(argc, argv);
384 test_peer_matches_name();
385 test_result_strings();
386 test_protector_invalid_args();
387 test_handshaker_invalid_args();
388 test_handshaker_invalid_state();
389 return 0;
390 }
391