]> git.proxmox.com Git - libgit2.git/blame - src/openssl_stream.c
Silence unused warnings when not using OpenSSL
[libgit2.git] / src / openssl_stream.c
CommitLineData
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
8#ifdef GIT_SSL
9
468d7b11
CMN
10#include <ctype.h>
11
12#include "global.h"
13#include "posix.h"
14#include "stream.h"
15#include "socket_stream.h"
1b75c29e 16#include "netops.h"
468d7b11
CMN
17#include "git2/transport.h"
18
a944c6cc
AK
19#ifndef GIT_WIN32
20# include <sys/types.h>
21# include <sys/socket.h>
22# include <netinet/in.h>
23#endif
24
ec032442
AK
25#include <openssl/ssl.h>
26#include <openssl/err.h>
27#include <openssl/x509v3.h>
28
468d7b11
CMN
29static int ssl_set_error(SSL *ssl, int error)
30{
31 int err;
32 unsigned long e;
33
34 err = SSL_get_error(ssl, error);
35
36 assert(err != SSL_ERROR_WANT_READ);
37 assert(err != SSL_ERROR_WANT_WRITE);
38
39 switch (err) {
40 case SSL_ERROR_WANT_CONNECT:
41 case SSL_ERROR_WANT_ACCEPT:
42 giterr_set(GITERR_NET, "SSL error: connection failure\n");
43 break;
44 case SSL_ERROR_WANT_X509_LOOKUP:
45 giterr_set(GITERR_NET, "SSL error: x509 error\n");
46 break;
47 case SSL_ERROR_SYSCALL:
48 e = ERR_get_error();
49 if (e > 0) {
50 giterr_set(GITERR_NET, "SSL error: %s",
51 ERR_error_string(e, NULL));
52 break;
53 } else if (error < 0) {
54 giterr_set(GITERR_OS, "SSL error: syscall failure");
55 break;
56 }
57 giterr_set(GITERR_NET, "SSL error: received early EOF");
58 break;
59 case SSL_ERROR_SSL:
60 e = ERR_get_error();
61 giterr_set(GITERR_NET, "SSL error: %s",
62 ERR_error_string(e, NULL));
63 break;
64 case SSL_ERROR_NONE:
65 case SSL_ERROR_ZERO_RETURN:
66 default:
67 giterr_set(GITERR_NET, "SSL error: unknown error");
68 break;
69 }
70 return -1;
71}
72
73static int ssl_teardown(SSL *ssl)
74{
75 int ret;
76
77 ret = SSL_shutdown(ssl);
78 if (ret < 0)
79 ret = ssl_set_error(ssl, ret);
80 else
81 ret = 0;
82
83 SSL_free(ssl);
84 return ret;
85}
86
87static int check_host_name(const char *name, const char *host)
88{
89 if (!strcasecmp(name, host))
90 return 0;
91
92 if (gitno__match_host(name, host) < 0)
93 return -1;
94
95 return 0;
96}
97
98static int verify_server_cert(SSL *ssl, const char *host)
99{
100 X509 *cert;
101 X509_NAME *peer_name;
102 ASN1_STRING *str;
103 unsigned char *peer_cn = NULL;
104 int matched = -1, type = GEN_DNS;
105 GENERAL_NAMES *alts;
106 struct in6_addr addr6;
107 struct in_addr addr4;
108 void *addr;
109 int i = -1,j;
110
111 if (SSL_get_verify_result(ssl) != X509_V_OK) {
112 giterr_set(GITERR_SSL, "The SSL certificate is invalid");
1b75c29e 113 return GIT_ECERTIFICATE;
468d7b11
CMN
114 }
115
116 /* Try to parse the host as an IP address to see if it is */
117 if (p_inet_pton(AF_INET, host, &addr4)) {
118 type = GEN_IPADD;
119 addr = &addr4;
120 } else {
121 if(p_inet_pton(AF_INET6, host, &addr6)) {
122 type = GEN_IPADD;
123 addr = &addr6;
124 }
125 }
126
127
128 cert = SSL_get_peer_certificate(ssl);
129 if (!cert) {
130 giterr_set(GITERR_SSL, "the server did not provide a certificate");
131 return -1;
132 }
133
134 /* Check the alternative names */
135 alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
136 if (alts) {
137 int num;
138
139 num = sk_GENERAL_NAME_num(alts);
140 for (i = 0; i < num && matched != 1; i++) {
141 const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
142 const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
143 size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
144
145 /* Skip any names of a type we're not looking for */
146 if (gn->type != type)
147 continue;
148
149 if (type == GEN_DNS) {
150 /* If it contains embedded NULs, don't even try */
151 if (memchr(name, '\0', namelen))
152 continue;
153
154 if (check_host_name(name, host) < 0)
155 matched = 0;
156 else
157 matched = 1;
158 } else if (type == GEN_IPADD) {
159 /* Here name isn't so much a name but a binary representation of the IP */
160 matched = !!memcmp(name, addr, namelen);
161 }
162 }
163 }
164 GENERAL_NAMES_free(alts);
165
166 if (matched == 0)
167 goto cert_fail_name;
168
169 if (matched == 1)
170 return 0;
171
172 /* If no alternative names are available, check the common name */
173 peer_name = X509_get_subject_name(cert);
174 if (peer_name == NULL)
175 goto on_error;
176
177 if (peer_name) {
178 /* Get the index of the last CN entry */
179 while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
180 i = j;
181 }
182
183 if (i < 0)
184 goto on_error;
185
186 str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
187 if (str == NULL)
188 goto on_error;
189
190 /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
191 if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
192 int size = ASN1_STRING_length(str);
193
194 if (size > 0) {
195 peer_cn = OPENSSL_malloc(size + 1);
196 GITERR_CHECK_ALLOC(peer_cn);
197 memcpy(peer_cn, ASN1_STRING_data(str), size);
198 peer_cn[size] = '\0';
199 }
200 } else {
201 int size = ASN1_STRING_to_UTF8(&peer_cn, str);
202 GITERR_CHECK_ALLOC(peer_cn);
203 if (memchr(peer_cn, '\0', size))
204 goto cert_fail_name;
205 }
206
207 if (check_host_name((char *)peer_cn, host) < 0)
208 goto cert_fail_name;
209
210 OPENSSL_free(peer_cn);
211
212 return 0;
213
214on_error:
215 OPENSSL_free(peer_cn);
216 return ssl_set_error(ssl, 0);
217
218cert_fail_name:
219 OPENSSL_free(peer_cn);
220 giterr_set(GITERR_SSL, "hostname does not match certificate");
221 return GIT_ECERTIFICATE;
222}
223
224typedef struct {
225 git_stream parent;
226 git_socket_stream *socket;
227 SSL *ssl;
228 git_cert_x509 cert_info;
229} openssl_stream;
230
231int openssl_close(git_stream *stream);
232
233int openssl_connect(git_stream *stream)
234{
235 int ret;
236 openssl_stream *st = (openssl_stream *) stream;
237
238 if ((ret = git_stream_connect((git_stream *)st->socket)) < 0)
239 return ret;
240
241 if ((ret = SSL_set_fd(st->ssl, st->socket->s)) <= 0) {
242 openssl_close((git_stream *) st);
243 return ssl_set_error(st->ssl, ret);
244 }
245
246 if ((ret = SSL_connect(st->ssl)) <= 0)
247 return ssl_set_error(st->ssl, ret);
248
249 return verify_server_cert(st->ssl, st->socket->host);
250}
251
252int openssl_certificate(git_cert **out, git_stream *stream)
253{
254 openssl_stream *st = (openssl_stream *) stream;
255 int len;
256 X509 *cert = SSL_get_peer_certificate(st->ssl);
257 unsigned char *guard, *encoded_cert;
258
259 /* Retrieve the length of the certificate first */
260 len = i2d_X509(cert, NULL);
261 if (len < 0) {
262 giterr_set(GITERR_NET, "failed to retrieve certificate information");
263 return -1;
264 }
265
266 encoded_cert = git__malloc(len);
267 GITERR_CHECK_ALLOC(encoded_cert);
268 /* i2d_X509 makes 'guard' point to just after the data */
269 guard = encoded_cert;
270
271 len = i2d_X509(cert, &guard);
272 if (len < 0) {
273 git__free(encoded_cert);
274 giterr_set(GITERR_NET, "failed to retrieve certificate information");
275 return -1;
276 }
277
278 st->cert_info.cert_type = GIT_CERT_X509;
279 st->cert_info.data = encoded_cert;
280 st->cert_info.len = len;
281
282 *out = (git_cert *)&st->cert_info;
283 return 0;
284}
285
49ae22ba 286ssize_t openssl_write(git_stream *stream, const char *data, size_t len, int flags)
468d7b11
CMN
287{
288 openssl_stream *st = (openssl_stream *) stream;
289 int ret;
290 size_t off = 0;
291
292 GIT_UNUSED(flags);
293
294 while (off < len) {
295 ret = SSL_write(st->ssl, data + off, len - off);
296 if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
297 return ssl_set_error(st->ssl, ret);
298
299 off += ret;
300 }
301
302 return off;
303}
304
305ssize_t openssl_read(git_stream *stream, void *data, size_t len)
306{
307 openssl_stream *st = (openssl_stream *) stream;
308 int ret;
309
310 do {
311 ret = SSL_read(st->ssl, data, len);
312 } while (SSL_get_error(st->ssl, ret) == SSL_ERROR_WANT_READ);
313
314 if (ret < 0) {
315 ssl_set_error(st->ssl, ret);
316 return -1;
317 }
318
319 return ret;
320}
321
322int openssl_close(git_stream *stream)
323{
324 openssl_stream *st = (openssl_stream *) stream;
325 int ret;
326
327 if ((ret = ssl_teardown(st->ssl)) < 0)
328 return -1;
329
330 return git_stream_close((git_stream *)st->socket);
331}
332
333void openssl_free(git_stream *stream)
334{
335 openssl_stream *st = (openssl_stream *) stream;
336
337 git__free(st->cert_info.data);
338 git_stream_free((git_stream *) st->socket);
339 git__free(st);
340}
341
342int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
343{
344 openssl_stream *st;
345
346 st = git__calloc(1, sizeof(openssl_stream));
347 GITERR_CHECK_ALLOC(st);
348
349 if (git_socket_stream_new((git_stream **) &st->socket, host, port))
350 return -1;
351
352 st->ssl = SSL_new(git__ssl_ctx);
353 if (st->ssl == NULL) {
354 giterr_set(GITERR_SSL, "failed to create ssl object");
355 return -1;
356 }
357
358 st->parent.version = GIT_STREAM_VERSION;
359 st->parent.encrypted = 1;
360 st->parent.connect = openssl_connect;
361 st->parent.certificate = openssl_certificate;
362 st->parent.read = openssl_read;
363 st->parent.write = openssl_write;
364 st->parent.close = openssl_close;
365 st->parent.free = openssl_free;
366
367 *out = (git_stream *) st;
368 return 0;
369}
370
371#else
372
373#include "stream.h"
374
375int git_openssl_stream_new(git_stream **out, const char *host, const char *port)
376{
70b852ce
CMN
377 GIT_UNUSED(out);
378 GIT_UNUSED(host);
379 GIT_UNUSED(port);
380
468d7b11
CMN
381 giterr_set(GITERR_SSL, "openssl is not supported in this version");
382 return -1;
383}
384
385#endif