2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "streams/stransport.h"
10 #ifdef GIT_SECURE_TRANSPORT
12 #include <CoreFoundation/CoreFoundation.h>
13 #include <Security/SecureTransport.h>
14 #include <Security/SecCertificate.h>
16 #include "git2/transport.h"
18 #include "streams/socket.h"
20 static int stransport_error(OSStatus ret
)
24 if (ret
== noErr
|| ret
== errSSLClosedGraceful
) {
30 message
= SecCopyErrorMessageString(ret
, NULL
);
31 GIT_ERROR_CHECK_ALLOC(message
);
33 git_error_set(GIT_ERROR_NET
, "SecureTransport error: %s", CFStringGetCStringPtr(message
, kCFStringEncodingUTF8
));
36 git_error_set(GIT_ERROR_NET
, "SecureTransport error: OSStatus %d", (unsigned int)ret
);
49 git_cert_x509 cert_info
;
52 static int stransport_connect(git_stream
*stream
)
54 stransport_stream
*st
= (stransport_stream
*) stream
;
56 SecTrustRef trust
= NULL
;
57 SecTrustResultType sec_res
;
60 if (st
->owned
&& (error
= git_stream_connect(st
->io
)) < 0)
63 ret
= SSLHandshake(st
->ctx
);
64 if (ret
!= errSSLServerAuthCompleted
) {
65 git_error_set(GIT_ERROR_SSL
, "unexpected return value from ssl handshake %d", (int)ret
);
69 if ((ret
= SSLCopyPeerTrust(st
->ctx
, &trust
)) != noErr
)
73 return GIT_ECERTIFICATE
;
75 if ((ret
= SecTrustEvaluate(trust
, &sec_res
)) != noErr
)
80 if (sec_res
== kSecTrustResultInvalid
|| sec_res
== kSecTrustResultOtherError
) {
81 git_error_set(GIT_ERROR_SSL
, "internal security trust error");
85 if (sec_res
== kSecTrustResultDeny
|| sec_res
== kSecTrustResultRecoverableTrustFailure
||
86 sec_res
== kSecTrustResultFatalTrustFailure
) {
87 git_error_set(GIT_ERROR_SSL
, "untrusted connection error");
88 return GIT_ECERTIFICATE
;
97 return stransport_error(ret
);
100 static int stransport_certificate(git_cert
**out
, git_stream
*stream
)
102 stransport_stream
*st
= (stransport_stream
*) stream
;
103 SecTrustRef trust
= NULL
;
104 SecCertificateRef sec_cert
;
107 if ((ret
= SSLCopyPeerTrust(st
->ctx
, &trust
)) != noErr
)
108 return stransport_error(ret
);
110 sec_cert
= SecTrustGetCertificateAtIndex(trust
, 0);
111 st
->der_data
= SecCertificateCopyData(sec_cert
);
114 if (st
->der_data
== NULL
) {
115 git_error_set(GIT_ERROR_SSL
, "retrieved invalid certificate data");
119 st
->cert_info
.parent
.cert_type
= GIT_CERT_X509
;
120 st
->cert_info
.data
= (void *) CFDataGetBytePtr(st
->der_data
);
121 st
->cert_info
.len
= CFDataGetLength(st
->der_data
);
123 *out
= (git_cert
*)&st
->cert_info
;
127 static int stransport_set_proxy(
129 const git_proxy_options
*proxy_opts
)
131 stransport_stream
*st
= (stransport_stream
*) stream
;
133 return git_stream_set_proxy(st
->io
, proxy_opts
);
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.
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).
145 * Libgit2 streams happen to already have this very behavior so this is just
148 static OSStatus
write_cb(SSLConnectionRef conn
, const void *data
, size_t *len
)
150 git_stream
*io
= (git_stream
*) conn
;
152 if (git_stream__write_full(io
, data
, *len
, 0) < 0)
153 return -36; /* "ioErr" from MacErrors.h which is not available on iOS */
158 static ssize_t
stransport_write(git_stream
*stream
, const char *data
, size_t len
, int flags
)
160 stransport_stream
*st
= (stransport_stream
*) stream
;
161 size_t data_len
, processed
;
166 data_len
= min(len
, SSIZE_MAX
);
167 if ((ret
= SSLWrite(st
->ctx
, data
, data_len
, &processed
)) != noErr
)
168 return stransport_error(ret
);
170 GIT_ASSERT(processed
< SSIZE_MAX
);
171 return (ssize_t
)processed
;
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.
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).
183 static OSStatus
read_cb(SSLConnectionRef conn
, void *data
, size_t *len
)
185 git_stream
*io
= (git_stream
*) conn
;
186 OSStatus error
= noErr
;
191 ret
= git_stream_read(io
, data
+ off
, *len
- off
);
193 error
= -36; /* "ioErr" from MacErrors.h which is not available on iOS */
197 error
= errSSLClosedGraceful
;
202 } while (off
< *len
);
208 static ssize_t
stransport_read(git_stream
*stream
, void *data
, size_t len
)
210 stransport_stream
*st
= (stransport_stream
*) stream
;
214 if ((ret
= SSLRead(st
->ctx
, data
, len
, &processed
)) != noErr
)
215 return stransport_error(ret
);
220 static int stransport_close(git_stream
*stream
)
222 stransport_stream
*st
= (stransport_stream
*) stream
;
225 ret
= SSLClose(st
->ctx
);
226 if (ret
!= noErr
&& ret
!= errSSLClosedGraceful
)
227 return stransport_error(ret
);
229 return st
->owned
? git_stream_close(st
->io
) : 0;
232 static void stransport_free(git_stream
*stream
)
234 stransport_stream
*st
= (stransport_stream
*) stream
;
237 git_stream_free(st
->io
);
241 CFRelease(st
->der_data
);
245 static int stransport_wrap(
251 stransport_stream
*st
;
256 GIT_ASSERT_ARG(host
);
258 st
= git__calloc(1, sizeof(stransport_stream
));
259 GIT_ERROR_CHECK_ALLOC(st
);
264 st
->ctx
= SSLCreateContext(NULL
, kSSLClientSide
, kSSLStreamType
);
266 git_error_set(GIT_ERROR_NET
, "failed to create SSL context");
271 if ((ret
= SSLSetIOFuncs(st
->ctx
, read_cb
, write_cb
)) != noErr
||
272 (ret
= SSLSetConnection(st
->ctx
, st
->io
)) != noErr
||
273 (ret
= SSLSetSessionOption(st
->ctx
, kSSLSessionOptionBreakOnServerAuth
, true)) != noErr
||
274 (ret
= SSLSetProtocolVersionMin(st
->ctx
, kTLSProtocol1
)) != noErr
||
275 (ret
= SSLSetProtocolVersionMax(st
->ctx
, kTLSProtocol12
)) != noErr
||
276 (ret
= SSLSetPeerDomainName(st
->ctx
, host
, strlen(host
))) != noErr
) {
279 return stransport_error(ret
);
282 st
->parent
.version
= GIT_STREAM_VERSION
;
283 st
->parent
.encrypted
= 1;
284 st
->parent
.proxy_support
= git_stream_supports_proxy(st
->io
);
285 st
->parent
.connect
= stransport_connect
;
286 st
->parent
.certificate
= stransport_certificate
;
287 st
->parent
.set_proxy
= stransport_set_proxy
;
288 st
->parent
.read
= stransport_read
;
289 st
->parent
.write
= stransport_write
;
290 st
->parent
.close
= stransport_close
;
291 st
->parent
.free
= stransport_free
;
293 *out
= (git_stream
*) st
;
297 int git_stransport_stream_wrap(
302 return stransport_wrap(out
, in
, host
, 0);
305 int git_stransport_stream_new(git_stream
**out
, const char *host
, const char *port
)
307 git_stream
*stream
= NULL
;
311 GIT_ASSERT_ARG(host
);
313 error
= git_socket_stream_new(&stream
, host
, port
);
316 error
= stransport_wrap(out
, stream
, host
, 1);
318 if (error
< 0 && stream
) {
319 git_stream_close(stream
);
320 git_stream_free(stream
);