]> git.proxmox.com Git - libgit2.git/blob - src/stransport_stream.c
644a5a7c20c2f9c913d722ad5cbd08aae8c2abec
[libgit2.git] / src / stransport_stream.c
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_SECURE_TRANSPORT
9
10 #include <CoreFoundation/CoreFoundation.h>
11 #include <Security/SecureTransport.h>
12 #include <Security/SecCertificate.h>
13
14 #include "git2/transport.h"
15
16 #include "socket_stream.h"
17
18 int stransport_error(OSStatus ret)
19 {
20 CFStringRef message;
21
22 if (ret == noErr) {
23 giterr_clear();
24 return 0;
25 }
26
27 message = SecCopyErrorMessageString(ret, NULL);
28 GITERR_CHECK_ALLOC(message);
29
30 giterr_set(GITERR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8));
31 CFRelease(message);
32 return -1;
33 }
34
35 typedef struct {
36 git_stream parent;
37 git_stream *io;
38 SSLContextRef ctx;
39 CFDataRef der_data;
40 git_cert_x509 cert_info;
41 } stransport_stream;
42
43 int stransport_connect(git_stream *stream)
44 {
45 stransport_stream *st = (stransport_stream *) stream;
46 int error;
47 SecTrustRef trust = NULL;
48 SecTrustResultType sec_res;
49 OSStatus ret;
50
51 if ((error = git_stream_connect(st->io)) < 0)
52 return error;
53
54 ret = SSLHandshake(st->ctx);
55 if (ret != errSSLServerAuthCompleted) {
56 giterr_set(GITERR_SSL, "unexpected return value from ssl handshake %d", ret);
57 return -1;
58 }
59
60 if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
61 goto on_error;
62
63 if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr)
64 goto on_error;
65
66 CFRelease(trust);
67
68 if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) {
69 giterr_set(GITERR_SSL, "internal security trust error");
70 return -1;
71 }
72
73 if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure ||
74 sec_res == kSecTrustResultFatalTrustFailure)
75 return GIT_ECERTIFICATE;
76
77 return 0;
78
79 on_error:
80 if (trust)
81 CFRelease(trust);
82
83 return stransport_error(ret);
84 }
85
86 int stransport_certificate(git_cert **out, git_stream *stream)
87 {
88 stransport_stream *st = (stransport_stream *) stream;
89 SecTrustRef trust = NULL;
90 SecCertificateRef sec_cert;
91 OSStatus ret;
92
93 if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr)
94 return stransport_error(ret);
95
96 sec_cert = SecTrustGetCertificateAtIndex(trust, 0);
97 st->der_data = SecCertificateCopyData(sec_cert);
98 CFRelease(trust);
99
100 if (st->der_data == NULL) {
101 giterr_set(GITERR_SSL, "retrieved invalid certificate data");
102 return -1;
103 }
104
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);
108
109 *out = (git_cert *)&st->cert_info;
110 return 0;
111 }
112
113 static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len)
114 {
115 git_stream *io = (git_stream *) conn;
116 ssize_t ret;
117
118 ret = git_stream_write(io, data, *len, 0);
119 if (ret < 0) {
120 *len = 0;
121 return -1;
122 }
123
124 *len = ret;
125
126 return noErr;
127 }
128
129 ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags)
130 {
131 stransport_stream *st = (stransport_stream *) stream;
132 size_t data_len, processed;
133 OSStatus ret;
134
135 GIT_UNUSED(flags);
136
137 data_len = len;
138 if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr)
139 return stransport_error(ret);
140
141 return processed;
142 }
143
144 static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len)
145 {
146 git_stream *io = (git_stream *) conn;
147 ssize_t ret;
148 size_t left, requested;
149
150 requested = left = *len;
151 do {
152 ret = git_stream_read(io, data + (requested - left), left);
153 if (ret < 0) {
154 *len = 0;
155 return -1;
156 }
157
158 left -= ret;
159 } while (left);
160
161 *len = requested;
162
163 if (ret == 0)
164 return errSSLClosedGraceful;
165
166 return noErr;
167 }
168
169 ssize_t stransport_read(git_stream *stream, void *data, size_t len)
170 {
171 stransport_stream *st = (stransport_stream *) stream;
172 size_t processed;
173 OSStatus ret;
174
175 if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr)
176 return stransport_error(ret);
177
178 return processed;
179 }
180
181 int stransport_close(git_stream *stream)
182 {
183 stransport_stream *st = (stransport_stream *) stream;
184 OSStatus ret;
185
186 if ((ret = SSLClose(st->ctx)) != noErr)
187 return stransport_error(ret);
188
189 return git_stream_close(st->io);
190 }
191
192 void stransport_free(git_stream *stream)
193 {
194 stransport_stream *st = (stransport_stream *) stream;
195
196 git_stream_free(st->io);
197 CFRelease(st->ctx);
198 if (st->der_data)
199 CFRelease(st->der_data);
200 git__free(st);
201 }
202
203 int git_stransport_stream_new(git_stream **out, const char *host, const char *port)
204 {
205 stransport_stream *st;
206 int error;
207 OSStatus ret;
208
209 assert(out && host);
210
211 st = git__calloc(1, sizeof(stransport_stream));
212 GITERR_CHECK_ALLOC(st);
213
214 if ((error = git_socket_stream_new(&st->io, host, port)) < 0){
215 git__free(st);
216 return error;
217 }
218
219 st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
220 if (!st->ctx) {
221 giterr_set(GITERR_NET, "failed to create SSL context");
222 return -1;
223 }
224
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);
231 }
232
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;
241
242 *out = (git_stream *) st;
243 return 0;
244 }
245
246 #endif