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 #ifdef GIT_SECURE_TRANSPORT
10 #include <CoreFoundation/CoreFoundation.h>
11 #include <Security/SecureTransport.h>
12 #include <Security/SecCertificate.h>
14 #include "git2/transport.h"
16 #include "socket_stream.h"
18 int stransport_error(OSStatus ret
)
27 message
= SecCopyErrorMessageString(ret
, NULL
);
28 GITERR_CHECK_ALLOC(message
);
30 giterr_set(GITERR_NET
, "SecureTransport error: %s", CFStringGetCStringPtr(message
, kCFStringEncodingUTF8
));
40 git_cert_x509 cert_info
;
43 int stransport_connect(git_stream
*stream
)
45 stransport_stream
*st
= (stransport_stream
*) stream
;
47 SecTrustRef trust
= NULL
;
48 SecTrustResultType sec_res
;
51 if ((error
= git_stream_connect(st
->io
)) < 0)
54 ret
= SSLHandshake(st
->ctx
);
55 if (ret
!= errSSLServerAuthCompleted
) {
56 giterr_set(GITERR_SSL
, "unexpected return value from ssl handshake %d", ret
);
60 if ((ret
= SSLCopyPeerTrust(st
->ctx
, &trust
)) != noErr
)
63 if ((ret
= SecTrustEvaluate(trust
, &sec_res
)) != noErr
)
68 if (sec_res
== kSecTrustResultInvalid
|| sec_res
== kSecTrustResultOtherError
) {
69 giterr_set(GITERR_SSL
, "internal security trust error");
73 if (sec_res
== kSecTrustResultDeny
|| sec_res
== kSecTrustResultRecoverableTrustFailure
||
74 sec_res
== kSecTrustResultFatalTrustFailure
)
75 return GIT_ECERTIFICATE
;
83 return stransport_error(ret
);
86 int stransport_certificate(git_cert
**out
, git_stream
*stream
)
88 stransport_stream
*st
= (stransport_stream
*) stream
;
89 SecTrustRef trust
= NULL
;
90 SecCertificateRef sec_cert
;
93 if ((ret
= SSLCopyPeerTrust(st
->ctx
, &trust
)) != noErr
)
94 return stransport_error(ret
);
96 sec_cert
= SecTrustGetCertificateAtIndex(trust
, 0);
97 st
->der_data
= SecCertificateCopyData(sec_cert
);
100 if (st
->der_data
== NULL
) {
101 giterr_set(GITERR_SSL
, "retrieved invalid certificate data");
105 st
->cert_info
.cert_type
= GIT_CERT_X509
;
106 st
->cert_info
.data
= (void *) CFDataGetBytePtr(st
->der_data
);
107 st
->cert_info
.len
= CFDataGetLength(st
->der_data
);
109 *out
= (git_cert
*)&st
->cert_info
;
113 static OSStatus
write_cb(SSLConnectionRef conn
, const void *data
, size_t *len
)
115 git_stream
*io
= (git_stream
*) conn
;
118 ret
= git_stream_write(io
, data
, *len
, 0);
129 ssize_t
stransport_write(git_stream
*stream
, const char *data
, size_t len
, int flags
)
131 stransport_stream
*st
= (stransport_stream
*) stream
;
132 size_t data_len
, processed
;
138 if ((ret
= SSLWrite(st
->ctx
, data
, data_len
, &processed
)) != noErr
)
139 return stransport_error(ret
);
144 static OSStatus
read_cb(SSLConnectionRef conn
, void *data
, size_t *len
)
146 git_stream
*io
= (git_stream
*) conn
;
148 size_t left
, requested
;
150 requested
= left
= *len
;
152 ret
= git_stream_read(io
, data
+ (requested
- left
), left
);
164 return errSSLClosedGraceful
;
169 ssize_t
stransport_read(git_stream
*stream
, void *data
, size_t len
)
171 stransport_stream
*st
= (stransport_stream
*) stream
;
175 if ((ret
= SSLRead(st
->ctx
, data
, len
, &processed
)) != noErr
)
176 return stransport_error(ret
);
181 int stransport_close(git_stream
*stream
)
183 stransport_stream
*st
= (stransport_stream
*) stream
;
186 if ((ret
= SSLClose(st
->ctx
)) != noErr
)
187 return stransport_error(ret
);
189 return git_stream_close(st
->io
);
192 void stransport_free(git_stream
*stream
)
194 stransport_stream
*st
= (stransport_stream
*) stream
;
196 git_stream_free(st
->io
);
199 CFRelease(st
->der_data
);
203 int git_stransport_stream_new(git_stream
**out
, const char *host
, const char *port
)
205 stransport_stream
*st
;
211 st
= git__calloc(1, sizeof(stransport_stream
));
212 GITERR_CHECK_ALLOC(st
);
214 if ((error
= git_socket_stream_new(&st
->io
, host
, port
)) < 0){
219 st
->ctx
= SSLCreateContext(NULL
, kSSLClientSide
, kSSLStreamType
);
221 giterr_set(GITERR_NET
, "failed to create SSL context");
225 if ((ret
= SSLSetIOFuncs(st
->ctx
, read_cb
, write_cb
)) != noErr
||
226 (ret
= SSLSetConnection(st
->ctx
, st
->io
)) != noErr
||
227 (ret
= SSLSetSessionOption(st
->ctx
, kSSLSessionOptionBreakOnServerAuth
, true)) != noErr
||
228 (ret
= SSLSetPeerDomainName(st
->ctx
, host
, strlen(host
))) != noErr
) {
229 git_stream_free((git_stream
*)st
);
230 return stransport_error(ret
);
233 st
->parent
.version
= GIT_STREAM_VERSION
;
234 st
->parent
.encrypted
= 1;
235 st
->parent
.connect
= stransport_connect
;
236 st
->parent
.certificate
= stransport_certificate
;
237 st
->parent
.read
= stransport_read
;
238 st
->parent
.write
= stransport_write
;
239 st
->parent
.close
= stransport_close
;
240 st
->parent
.free
= stransport_free
;
242 *out
= (git_stream
*) st
;