]> git.proxmox.com Git - libgit2.git/blame - src/streams/stransport.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / streams / stransport.c
CommitLineData
6bb54cbf
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/stransport.h"
9
6bb54cbf
CMN
10#ifdef GIT_SECURE_TRANSPORT
11
12#include <CoreFoundation/CoreFoundation.h>
13#include <Security/SecureTransport.h>
14#include <Security/SecCertificate.h>
15
16#include "git2/transport.h"
17
eae0bfdc 18#include "streams/socket.h"
6bb54cbf 19
067bf5dc 20static int stransport_error(OSStatus ret)
6bb54cbf 21{
b7e1c81d
CMN
22 CFStringRef message;
23
44b769e4 24 if (ret == noErr || ret == errSSLClosedGraceful) {
ac3d33df 25 git_error_clear();
6bb54cbf 26 return 0;
6bb54cbf 27 }
b7e1c81d 28
b224c388 29#if !TARGET_OS_IPHONE
b7e1c81d 30 message = SecCopyErrorMessageString(ret, NULL);
ac3d33df 31 GIT_ERROR_CHECK_ALLOC(message);
b7e1c81d 32
ac3d33df 33 git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
b7e1c81d 34 CFRelease(message);
b224c388 35#else
ac3d33df
JK
36 git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret);
37 GIT_UNUSED(message);
b224c388
LC
38#endif
39
b7e1c81d 40 return -1;
6bb54cbf
CMN
41}
42
43typedef struct {
44 git_stream parent;
45 git_stream *io;
ac3d33df 46 int owned;
6bb54cbf
CMN
47 SSLContextRef ctx;
48 CFDataRef der_data;
49 git_cert_x509 cert_info;
50} stransport_stream;
51
067bf5dc 52static int stransport_connect(git_stream *stream)
6bb54cbf
CMN
53{
54 stransport_stream *st = (stransport_stream *) stream;
55 int error;
b7e1c81d
CMN
56 SecTrustRef trust = NULL;
57 SecTrustResultType sec_res;
6bb54cbf
CMN
58 OSStatus ret;
59
ac3d33df 60 if (st->owned && (error = git_stream_connect(st->io)) < 0)
6bb54cbf
CMN
61 return error;
62
b7e1c81d
CMN
63 ret = SSLHandshake(st->ctx);
64 if (ret != errSSLServerAuthCompleted) {
ac3d33df 65 git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret);
b7e1c81d
CMN
66 return -1;
67 }
68
69 if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
70 goto on_error;
71
9884dd61
CMN
72 if (!trust)
73 return GIT_ECERTIFICATE;
74
b7e1c81d
CMN
75 if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
76 goto on_error;
77
78 CFRelease(trust);
79
80 if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
ac3d33df 81 git_error_set(GIT_ERROR_SSL, "internal security trust error");
b7e1c81d
CMN
82 return -1;
83 }
84
85 if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
eae0bfdc 86 sec_res == kSecTrustResultFatalTrustFailure) {
ac3d33df 87 git_error_set(GIT_ERROR_SSL, "untrusted connection error");
b7e1c81d 88 return GIT_ECERTIFICATE;
eae0bfdc 89 }
6bb54cbf
CMN
90
91 return 0;
b7e1c81d
CMN
92
93on_error:
94 if (trust)
95 CFRelease(trust);
96
97 return stransport_error(ret);
6bb54cbf
CMN
98}
99
067bf5dc 100static int stransport_certificate(git_cert **out, git_stream *stream)
6bb54cbf
CMN
101{
102 stransport_stream *st = (stransport_stream *) stream;
103 SecTrustRef trust = NULL;
104 SecCertificateRef sec_cert;
6bb54cbf
CMN
105 OSStatus ret;
106
107 if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
108 return stransport_error(ret);
109
6bb54cbf
CMN
110 sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
111 st->der_data = SecCertificateCopyData(sec_cert);
112 CFRelease(trust);
113
114 if (st->der_data == NULL) {
ac3d33df 115 git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data");
6bb54cbf
CMN
116 return -1;
117 }
118
79698030 119 st->cert_info.parent.cert_type = GIT_CERT_X509;
6bb54cbf
CMN
120 st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data);
121 st->cert_info.len = CFDataGetLength(st->der_data);
122
123 *out = (git_cert *)&st->cert_info;
124 return 0;
125}
126
067bf5dc 127static int stransport_set_proxy(
e0aed4bd
ET
128 git_stream *stream,
129 const git_proxy_options *proxy_opts)
58ca8c7e
CMN
130{
131 stransport_stream *st = (stransport_stream *) stream;
132
e0aed4bd 133 return git_stream_set_proxy(st->io, proxy_opts);
58ca8c7e
CMN
134}
135
6d0a0aca
POL
136/*
137 * Contrary to typical network IO callbacks, Secure Transport write callback is
138 * expected to write *all* passed data, not just as much as it can, and any
139 * other case would be considered a failure.
140 *
141 * This behavior is actually not specified in the Apple documentation, but is
142 * required for things to work correctly (and incidentally, that's also how
143 * Apple implements it in its projects at opensource.apple.com).
144 *
145 * Libgit2 streams happen to already have this very behavior so this is just
146 * passthrough.
147 */
6bb54cbf
CMN
148static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len)
149{
150 git_stream *io = (git_stream *) conn;
6bb54cbf 151
ac3d33df 152 if (git_stream__write_full(io, data, *len, 0) < 0)
6d0a0aca 153 return -36; /* "ioErr" from MacErrors.h which is not available on iOS */
6bb54cbf 154
6bb54cbf
CMN
155 return noErr;
156}
157
067bf5dc 158static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags)
6bb54cbf
CMN
159{
160 stransport_stream *st = (stransport_stream *) stream;
161 size_t data_len, processed;
162 OSStatus ret;
163
164 GIT_UNUSED(flags);
165
ac3d33df 166 data_len = min(len, SSIZE_MAX);
6bb54cbf
CMN
167 if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr)
168 return stransport_error(ret);
169
c25aa7cd 170 GIT_ASSERT(processed < SSIZE_MAX);
ac3d33df 171 return (ssize_t)processed;
6bb54cbf
CMN
172}
173
6d0a0aca
POL
174/*
175 * Contrary to typical network IO callbacks, Secure Transport read callback is
176 * expected to read *exactly* the requested number of bytes, not just as much
177 * as it can, and any other case would be considered a failure.
178 *
179 * This behavior is actually not specified in the Apple documentation, but is
180 * required for things to work correctly (and incidentally, that's also how
181 * Apple implements it in its projects at opensource.apple.com).
182 */
6bb54cbf
CMN
183static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len)
184{
185 git_stream *io = (git_stream *) conn;
6d0a0aca
POL
186 OSStatus error = noErr;
187 size_t off = 0;
6bb54cbf 188 ssize_t ret;
6bb54cbf 189
6bb54cbf 190 do {
6d0a0aca 191 ret = git_stream_read(io, data + off, *len - off);
6bb54cbf 192 if (ret < 0) {
6d0a0aca
POL
193 error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */
194 break;
195 }
196 if (ret == 0) {
197 error = errSSLClosedGraceful;
198 break;
6bb54cbf
CMN
199 }
200
6d0a0aca
POL
201 off += ret;
202 } while (off < *len);
6bb54cbf 203
6d0a0aca
POL
204 *len = off;
205 return error;
6bb54cbf
CMN
206}
207
067bf5dc 208static ssize_t stransport_read(git_stream *stream, void *data, size_t len)
6bb54cbf
CMN
209{
210 stransport_stream *st = (stransport_stream *) stream;
211 size_t processed;
212 OSStatus ret;
213
214 if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr)
215 return stransport_error(ret);
216
217 return processed;
218}
219
067bf5dc 220static int stransport_close(git_stream *stream)
6bb54cbf
CMN
221{
222 stransport_stream *st = (stransport_stream *) stream;
223 OSStatus ret;
224
44b769e4
CMN
225 ret = SSLClose(st->ctx);
226 if (ret != noErr && ret != errSSLClosedGraceful)
6bb54cbf
CMN
227 return stransport_error(ret);
228
ac3d33df 229 return st->owned ? git_stream_close(st->io) : 0;
6bb54cbf
CMN
230}
231
067bf5dc 232static void stransport_free(git_stream *stream)
6bb54cbf
CMN
233{
234 stransport_stream *st = (stransport_stream *) stream;
235
ac3d33df
JK
236 if (st->owned)
237 git_stream_free(st->io);
238
6bb54cbf
CMN
239 CFRelease(st->ctx);
240 if (st->der_data)
241 CFRelease(st->der_data);
242 git__free(st);
243}
244
ac3d33df
JK
245static int stransport_wrap(
246 git_stream **out,
247 git_stream *in,
248 const char *host,
249 int owned)
6bb54cbf
CMN
250{
251 stransport_stream *st;
6bb54cbf
CMN
252 OSStatus ret;
253
c25aa7cd
PP
254 GIT_ASSERT_ARG(out);
255 GIT_ASSERT_ARG(in);
256 GIT_ASSERT_ARG(host);
6bb54cbf
CMN
257
258 st = git__calloc(1, sizeof(stransport_stream));
ac3d33df 259 GIT_ERROR_CHECK_ALLOC(st);
58ca8c7e 260
ac3d33df
JK
261 st->io = in;
262 st->owned = owned;
6bb54cbf
CMN
263
264 st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
265 if (!st->ctx) {
ac3d33df 266 git_error_set(GIT_ERROR_NET, "failed to create SSL context");
b9895144 267 git__free(st);
6bb54cbf
CMN
268 return -1;
269 }
270
271 if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr ||
272 (ret = SSLSetConnection(st->ctx, st->io)) != noErr ||
b7e1c81d 273 (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr ||
65ac7ddc
CMN
274 (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr ||
275 (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr ||
6bb54cbf 276 (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) {
b9895144
PS
277 CFRelease(st->ctx);
278 git__free(st);
6bb54cbf
CMN
279 return stransport_error(ret);
280 }
281
282 st->parent.version = GIT_STREAM_VERSION;
283 st->parent.encrypted = 1;
58ca8c7e 284 st->parent.proxy_support = git_stream_supports_proxy(st->io);
6bb54cbf
CMN
285 st->parent.connect = stransport_connect;
286 st->parent.certificate = stransport_certificate;
58ca8c7e 287 st->parent.set_proxy = stransport_set_proxy;
6bb54cbf
CMN
288 st->parent.read = stransport_read;
289 st->parent.write = stransport_write;
290 st->parent.close = stransport_close;
291 st->parent.free = stransport_free;
292
293 *out = (git_stream *) st;
294 return 0;
295}
296
ac3d33df
JK
297int git_stransport_stream_wrap(
298 git_stream **out,
299 git_stream *in,
300 const char *host)
301{
302 return stransport_wrap(out, in, host, 0);
303}
304
305int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
306{
307 git_stream *stream = NULL;
308 int error;
309
c25aa7cd
PP
310 GIT_ASSERT_ARG(out);
311 GIT_ASSERT_ARG(host);
ac3d33df
JK
312
313 error = git_socket_stream_new(&stream, host, port);
314
315 if (!error)
316 error = stransport_wrap(out, stream, host, 1);
317
318 if (error < 0 && stream) {
319 git_stream_close(stream);
320 git_stream_free(stream);
321 }
322
323 return error;
324}
325
6bb54cbf 326#endif