]>
Commit | Line | Data |
---|---|---|
ac3d33df JK |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
8 | #include "streams/mbedtls.h" | |
9 | ||
10 | #ifdef GIT_MBEDTLS | |
11 | ||
12 | #include <ctype.h> | |
13 | ||
c25aa7cd | 14 | #include "runtime.h" |
ac3d33df JK |
15 | #include "stream.h" |
16 | #include "streams/socket.h" | |
17 | #include "netops.h" | |
18 | #include "git2/transport.h" | |
19 | #include "util.h" | |
20 | ||
21 | #ifndef GIT_DEFAULT_CERT_LOCATION | |
22 | #define GIT_DEFAULT_CERT_LOCATION NULL | |
23 | #endif | |
24 | ||
25 | /* Work around C90-conformance issues */ | |
26 | #if defined(_MSC_VER) | |
27 | # define inline __inline | |
28 | #elif defined(__GNUC__) | |
29 | # define inline __inline__ | |
30 | #else | |
31 | # define inline | |
32 | #endif | |
33 | ||
34 | #include <mbedtls/config.h> | |
35 | #include <mbedtls/ssl.h> | |
36 | #include <mbedtls/error.h> | |
37 | #include <mbedtls/entropy.h> | |
38 | #include <mbedtls/ctr_drbg.h> | |
39 | ||
40 | #undef inline | |
41 | ||
42 | #define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" | |
43 | #define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 | |
44 | ||
45 | static mbedtls_ssl_config *git__ssl_conf; | |
46 | static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; | |
47 | static mbedtls_entropy_context *mbedtls_entropy; | |
48 | ||
49 | /** | |
50 | * This function aims to clean-up the SSL context which | |
51 | * we allocated. | |
52 | */ | |
53 | static void shutdown_ssl(void) | |
54 | { | |
55 | if (git__ssl_conf) { | |
56 | mbedtls_x509_crt_free(git__ssl_conf->ca_chain); | |
57 | git__free(git__ssl_conf->ca_chain); | |
58 | mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); | |
59 | git__free(git__ssl_conf->p_rng); | |
60 | mbedtls_ssl_config_free(git__ssl_conf); | |
61 | git__free(git__ssl_conf); | |
62 | git__ssl_conf = NULL; | |
63 | } | |
64 | if (mbedtls_entropy) { | |
65 | mbedtls_entropy_free(mbedtls_entropy); | |
66 | git__free(mbedtls_entropy); | |
67 | mbedtls_entropy = NULL; | |
68 | } | |
69 | } | |
70 | ||
ac3d33df JK |
71 | int git_mbedtls_stream_global_init(void) |
72 | { | |
73 | int loaded = 0; | |
74 | char *crtpath = GIT_DEFAULT_CERT_LOCATION; | |
75 | struct stat statbuf; | |
76 | mbedtls_ctr_drbg_context *ctr_drbg = NULL; | |
77 | ||
78 | size_t ciphers_known = 0; | |
79 | char *cipher_name = NULL; | |
80 | char *cipher_string = NULL; | |
81 | char *cipher_string_tmp = NULL; | |
82 | ||
83 | git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); | |
84 | GIT_ERROR_CHECK_ALLOC(git__ssl_conf); | |
85 | ||
86 | mbedtls_ssl_config_init(git__ssl_conf); | |
87 | if (mbedtls_ssl_config_defaults(git__ssl_conf, | |
88 | MBEDTLS_SSL_IS_CLIENT, | |
89 | MBEDTLS_SSL_TRANSPORT_STREAM, | |
90 | MBEDTLS_SSL_PRESET_DEFAULT) != 0) { | |
91 | git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); | |
92 | goto cleanup; | |
93 | } | |
94 | ||
95 | /* configure TLSv1 */ | |
96 | mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); | |
97 | ||
98 | /* verify_server_cert is responsible for making the check. | |
99 | * OPTIONAL because REQUIRED drops the certificate as soon as the check | |
100 | * is made, so we can never see the certificate and override it. */ | |
101 | mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); | |
102 | ||
103 | /* set the list of allowed ciphersuites */ | |
104 | ciphers_known = 0; | |
105 | cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS); | |
106 | GIT_ERROR_CHECK_ALLOC(cipher_string); | |
107 | ||
108 | while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) { | |
109 | int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name); | |
110 | if (cipherid == 0) continue; | |
111 | ||
112 | if (ciphers_known >= ARRAY_SIZE(ciphers_list)) { | |
113 | git_error_set(GIT_ERROR_SSL, "out of cipher list space"); | |
114 | goto cleanup; | |
115 | } | |
116 | ||
117 | ciphers_list[ciphers_known++] = cipherid; | |
118 | } | |
119 | git__free(cipher_string); | |
120 | ||
121 | if (!ciphers_known) { | |
122 | git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); | |
123 | goto cleanup; | |
124 | } | |
125 | mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); | |
126 | ||
127 | /* Seeding the random number generator */ | |
128 | mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); | |
129 | GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); | |
130 | ||
131 | mbedtls_entropy_init(mbedtls_entropy); | |
132 | ||
133 | ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); | |
134 | GIT_ERROR_CHECK_ALLOC(ctr_drbg); | |
135 | ||
136 | mbedtls_ctr_drbg_init(ctr_drbg); | |
137 | ||
138 | if (mbedtls_ctr_drbg_seed(ctr_drbg, | |
139 | mbedtls_entropy_func, | |
140 | mbedtls_entropy, NULL, 0) != 0) { | |
141 | git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); | |
142 | goto cleanup; | |
143 | } | |
144 | ||
145 | mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); | |
146 | ||
147 | /* load default certificates */ | |
148 | if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) | |
c25aa7cd | 149 | loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); |
ac3d33df | 150 | if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) |
c25aa7cd | 151 | loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); |
ac3d33df | 152 | |
c25aa7cd | 153 | return git_runtime_shutdown_register(shutdown_ssl); |
ac3d33df JK |
154 | |
155 | cleanup: | |
156 | mbedtls_ctr_drbg_free(ctr_drbg); | |
157 | git__free(ctr_drbg); | |
158 | mbedtls_ssl_config_free(git__ssl_conf); | |
159 | git__free(git__ssl_conf); | |
160 | git__ssl_conf = NULL; | |
161 | ||
162 | return -1; | |
163 | } | |
164 | ||
165 | static int bio_read(void *b, unsigned char *buf, size_t len) | |
166 | { | |
167 | git_stream *io = (git_stream *) b; | |
168 | return (int) git_stream_read(io, buf, min(len, INT_MAX)); | |
169 | } | |
170 | ||
171 | static int bio_write(void *b, const unsigned char *buf, size_t len) | |
172 | { | |
173 | git_stream *io = (git_stream *) b; | |
174 | return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0); | |
175 | } | |
176 | ||
177 | static int ssl_set_error(mbedtls_ssl_context *ssl, int error) | |
178 | { | |
179 | char errbuf[512]; | |
180 | int ret = -1; | |
181 | ||
c25aa7cd PP |
182 | GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ); |
183 | GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE); | |
ac3d33df JK |
184 | |
185 | if (error != 0) | |
186 | mbedtls_strerror( error, errbuf, 512 ); | |
187 | ||
188 | switch(error) { | |
189 | case 0: | |
190 | git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); | |
191 | break; | |
192 | ||
193 | case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: | |
194 | git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); | |
195 | ret = GIT_ECERTIFICATE; | |
196 | break; | |
197 | ||
198 | default: | |
199 | git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); | |
200 | } | |
201 | ||
202 | return ret; | |
203 | } | |
204 | ||
205 | static int ssl_teardown(mbedtls_ssl_context *ssl) | |
206 | { | |
207 | int ret = 0; | |
208 | ||
209 | ret = mbedtls_ssl_close_notify(ssl); | |
210 | if (ret < 0) | |
211 | ret = ssl_set_error(ssl, ret); | |
212 | ||
213 | mbedtls_ssl_free(ssl); | |
214 | return ret; | |
215 | } | |
216 | ||
217 | static int verify_server_cert(mbedtls_ssl_context *ssl) | |
218 | { | |
219 | int ret = -1; | |
220 | ||
221 | if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) { | |
222 | char vrfy_buf[512]; | |
223 | int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret); | |
224 | if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */ | |
225 | git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf); | |
226 | return GIT_ECERTIFICATE; | |
227 | } | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | typedef struct { | |
233 | git_stream parent; | |
234 | git_stream *io; | |
235 | int owned; | |
236 | bool connected; | |
237 | char *host; | |
238 | mbedtls_ssl_context *ssl; | |
239 | git_cert_x509 cert_info; | |
240 | } mbedtls_stream; | |
241 | ||
242 | ||
243 | static int mbedtls_connect(git_stream *stream) | |
244 | { | |
245 | int ret; | |
246 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
247 | ||
248 | if (st->owned && (ret = git_stream_connect(st->io)) < 0) | |
249 | return ret; | |
250 | ||
251 | st->connected = true; | |
252 | ||
253 | mbedtls_ssl_set_hostname(st->ssl, st->host); | |
254 | ||
255 | mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); | |
256 | ||
257 | if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) | |
258 | return ssl_set_error(st->ssl, ret); | |
259 | ||
260 | return verify_server_cert(st->ssl); | |
261 | } | |
262 | ||
263 | static int mbedtls_certificate(git_cert **out, git_stream *stream) | |
264 | { | |
265 | unsigned char *encoded_cert; | |
266 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
267 | ||
268 | const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl); | |
269 | if (!cert) { | |
270 | git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); | |
271 | return -1; | |
272 | } | |
273 | ||
274 | /* Retrieve the length of the certificate first */ | |
275 | if (cert->raw.len == 0) { | |
276 | git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); | |
277 | return -1; | |
278 | } | |
279 | ||
280 | encoded_cert = git__malloc(cert->raw.len); | |
281 | GIT_ERROR_CHECK_ALLOC(encoded_cert); | |
282 | memcpy(encoded_cert, cert->raw.p, cert->raw.len); | |
283 | ||
284 | st->cert_info.parent.cert_type = GIT_CERT_X509; | |
285 | st->cert_info.data = encoded_cert; | |
286 | st->cert_info.len = cert->raw.len; | |
287 | ||
288 | *out = &st->cert_info.parent; | |
289 | ||
290 | return 0; | |
291 | } | |
292 | ||
293 | static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options) | |
294 | { | |
295 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
296 | ||
297 | return git_stream_set_proxy(st->io, proxy_options); | |
298 | } | |
299 | ||
300 | static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags) | |
301 | { | |
302 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
303 | int written; | |
304 | ||
305 | GIT_UNUSED(flags); | |
306 | ||
307 | /* | |
308 | * `mbedtls_ssl_write` can only represent INT_MAX bytes | |
309 | * written via its return value. We thus need to clamp | |
310 | * the maximum number of bytes written. | |
311 | */ | |
312 | len = min(len, INT_MAX); | |
313 | ||
314 | if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0) | |
315 | return ssl_set_error(st->ssl, written); | |
316 | ||
317 | return written; | |
318 | } | |
319 | ||
320 | static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len) | |
321 | { | |
322 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
323 | int ret; | |
324 | ||
325 | if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0) | |
326 | ssl_set_error(st->ssl, ret); | |
327 | ||
328 | return ret; | |
329 | } | |
330 | ||
331 | static int mbedtls_stream_close(git_stream *stream) | |
332 | { | |
333 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
334 | int ret = 0; | |
335 | ||
336 | if (st->connected && (ret = ssl_teardown(st->ssl)) != 0) | |
337 | return -1; | |
338 | ||
339 | st->connected = false; | |
340 | ||
341 | return st->owned ? git_stream_close(st->io) : 0; | |
342 | } | |
343 | ||
344 | static void mbedtls_stream_free(git_stream *stream) | |
345 | { | |
346 | mbedtls_stream *st = (mbedtls_stream *) stream; | |
347 | ||
348 | if (st->owned) | |
349 | git_stream_free(st->io); | |
350 | ||
351 | git__free(st->host); | |
352 | git__free(st->cert_info.data); | |
353 | mbedtls_ssl_free(st->ssl); | |
354 | git__free(st->ssl); | |
355 | git__free(st); | |
356 | } | |
357 | ||
358 | static int mbedtls_stream_wrap( | |
359 | git_stream **out, | |
360 | git_stream *in, | |
361 | const char *host, | |
362 | int owned) | |
363 | { | |
364 | mbedtls_stream *st; | |
365 | int error; | |
366 | ||
367 | st = git__calloc(1, sizeof(mbedtls_stream)); | |
368 | GIT_ERROR_CHECK_ALLOC(st); | |
369 | ||
370 | st->io = in; | |
371 | st->owned = owned; | |
372 | ||
373 | st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); | |
374 | GIT_ERROR_CHECK_ALLOC(st->ssl); | |
375 | mbedtls_ssl_init(st->ssl); | |
376 | if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { | |
377 | git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); | |
378 | error = -1; | |
379 | goto out_err; | |
380 | } | |
381 | ||
382 | st->host = git__strdup(host); | |
383 | GIT_ERROR_CHECK_ALLOC(st->host); | |
384 | ||
385 | st->parent.version = GIT_STREAM_VERSION; | |
386 | st->parent.encrypted = 1; | |
387 | st->parent.proxy_support = git_stream_supports_proxy(st->io); | |
388 | st->parent.connect = mbedtls_connect; | |
389 | st->parent.certificate = mbedtls_certificate; | |
390 | st->parent.set_proxy = mbedtls_set_proxy; | |
391 | st->parent.read = mbedtls_stream_read; | |
392 | st->parent.write = mbedtls_stream_write; | |
393 | st->parent.close = mbedtls_stream_close; | |
394 | st->parent.free = mbedtls_stream_free; | |
395 | ||
396 | *out = (git_stream *) st; | |
397 | return 0; | |
398 | ||
399 | out_err: | |
400 | mbedtls_ssl_free(st->ssl); | |
401 | git_stream_close(st->io); | |
402 | git_stream_free(st->io); | |
403 | git__free(st); | |
404 | ||
405 | return error; | |
406 | } | |
407 | ||
408 | int git_mbedtls_stream_wrap( | |
409 | git_stream **out, | |
410 | git_stream *in, | |
411 | const char *host) | |
412 | { | |
413 | return mbedtls_stream_wrap(out, in, host, 0); | |
414 | } | |
415 | ||
416 | int git_mbedtls_stream_new( | |
417 | git_stream **out, | |
418 | const char *host, | |
419 | const char *port) | |
420 | { | |
421 | git_stream *stream; | |
422 | int error; | |
423 | ||
c25aa7cd PP |
424 | GIT_ASSERT_ARG(out); |
425 | GIT_ASSERT_ARG(host); | |
426 | GIT_ASSERT_ARG(port); | |
ac3d33df JK |
427 | |
428 | if ((error = git_socket_stream_new(&stream, host, port)) < 0) | |
429 | return error; | |
430 | ||
431 | if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) { | |
432 | git_stream_close(stream); | |
433 | git_stream_free(stream); | |
434 | } | |
435 | ||
436 | return error; | |
437 | } | |
438 | ||
c25aa7cd | 439 | int git_mbedtls__set_cert_location(const char *file, const char *path) |
ac3d33df JK |
440 | { |
441 | int ret = 0; | |
442 | char errbuf[512]; | |
443 | mbedtls_x509_crt *cacert; | |
444 | ||
c25aa7cd | 445 | GIT_ASSERT_ARG(file || path); |
ac3d33df JK |
446 | |
447 | cacert = git__malloc(sizeof(mbedtls_x509_crt)); | |
448 | GIT_ERROR_CHECK_ALLOC(cacert); | |
449 | ||
450 | mbedtls_x509_crt_init(cacert); | |
c25aa7cd PP |
451 | if (file) |
452 | ret = mbedtls_x509_crt_parse_file(cacert, file); | |
453 | if (ret >= 0 && path) | |
ac3d33df | 454 | ret = mbedtls_x509_crt_parse_path(cacert, path); |
ac3d33df JK |
455 | /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ |
456 | if (ret < 0) { | |
457 | mbedtls_x509_crt_free(cacert); | |
458 | git__free(cacert); | |
459 | mbedtls_strerror( ret, errbuf, 512 ); | |
460 | git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); | |
461 | return -1; | |
462 | } | |
463 | ||
464 | mbedtls_x509_crt_free(git__ssl_conf->ca_chain); | |
465 | git__free(git__ssl_conf->ca_chain); | |
466 | mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); | |
467 | ||
468 | return 0; | |
469 | } | |
470 | ||
471 | #else | |
472 | ||
473 | #include "stream.h" | |
474 | ||
475 | int git_mbedtls_stream_global_init(void) | |
476 | { | |
477 | return 0; | |
478 | } | |
479 | ||
480 | #endif |