]>
Commit | Line | Data |
---|---|---|
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 | 20 | static 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 | ||
43 | typedef 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 | 52 | static 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 | |
93 | on_error: | |
94 | if (trust) | |
95 | CFRelease(trust); | |
96 | ||
97 | return stransport_error(ret); | |
6bb54cbf CMN |
98 | } |
99 | ||
067bf5dc | 100 | static 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 | 127 | static 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 |
148 | static 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 | 158 | static 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 |
183 | static 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 | 208 | static 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 | 220 | static 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 | 232 | static 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 |
245 | static 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 |
297 | int 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 | ||
305 | int 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 |