]>
Commit | Line | Data |
---|---|---|
468d7b11 CMN |
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 | ||
eae0bfdc PP |
8 | #include "streams/openssl.h" |
9 | ||
24e53d2f | 10 | #ifdef GIT_OPENSSL |
468d7b11 | 11 | |
468d7b11 CMN |
12 | #include <ctype.h> |
13 | ||
14 | #include "global.h" | |
15 | #include "posix.h" | |
16 | #include "stream.h" | |
eae0bfdc | 17 | #include "streams/socket.h" |
1b75c29e | 18 | #include "netops.h" |
468d7b11 | 19 | #include "git2/transport.h" |
c8fe6c09 | 20 | #include "git2/sys/openssl.h" |
468d7b11 | 21 | |
a944c6cc AK |
22 | #ifndef GIT_WIN32 |
23 | # include <sys/types.h> | |
24 | # include <sys/socket.h> | |
25 | # include <netinet/in.h> | |
26 | #endif | |
27 | ||
ec032442 AK |
28 | #include <openssl/ssl.h> |
29 | #include <openssl/err.h> | |
30 | #include <openssl/x509v3.h> | |
e247649d CMN |
31 | #include <openssl/bio.h> |
32 | ||
8a6d6677 ET |
33 | SSL_CTX *git__ssl_ctx; |
34 | ||
fa72d6da DB |
35 | #define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" |
36 | ||
ac3d33df JK |
37 | #if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \ |
38 | (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) | |
39 | # define OPENSSL_LEGACY_API | |
40 | #endif | |
41 | ||
42 | /* | |
43 | * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it | |
44 | * which do not exist in previous versions. We define these inline functions so | |
45 | * we can program against the interface instead of littering the implementation | |
46 | * with ifdefs. We do the same for OPENSSL_init_ssl. | |
47 | */ | |
48 | #if defined(OPENSSL_LEGACY_API) | |
49 | static int OPENSSL_init_ssl(int opts, void *settings) | |
50 | { | |
51 | GIT_UNUSED(opts); | |
52 | GIT_UNUSED(settings); | |
53 | SSL_load_error_strings(); | |
54 | OpenSSL_add_ssl_algorithms(); | |
55 | return 0; | |
56 | } | |
57 | ||
58 | static BIO_METHOD* BIO_meth_new(int type, const char *name) | |
59 | { | |
60 | BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD)); | |
61 | if (!meth) { | |
62 | return NULL; | |
63 | } | |
64 | ||
65 | meth->type = type; | |
66 | meth->name = name; | |
67 | ||
68 | return meth; | |
69 | } | |
70 | ||
71 | static void BIO_meth_free(BIO_METHOD *biom) | |
72 | { | |
73 | git__free(biom); | |
74 | } | |
75 | ||
76 | static int BIO_meth_set_write(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)) | |
77 | { | |
78 | biom->bwrite = write; | |
79 | return 1; | |
80 | } | |
81 | ||
82 | static int BIO_meth_set_read(BIO_METHOD *biom, int (*read) (BIO *, char *, int)) | |
83 | { | |
84 | biom->bread = read; | |
85 | return 1; | |
86 | } | |
87 | ||
88 | static int BIO_meth_set_puts(BIO_METHOD *biom, int (*puts) (BIO *, const char *)) | |
89 | { | |
90 | biom->bputs = puts; | |
91 | return 1; | |
92 | } | |
93 | ||
94 | static int BIO_meth_set_gets(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)) | |
95 | ||
96 | { | |
97 | biom->bgets = gets; | |
98 | return 1; | |
99 | } | |
100 | ||
101 | static int BIO_meth_set_ctrl(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)) | |
102 | { | |
103 | biom->ctrl = ctrl; | |
104 | return 1; | |
105 | } | |
106 | ||
107 | static int BIO_meth_set_create(BIO_METHOD *biom, int (*create) (BIO *)) | |
108 | { | |
109 | biom->create = create; | |
110 | return 1; | |
111 | } | |
112 | ||
113 | static int BIO_meth_set_destroy(BIO_METHOD *biom, int (*destroy) (BIO *)) | |
114 | { | |
115 | biom->destroy = destroy; | |
116 | return 1; | |
117 | } | |
118 | ||
119 | static int BIO_get_new_index(void) | |
120 | { | |
121 | /* This exists as of 1.1 so before we'd just have 0 */ | |
122 | return 0; | |
123 | } | |
124 | ||
125 | static void BIO_set_init(BIO *b, int init) | |
126 | { | |
127 | b->init = init; | |
128 | } | |
129 | ||
130 | static void BIO_set_data(BIO *a, void *ptr) | |
131 | { | |
132 | a->ptr = ptr; | |
133 | } | |
134 | ||
135 | static void *BIO_get_data(BIO *a) | |
136 | { | |
137 | return a->ptr; | |
138 | } | |
139 | ||
140 | static const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *x) | |
141 | { | |
142 | return ASN1_STRING_data((ASN1_STRING *)x); | |
143 | } | |
8a6d6677 | 144 | |
ac3d33df | 145 | # if defined(GIT_THREADS) |
8a6d6677 ET |
146 | static git_mutex *openssl_locks; |
147 | ||
148 | static void openssl_locking_function( | |
149 | int mode, int n, const char *file, int line) | |
150 | { | |
151 | int lock; | |
152 | ||
153 | GIT_UNUSED(file); | |
154 | GIT_UNUSED(line); | |
155 | ||
156 | lock = mode & CRYPTO_LOCK; | |
157 | ||
158 | if (lock) { | |
22a2d3d5 | 159 | (void)git_mutex_lock(&openssl_locks[n]); |
8a6d6677 ET |
160 | } else { |
161 | git_mutex_unlock(&openssl_locks[n]); | |
162 | } | |
163 | } | |
164 | ||
165 | static void shutdown_ssl_locking(void) | |
166 | { | |
167 | int num_locks, i; | |
168 | ||
169 | num_locks = CRYPTO_num_locks(); | |
170 | CRYPTO_set_locking_callback(NULL); | |
171 | ||
172 | for (i = 0; i < num_locks; ++i) | |
dd0b1e8c | 173 | git_mutex_free(&openssl_locks[i]); |
8a6d6677 ET |
174 | git__free(openssl_locks); |
175 | } | |
ac3d33df JK |
176 | # endif /* GIT_THREADS */ |
177 | #endif /* OPENSSL_LEGACY_API */ | |
8a6d6677 | 178 | |
f15eedb3 CMN |
179 | static BIO_METHOD *git_stream_bio_method; |
180 | static int init_bio_method(void); | |
181 | ||
8a6d6677 ET |
182 | /** |
183 | * This function aims to clean-up the SSL context which | |
184 | * we allocated. | |
185 | */ | |
186 | static void shutdown_ssl(void) | |
187 | { | |
f15eedb3 CMN |
188 | if (git_stream_bio_method) { |
189 | BIO_meth_free(git_stream_bio_method); | |
190 | git_stream_bio_method = NULL; | |
191 | } | |
192 | ||
8a6d6677 ET |
193 | if (git__ssl_ctx) { |
194 | SSL_CTX_free(git__ssl_ctx); | |
195 | git__ssl_ctx = NULL; | |
196 | } | |
197 | } | |
198 | ||
22a2d3d5 UG |
199 | #ifdef VALGRIND |
200 | #ifdef OPENSSL_LEGACY_API | |
201 | static void *git_openssl_malloc(size_t bytes) | |
202 | { | |
203 | return git__calloc(1, bytes); | |
204 | } | |
205 | ||
206 | static void *git_openssl_realloc(void *mem, size_t size) | |
207 | { | |
208 | return git__realloc(mem, size); | |
209 | } | |
210 | ||
211 | static void git_openssl_free(void *mem) | |
212 | { | |
213 | return git__free(mem); | |
214 | } | |
215 | #else | |
216 | static void *git_openssl_malloc(size_t bytes, const char *file, int line) | |
217 | { | |
218 | GIT_UNUSED(file); | |
219 | GIT_UNUSED(line); | |
220 | return git__calloc(1, bytes); | |
221 | } | |
222 | ||
223 | static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line) | |
224 | { | |
225 | GIT_UNUSED(file); | |
226 | GIT_UNUSED(line); | |
227 | return git__realloc(mem, size); | |
228 | } | |
229 | ||
230 | static void git_openssl_free(void *mem, const char *file, int line) | |
231 | { | |
232 | GIT_UNUSED(file); | |
233 | GIT_UNUSED(line); | |
234 | return git__free(mem); | |
235 | } | |
236 | #endif | |
237 | #endif | |
238 | ||
8a6d6677 ET |
239 | int git_openssl_stream_global_init(void) |
240 | { | |
8a6d6677 | 241 | long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; |
fa72d6da | 242 | const char *ciphers = git_libgit2__ssl_ciphers(); |
22a2d3d5 UG |
243 | #ifdef VALGRIND |
244 | static bool allocators_initialized = false; | |
245 | #endif | |
8a6d6677 ET |
246 | |
247 | /* Older OpenSSL and MacOS OpenSSL doesn't have this */ | |
248 | #ifdef SSL_OP_NO_COMPRESSION | |
249 | ssl_opts |= SSL_OP_NO_COMPRESSION; | |
250 | #endif | |
251 | ||
22a2d3d5 UG |
252 | #ifdef VALGRIND |
253 | /* Swap in our own allocator functions that initialize allocated memory */ | |
254 | if (!allocators_initialized && | |
255 | CRYPTO_set_mem_functions(git_openssl_malloc, | |
256 | git_openssl_realloc, | |
257 | git_openssl_free) != 1) | |
258 | goto error; | |
259 | allocators_initialized = true; | |
260 | #endif | |
261 | ||
88520151 | 262 | OPENSSL_init_ssl(0, NULL); |
88520151 | 263 | |
8a6d6677 ET |
264 | /* |
265 | * Load SSLv{2,3} and TLSv1 so that we can talk with servers | |
266 | * which use the SSL hellos, which are often used for | |
267 | * compatibility. We then disable SSL so we only allow OpenSSL | |
268 | * to speak TLSv1 to perform the encryption itself. | |
269 | */ | |
ac3d33df JK |
270 | if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) |
271 | goto error; | |
272 | ||
8a6d6677 ET |
273 | SSL_CTX_set_options(git__ssl_ctx, ssl_opts); |
274 | SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); | |
275 | SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); | |
ac3d33df JK |
276 | if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) |
277 | goto error; | |
fa72d6da | 278 | |
ac3d33df | 279 | if (!ciphers) |
fa72d6da | 280 | ciphers = GIT_SSL_DEFAULT_CIPHERS; |
fa72d6da | 281 | |
ac3d33df JK |
282 | if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) |
283 | goto error; | |
f15eedb3 | 284 | |
ac3d33df JK |
285 | if (init_bio_method() < 0) |
286 | goto error; | |
8a6d6677 ET |
287 | |
288 | git__on_shutdown(shutdown_ssl); | |
289 | ||
290 | return 0; | |
ac3d33df JK |
291 | |
292 | error: | |
293 | git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s", | |
294 | ERR_error_string(ERR_get_error(), NULL)); | |
295 | SSL_CTX_free(git__ssl_ctx); | |
296 | git__ssl_ctx = NULL; | |
297 | return -1; | |
8a6d6677 ET |
298 | } |
299 | ||
ac3d33df | 300 | #if defined(GIT_THREADS) && defined(OPENSSL_LEGACY_API) |
eae0bfdc PP |
301 | static void threadid_cb(CRYPTO_THREADID *threadid) |
302 | { | |
ac3d33df JK |
303 | GIT_UNUSED(threadid); |
304 | CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); | |
eae0bfdc PP |
305 | } |
306 | #endif | |
307 | ||
8a6d6677 ET |
308 | int git_openssl_set_locking(void) |
309 | { | |
ac3d33df | 310 | #if defined(GIT_THREADS) && defined(OPENSSL_LEGACY_API) |
8a6d6677 ET |
311 | int num_locks, i; |
312 | ||
eae0bfdc PP |
313 | CRYPTO_THREADID_set_callback(threadid_cb); |
314 | ||
8a6d6677 ET |
315 | num_locks = CRYPTO_num_locks(); |
316 | openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); | |
ac3d33df | 317 | GIT_ERROR_CHECK_ALLOC(openssl_locks); |
8a6d6677 ET |
318 | |
319 | for (i = 0; i < num_locks; i++) { | |
320 | if (git_mutex_init(&openssl_locks[i]) != 0) { | |
ac3d33df | 321 | git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks"); |
8a6d6677 ET |
322 | return -1; |
323 | } | |
324 | } | |
325 | ||
326 | CRYPTO_set_locking_callback(openssl_locking_function); | |
327 | git__on_shutdown(shutdown_ssl_locking); | |
328 | return 0; | |
ac3d33df | 329 | #elif !defined(OPENSSL_LEGACY_API) |
29081c2f | 330 | return 0; |
8a6d6677 | 331 | #else |
ac3d33df | 332 | git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); |
8a6d6677 ET |
333 | return -1; |
334 | #endif | |
335 | } | |
336 | ||
337 | ||
e247649d CMN |
338 | static int bio_create(BIO *b) |
339 | { | |
feb330d5 | 340 | BIO_set_init(b, 1); |
f15eedb3 | 341 | BIO_set_data(b, NULL); |
e247649d CMN |
342 | |
343 | return 1; | |
344 | } | |
345 | ||
346 | static int bio_destroy(BIO *b) | |
347 | { | |
348 | if (!b) | |
349 | return 0; | |
350 | ||
feb330d5 | 351 | BIO_set_data(b, NULL); |
e247649d CMN |
352 | |
353 | return 1; | |
354 | } | |
355 | ||
356 | static int bio_read(BIO *b, char *buf, int len) | |
357 | { | |
feb330d5 | 358 | git_stream *io = (git_stream *) BIO_get_data(b); |
f15eedb3 | 359 | |
e247649d CMN |
360 | return (int) git_stream_read(io, buf, len); |
361 | } | |
362 | ||
363 | static int bio_write(BIO *b, const char *buf, int len) | |
364 | { | |
feb330d5 | 365 | git_stream *io = (git_stream *) BIO_get_data(b); |
e247649d CMN |
366 | return (int) git_stream_write(io, buf, len, 0); |
367 | } | |
368 | ||
369 | static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) | |
370 | { | |
371 | GIT_UNUSED(b); | |
372 | GIT_UNUSED(num); | |
373 | GIT_UNUSED(ptr); | |
374 | ||
375 | if (cmd == BIO_CTRL_FLUSH) | |
376 | return 1; | |
377 | ||
378 | return 0; | |
379 | } | |
380 | ||
381 | static int bio_gets(BIO *b, char *buf, int len) | |
382 | { | |
383 | GIT_UNUSED(b); | |
384 | GIT_UNUSED(buf); | |
385 | GIT_UNUSED(len); | |
386 | return -1; | |
387 | } | |
388 | ||
389 | static int bio_puts(BIO *b, const char *str) | |
390 | { | |
391 | return bio_write(b, str, strlen(str)); | |
392 | } | |
393 | ||
f15eedb3 CMN |
394 | static int init_bio_method(void) |
395 | { | |
396 | /* Set up the BIO_METHOD we use for wrapping our own stream implementations */ | |
397 | git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream"); | |
ac3d33df | 398 | GIT_ERROR_CHECK_ALLOC(git_stream_bio_method); |
f15eedb3 CMN |
399 | |
400 | BIO_meth_set_write(git_stream_bio_method, bio_write); | |
401 | BIO_meth_set_read(git_stream_bio_method, bio_read); | |
402 | BIO_meth_set_puts(git_stream_bio_method, bio_puts); | |
403 | BIO_meth_set_gets(git_stream_bio_method, bio_gets); | |
404 | BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl); | |
405 | BIO_meth_set_create(git_stream_bio_method, bio_create); | |
406 | BIO_meth_set_destroy(git_stream_bio_method, bio_destroy); | |
407 | ||
408 | return 0; | |
409 | } | |
ec032442 | 410 | |
468d7b11 CMN |
411 | static int ssl_set_error(SSL *ssl, int error) |
412 | { | |
413 | int err; | |
414 | unsigned long e; | |
415 | ||
416 | err = SSL_get_error(ssl, error); | |
417 | ||
418 | assert(err != SSL_ERROR_WANT_READ); | |
419 | assert(err != SSL_ERROR_WANT_WRITE); | |
420 | ||
421 | switch (err) { | |
422 | case SSL_ERROR_WANT_CONNECT: | |
423 | case SSL_ERROR_WANT_ACCEPT: | |
ac3d33df | 424 | git_error_set(GIT_ERROR_SSL, "SSL error: connection failure"); |
468d7b11 CMN |
425 | break; |
426 | case SSL_ERROR_WANT_X509_LOOKUP: | |
ac3d33df | 427 | git_error_set(GIT_ERROR_SSL, "SSL error: x509 error"); |
468d7b11 CMN |
428 | break; |
429 | case SSL_ERROR_SYSCALL: | |
430 | e = ERR_get_error(); | |
431 | if (e > 0) { | |
eae0bfdc PP |
432 | char errmsg[256]; |
433 | ERR_error_string_n(e, errmsg, sizeof(errmsg)); | |
ac3d33df | 434 | git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg); |
468d7b11 CMN |
435 | break; |
436 | } else if (error < 0) { | |
ac3d33df | 437 | git_error_set(GIT_ERROR_OS, "SSL error: syscall failure"); |
468d7b11 CMN |
438 | break; |
439 | } | |
ac3d33df | 440 | git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF"); |
1396c381 | 441 | return GIT_EEOF; |
468d7b11 CMN |
442 | break; |
443 | case SSL_ERROR_SSL: | |
eae0bfdc PP |
444 | { |
445 | char errmsg[256]; | |
468d7b11 | 446 | e = ERR_get_error(); |
eae0bfdc | 447 | ERR_error_string_n(e, errmsg, sizeof(errmsg)); |
ac3d33df | 448 | git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg); |
468d7b11 | 449 | break; |
eae0bfdc | 450 | } |
468d7b11 CMN |
451 | case SSL_ERROR_NONE: |
452 | case SSL_ERROR_ZERO_RETURN: | |
453 | default: | |
ac3d33df | 454 | git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); |
468d7b11 CMN |
455 | break; |
456 | } | |
457 | return -1; | |
458 | } | |
459 | ||
460 | static int ssl_teardown(SSL *ssl) | |
461 | { | |
462 | int ret; | |
463 | ||
464 | ret = SSL_shutdown(ssl); | |
465 | if (ret < 0) | |
466 | ret = ssl_set_error(ssl, ret); | |
467 | else | |
468 | ret = 0; | |
469 | ||
468d7b11 CMN |
470 | return ret; |
471 | } | |
472 | ||
473 | static int check_host_name(const char *name, const char *host) | |
474 | { | |
475 | if (!strcasecmp(name, host)) | |
476 | return 0; | |
477 | ||
478 | if (gitno__match_host(name, host) < 0) | |
479 | return -1; | |
480 | ||
481 | return 0; | |
482 | } | |
483 | ||
484 | static int verify_server_cert(SSL *ssl, const char *host) | |
485 | { | |
eae0bfdc | 486 | X509 *cert = NULL; |
468d7b11 CMN |
487 | X509_NAME *peer_name; |
488 | ASN1_STRING *str; | |
489 | unsigned char *peer_cn = NULL; | |
490 | int matched = -1, type = GEN_DNS; | |
491 | GENERAL_NAMES *alts; | |
492 | struct in6_addr addr6; | |
493 | struct in_addr addr4; | |
eae0bfdc PP |
494 | void *addr = NULL; |
495 | int i = -1, j, error = 0; | |
468d7b11 CMN |
496 | |
497 | if (SSL_get_verify_result(ssl) != X509_V_OK) { | |
ac3d33df | 498 | git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid"); |
1b75c29e | 499 | return GIT_ECERTIFICATE; |
468d7b11 CMN |
500 | } |
501 | ||
502 | /* Try to parse the host as an IP address to see if it is */ | |
503 | if (p_inet_pton(AF_INET, host, &addr4)) { | |
504 | type = GEN_IPADD; | |
505 | addr = &addr4; | |
506 | } else { | |
eae0bfdc | 507 | if (p_inet_pton(AF_INET6, host, &addr6)) { |
468d7b11 CMN |
508 | type = GEN_IPADD; |
509 | addr = &addr6; | |
510 | } | |
511 | } | |
512 | ||
513 | ||
514 | cert = SSL_get_peer_certificate(ssl); | |
515 | if (!cert) { | |
eae0bfdc | 516 | error = -1; |
ac3d33df | 517 | git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); |
eae0bfdc | 518 | goto cleanup; |
468d7b11 CMN |
519 | } |
520 | ||
521 | /* Check the alternative names */ | |
522 | alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); | |
523 | if (alts) { | |
524 | int num; | |
525 | ||
526 | num = sk_GENERAL_NAME_num(alts); | |
527 | for (i = 0; i < num && matched != 1; i++) { | |
528 | const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); | |
2f3adf95 | 529 | const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5); |
468d7b11 CMN |
530 | size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); |
531 | ||
532 | /* Skip any names of a type we're not looking for */ | |
533 | if (gn->type != type) | |
534 | continue; | |
535 | ||
536 | if (type == GEN_DNS) { | |
537 | /* If it contains embedded NULs, don't even try */ | |
538 | if (memchr(name, '\0', namelen)) | |
539 | continue; | |
540 | ||
541 | if (check_host_name(name, host) < 0) | |
542 | matched = 0; | |
543 | else | |
544 | matched = 1; | |
545 | } else if (type == GEN_IPADD) { | |
546 | /* Here name isn't so much a name but a binary representation of the IP */ | |
eae0bfdc | 547 | matched = addr && !!memcmp(name, addr, namelen); |
468d7b11 CMN |
548 | } |
549 | } | |
550 | } | |
551 | GENERAL_NAMES_free(alts); | |
552 | ||
553 | if (matched == 0) | |
554 | goto cert_fail_name; | |
555 | ||
eae0bfdc PP |
556 | if (matched == 1) { |
557 | goto cleanup; | |
558 | } | |
468d7b11 CMN |
559 | |
560 | /* If no alternative names are available, check the common name */ | |
561 | peer_name = X509_get_subject_name(cert); | |
562 | if (peer_name == NULL) | |
563 | goto on_error; | |
564 | ||
565 | if (peer_name) { | |
566 | /* Get the index of the last CN entry */ | |
567 | while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) | |
568 | i = j; | |
569 | } | |
570 | ||
571 | if (i < 0) | |
572 | goto on_error; | |
573 | ||
574 | str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); | |
575 | if (str == NULL) | |
576 | goto on_error; | |
577 | ||
578 | /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ | |
579 | if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { | |
580 | int size = ASN1_STRING_length(str); | |
581 | ||
582 | if (size > 0) { | |
583 | peer_cn = OPENSSL_malloc(size + 1); | |
ac3d33df | 584 | GIT_ERROR_CHECK_ALLOC(peer_cn); |
2f3adf95 | 585 | memcpy(peer_cn, ASN1_STRING_get0_data(str), size); |
468d7b11 | 586 | peer_cn[size] = '\0'; |
05bf67b9 PS |
587 | } else { |
588 | goto cert_fail_name; | |
468d7b11 CMN |
589 | } |
590 | } else { | |
591 | int size = ASN1_STRING_to_UTF8(&peer_cn, str); | |
ac3d33df | 592 | GIT_ERROR_CHECK_ALLOC(peer_cn); |
468d7b11 CMN |
593 | if (memchr(peer_cn, '\0', size)) |
594 | goto cert_fail_name; | |
595 | } | |
596 | ||
597 | if (check_host_name((char *)peer_cn, host) < 0) | |
598 | goto cert_fail_name; | |
599 | ||
eae0bfdc | 600 | goto cleanup; |
468d7b11 | 601 | |
eae0bfdc PP |
602 | cert_fail_name: |
603 | error = GIT_ECERTIFICATE; | |
ac3d33df | 604 | git_error_set(GIT_ERROR_SSL, "hostname does not match certificate"); |
eae0bfdc | 605 | goto cleanup; |
468d7b11 CMN |
606 | |
607 | on_error: | |
eae0bfdc PP |
608 | error = ssl_set_error(ssl, 0); |
609 | goto cleanup; | |
468d7b11 | 610 | |
eae0bfdc PP |
611 | cleanup: |
612 | X509_free(cert); | |
468d7b11 | 613 | OPENSSL_free(peer_cn); |
eae0bfdc | 614 | return error; |
468d7b11 CMN |
615 | } |
616 | ||
617 | typedef struct { | |
618 | git_stream parent; | |
e247649d | 619 | git_stream *io; |
ac3d33df | 620 | int owned; |
146a96de | 621 | bool connected; |
e247649d | 622 | char *host; |
468d7b11 CMN |
623 | SSL *ssl; |
624 | git_cert_x509 cert_info; | |
625 | } openssl_stream; | |
626 | ||
ac3d33df | 627 | static int openssl_connect(git_stream *stream) |
468d7b11 CMN |
628 | { |
629 | int ret; | |
e247649d | 630 | BIO *bio; |
468d7b11 CMN |
631 | openssl_stream *st = (openssl_stream *) stream; |
632 | ||
ac3d33df | 633 | if (st->owned && (ret = git_stream_connect(st->io)) < 0) |
468d7b11 CMN |
634 | return ret; |
635 | ||
feb330d5 | 636 | bio = BIO_new(git_stream_bio_method); |
ac3d33df | 637 | GIT_ERROR_CHECK_ALLOC(bio); |
468d7b11 | 638 | |
f15eedb3 | 639 | BIO_set_data(bio, st->io); |
e247649d | 640 | SSL_set_bio(st->ssl, bio, bio); |
f15eedb3 | 641 | |
987045c7 | 642 | /* specify the host in case SNI is needed */ |
febc8c46 | 643 | #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME |
e247649d | 644 | SSL_set_tlsext_host_name(st->ssl, st->host); |
febc8c46 | 645 | #endif |
987045c7 | 646 | |
468d7b11 CMN |
647 | if ((ret = SSL_connect(st->ssl)) <= 0) |
648 | return ssl_set_error(st->ssl, ret); | |
649 | ||
ac3d33df JK |
650 | st->connected = true; |
651 | ||
e247649d | 652 | return verify_server_cert(st->ssl, st->host); |
468d7b11 CMN |
653 | } |
654 | ||
ac3d33df | 655 | static int openssl_certificate(git_cert **out, git_stream *stream) |
468d7b11 CMN |
656 | { |
657 | openssl_stream *st = (openssl_stream *) stream; | |
468d7b11 | 658 | X509 *cert = SSL_get_peer_certificate(st->ssl); |
22a2d3d5 UG |
659 | unsigned char *guard, *encoded_cert = NULL; |
660 | int error, len; | |
468d7b11 CMN |
661 | |
662 | /* Retrieve the length of the certificate first */ | |
663 | len = i2d_X509(cert, NULL); | |
664 | if (len < 0) { | |
ac3d33df | 665 | git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); |
22a2d3d5 UG |
666 | error = -1; |
667 | goto out; | |
468d7b11 CMN |
668 | } |
669 | ||
670 | encoded_cert = git__malloc(len); | |
ac3d33df | 671 | GIT_ERROR_CHECK_ALLOC(encoded_cert); |
468d7b11 CMN |
672 | /* i2d_X509 makes 'guard' point to just after the data */ |
673 | guard = encoded_cert; | |
674 | ||
675 | len = i2d_X509(cert, &guard); | |
676 | if (len < 0) { | |
ac3d33df | 677 | git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); |
22a2d3d5 UG |
678 | error = -1; |
679 | goto out; | |
468d7b11 CMN |
680 | } |
681 | ||
79698030 | 682 | st->cert_info.parent.cert_type = GIT_CERT_X509; |
468d7b11 CMN |
683 | st->cert_info.data = encoded_cert; |
684 | st->cert_info.len = len; | |
22a2d3d5 | 685 | encoded_cert = NULL; |
468d7b11 | 686 | |
79698030 | 687 | *out = &st->cert_info.parent; |
22a2d3d5 | 688 | error = 0; |
79698030 | 689 | |
22a2d3d5 UG |
690 | out: |
691 | git__free(encoded_cert); | |
692 | X509_free(cert); | |
693 | return error; | |
468d7b11 CMN |
694 | } |
695 | ||
b373e9a6 | 696 | static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) |
e247649d CMN |
697 | { |
698 | openssl_stream *st = (openssl_stream *) stream; | |
699 | ||
b373e9a6 | 700 | return git_stream_set_proxy(st->io, proxy_opts); |
e247649d CMN |
701 | } |
702 | ||
ac3d33df | 703 | static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags) |
468d7b11 CMN |
704 | { |
705 | openssl_stream *st = (openssl_stream *) stream; | |
ac3d33df | 706 | int ret, len = min(data_len, INT_MAX); |
468d7b11 CMN |
707 | |
708 | GIT_UNUSED(flags); | |
709 | ||
ac3d33df | 710 | if ((ret = SSL_write(st->ssl, data, len)) <= 0) |
77bffc2c | 711 | return ssl_set_error(st->ssl, ret); |
468d7b11 | 712 | |
77bffc2c | 713 | return ret; |
468d7b11 CMN |
714 | } |
715 | ||
ac3d33df | 716 | static ssize_t openssl_read(git_stream *stream, void *data, size_t len) |
468d7b11 CMN |
717 | { |
718 | openssl_stream *st = (openssl_stream *) stream; | |
719 | int ret; | |
720 | ||
568c5a9f | 721 | if ((ret = SSL_read(st->ssl, data, len)) <= 0) |
4734c52a | 722 | return ssl_set_error(st->ssl, ret); |
468d7b11 CMN |
723 | |
724 | return ret; | |
725 | } | |
726 | ||
ac3d33df | 727 | static int openssl_close(git_stream *stream) |
468d7b11 CMN |
728 | { |
729 | openssl_stream *st = (openssl_stream *) stream; | |
730 | int ret; | |
731 | ||
146a96de | 732 | if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) |
468d7b11 CMN |
733 | return -1; |
734 | ||
146a96de CMN |
735 | st->connected = false; |
736 | ||
ac3d33df | 737 | return st->owned ? git_stream_close(st->io) : 0; |
468d7b11 CMN |
738 | } |
739 | ||
ac3d33df | 740 | static void openssl_free(git_stream *stream) |
468d7b11 CMN |
741 | { |
742 | openssl_stream *st = (openssl_stream *) stream; | |
743 | ||
ac3d33df JK |
744 | if (st->owned) |
745 | git_stream_free(st->io); | |
746 | ||
deecaa2e | 747 | SSL_free(st->ssl); |
3ca84ac0 | 748 | git__free(st->host); |
468d7b11 | 749 | git__free(st->cert_info.data); |
468d7b11 CMN |
750 | git__free(st); |
751 | } | |
752 | ||
ac3d33df JK |
753 | static int openssl_stream_wrap( |
754 | git_stream **out, | |
755 | git_stream *in, | |
756 | const char *host, | |
757 | int owned) | |
468d7b11 CMN |
758 | { |
759 | openssl_stream *st; | |
760 | ||
ac3d33df | 761 | assert(out && in && host); |
468d7b11 | 762 | |
ac3d33df JK |
763 | st = git__calloc(1, sizeof(openssl_stream)); |
764 | GIT_ERROR_CHECK_ALLOC(st); | |
e247649d | 765 | |
ac3d33df JK |
766 | st->io = in; |
767 | st->owned = owned; | |
468d7b11 CMN |
768 | |
769 | st->ssl = SSL_new(git__ssl_ctx); | |
770 | if (st->ssl == NULL) { | |
ac3d33df JK |
771 | git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); |
772 | git__free(st); | |
773 | return -1; | |
468d7b11 CMN |
774 | } |
775 | ||
e247649d | 776 | st->host = git__strdup(host); |
ac3d33df | 777 | GIT_ERROR_CHECK_ALLOC(st->host); |
e247649d | 778 | |
468d7b11 CMN |
779 | st->parent.version = GIT_STREAM_VERSION; |
780 | st->parent.encrypted = 1; | |
e247649d | 781 | st->parent.proxy_support = git_stream_supports_proxy(st->io); |
468d7b11 CMN |
782 | st->parent.connect = openssl_connect; |
783 | st->parent.certificate = openssl_certificate; | |
e247649d | 784 | st->parent.set_proxy = openssl_set_proxy; |
468d7b11 CMN |
785 | st->parent.read = openssl_read; |
786 | st->parent.write = openssl_write; | |
787 | st->parent.close = openssl_close; | |
788 | st->parent.free = openssl_free; | |
789 | ||
790 | *out = (git_stream *) st; | |
791 | return 0; | |
ac3d33df | 792 | } |
2baf854e | 793 | |
ac3d33df JK |
794 | int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host) |
795 | { | |
796 | return openssl_stream_wrap(out, in, host, 0); | |
797 | } | |
798 | ||
799 | int git_openssl_stream_new(git_stream **out, const char *host, const char *port) | |
800 | { | |
801 | git_stream *stream = NULL; | |
802 | int error; | |
803 | ||
804 | assert(out && host && port); | |
805 | ||
806 | if ((error = git_socket_stream_new(&stream, host, port)) < 0) | |
807 | return error; | |
808 | ||
809 | if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) { | |
810 | git_stream_close(stream); | |
811 | git_stream_free(stream); | |
812 | } | |
2baf854e PS |
813 | |
814 | return error; | |
468d7b11 CMN |
815 | } |
816 | ||
eae0bfdc PP |
817 | int git_openssl__set_cert_location(const char *file, const char *path) |
818 | { | |
819 | if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { | |
820 | char errmsg[256]; | |
821 | ||
822 | ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); | |
ac3d33df | 823 | git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s", |
eae0bfdc PP |
824 | errmsg); |
825 | ||
826 | return -1; | |
827 | } | |
828 | return 0; | |
829 | } | |
830 | ||
468d7b11 CMN |
831 | #else |
832 | ||
833 | #include "stream.h" | |
68ad3156 | 834 | #include "git2/sys/openssl.h" |
468d7b11 | 835 | |
8a6d6677 ET |
836 | int git_openssl_stream_global_init(void) |
837 | { | |
838 | return 0; | |
839 | } | |
840 | ||
841 | int git_openssl_set_locking(void) | |
842 | { | |
ac3d33df | 843 | git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support"); |
eae0bfdc PP |
844 | return -1; |
845 | } | |
846 | ||
468d7b11 | 847 | #endif |