blob: bc8a32069e27d3a5c9adbf68dc190b24aff39bae
1 | /* |
2 | * Copyright (c) 2015 Rodger Combs |
3 | * |
4 | * This file is part of FFmpeg. |
5 | * |
6 | * FFmpeg is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public License |
8 | * as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * FFmpeg is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public License |
17 | * along with FFmpeg; if not, write to the Free Software * Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | #include <errno.h> |
22 | |
23 | |
24 | #include "avformat.h" |
25 | #include "avio_internal.h" |
26 | #include "internal.h" |
27 | #include "network.h" |
28 | #include "os_support.h" |
29 | #include "url.h" |
30 | #include "tls.h" |
31 | #include "libavcodec/internal.h" |
32 | #include "libavutil/avstring.h" |
33 | #include "libavutil/opt.h" |
34 | #include "libavutil/parseutils.h" |
35 | |
36 | #include <Security/Security.h> |
37 | #include <Security/SecureTransport.h> |
38 | #include <CoreFoundation/CoreFoundation.h> |
39 | |
40 | // We use a private API call here; it's good enough for WebKit. |
41 | SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey); |
42 | #define ioErr -36 |
43 | |
44 | typedef struct TLSContext { |
45 | const AVClass *class; |
46 | TLSShared tls_shared; |
47 | SSLContextRef ssl_context; |
48 | CFArrayRef ca_array; |
49 | int lastErr; |
50 | } TLSContext; |
51 | |
52 | static int print_tls_error(URLContext *h, int ret) |
53 | { |
54 | TLSContext *c = h->priv_data; |
55 | switch (ret) { |
56 | case errSSLWouldBlock: |
57 | break; |
58 | case errSSLXCertChainInvalid: |
59 | av_log(h, AV_LOG_ERROR, "Invalid certificate chain\n"); |
60 | return AVERROR(EIO); |
61 | case ioErr: |
62 | return c->lastErr; |
63 | default: |
64 | av_log(h, AV_LOG_ERROR, "IO Error: %i\n", ret); |
65 | return AVERROR(EIO); |
66 | } |
67 | return AVERROR(EIO); |
68 | } |
69 | |
70 | static int import_pem(URLContext *h, char *path, CFArrayRef *array) |
71 | { |
72 | AVIOContext *s = NULL; |
73 | CFDataRef data = NULL; |
74 | int64_t ret = 0; |
75 | char *buf = NULL; |
76 | SecExternalFormat format = kSecFormatPEMSequence; |
77 | SecExternalFormat type = kSecItemTypeAggregate; |
78 | CFStringRef pathStr = CFStringCreateWithCString(NULL, path, 0x08000100); |
79 | if (!pathStr) { |
80 | ret = AVERROR(ENOMEM); |
81 | goto end; |
82 | } |
83 | |
84 | if ((ret = ffio_open_whitelist(&s, path, AVIO_FLAG_READ, |
85 | &h->interrupt_callback, NULL, |
86 | h->protocol_whitelist, h->protocol_blacklist)) < 0) |
87 | goto end; |
88 | |
89 | if ((ret = avio_size(s)) < 0) |
90 | goto end; |
91 | |
92 | if (ret == 0) { |
93 | ret = AVERROR_INVALIDDATA; |
94 | goto end; |
95 | } |
96 | |
97 | if (!(buf = av_malloc(ret))) { |
98 | ret = AVERROR(ENOMEM); |
99 | goto end; |
100 | } |
101 | |
102 | if ((ret = avio_read(s, buf, ret)) < 0) |
103 | goto end; |
104 | |
105 | data = CFDataCreate(kCFAllocatorDefault, buf, ret); |
106 | |
107 | if (SecItemImport(data, pathStr, &format, &type, |
108 | 0, NULL, NULL, array) != noErr || !array) { |
109 | ret = AVERROR_UNKNOWN; |
110 | goto end; |
111 | } |
112 | |
113 | if (CFArrayGetCount(*array) == 0) { |
114 | ret = AVERROR_INVALIDDATA; |
115 | goto end; |
116 | } |
117 | |
118 | end: |
119 | av_free(buf); |
120 | if (pathStr) |
121 | CFRelease(pathStr); |
122 | if (data) |
123 | CFRelease(data); |
124 | if (s) |
125 | avio_close(s); |
126 | return ret; |
127 | } |
128 | |
129 | static int load_ca(URLContext *h) |
130 | { |
131 | TLSContext *c = h->priv_data; |
132 | int ret = 0; |
133 | CFArrayRef array = NULL; |
134 | |
135 | if ((ret = import_pem(h, c->tls_shared.ca_file, &array)) < 0) |
136 | goto end; |
137 | |
138 | if (!(c->ca_array = CFRetain(array))) { |
139 | ret = AVERROR(ENOMEM); |
140 | goto end; |
141 | } |
142 | |
143 | end: |
144 | if (array) |
145 | CFRelease(array); |
146 | return ret; |
147 | } |
148 | |
149 | static int load_cert(URLContext *h) |
150 | { |
151 | TLSContext *c = h->priv_data; |
152 | int ret = 0; |
153 | CFArrayRef certArray = NULL; |
154 | CFArrayRef keyArray = NULL; |
155 | SecIdentityRef id = NULL; |
156 | CFMutableArrayRef outArray = NULL; |
157 | |
158 | if ((ret = import_pem(h, c->tls_shared.cert_file, &certArray)) < 0) |
159 | goto end; |
160 | |
161 | if ((ret = import_pem(h, c->tls_shared.key_file, &keyArray)) < 0) |
162 | goto end; |
163 | |
164 | if (!(id = SecIdentityCreate(kCFAllocatorDefault, |
165 | (SecCertificateRef)CFArrayGetValueAtIndex(certArray, 0), |
166 | (SecKeyRef)CFArrayGetValueAtIndex(keyArray, 0)))) { |
167 | ret = AVERROR_UNKNOWN; |
168 | goto end; |
169 | } |
170 | |
171 | if (!(outArray = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, certArray))) { |
172 | ret = AVERROR(ENOMEM); |
173 | goto end; |
174 | } |
175 | |
176 | CFArraySetValueAtIndex(outArray, 0, id); |
177 | |
178 | SSLSetCertificate(c->ssl_context, outArray); |
179 | |
180 | end: |
181 | if (certArray) |
182 | CFRelease(certArray); |
183 | if (keyArray) |
184 | CFRelease(keyArray); |
185 | if (outArray) |
186 | CFRelease(outArray); |
187 | if (id) |
188 | CFRelease(id); |
189 | return ret; |
190 | } |
191 | |
192 | static OSStatus tls_read_cb(SSLConnectionRef connection, void *data, size_t *dataLength) |
193 | { |
194 | URLContext *h = (URLContext*)connection; |
195 | TLSContext *c = h->priv_data; |
196 | int read = ffurl_read_complete(c->tls_shared.tcp, data, *dataLength); |
197 | if (read <= 0) { |
198 | *dataLength = 0; |
199 | switch(AVUNERROR(read)) { |
200 | case ENOENT: |
201 | case 0: |
202 | return errSSLClosedGraceful; |
203 | case ECONNRESET: |
204 | return errSSLClosedAbort; |
205 | case EAGAIN: |
206 | return errSSLWouldBlock; |
207 | default: |
208 | c->lastErr = read; |
209 | return ioErr; |
210 | } |
211 | } else { |
212 | *dataLength = read; |
213 | return noErr; |
214 | } |
215 | } |
216 | |
217 | static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size_t *dataLength) |
218 | { |
219 | URLContext *h = (URLContext*)connection; |
220 | TLSContext *c = h->priv_data; |
221 | int written = ffurl_write(c->tls_shared.tcp, data, *dataLength); |
222 | if (written <= 0) { |
223 | *dataLength = 0; |
224 | switch(AVUNERROR(written)) { |
225 | case EAGAIN: |
226 | return errSSLWouldBlock; |
227 | default: |
228 | c->lastErr = written; |
229 | return ioErr; |
230 | } |
231 | } else { |
232 | *dataLength = written; |
233 | return noErr; |
234 | } |
235 | } |
236 | |
237 | static int tls_close(URLContext *h) |
238 | { |
239 | TLSContext *c = h->priv_data; |
240 | if (c->ssl_context) { |
241 | SSLClose(c->ssl_context); |
242 | CFRelease(c->ssl_context); |
243 | } |
244 | if (c->ca_array) |
245 | CFRelease(c->ca_array); |
246 | if (c->tls_shared.tcp) |
247 | ffurl_close(c->tls_shared.tcp); |
248 | return 0; |
249 | } |
250 | |
251 | #define CHECK_ERROR(func, ...) do { \ |
252 | OSStatus status = func(__VA_ARGS__); \ |
253 | if (status != noErr) { \ |
254 | ret = AVERROR_UNKNOWN; \ |
255 | av_log(h, AV_LOG_ERROR, #func ": Error %i\n", (int)status); \ |
256 | goto fail; \ |
257 | } \ |
258 | } while (0) |
259 | |
260 | static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options) |
261 | { |
262 | TLSContext *c = h->priv_data; |
263 | TLSShared *s = &c->tls_shared; |
264 | int ret; |
265 | |
266 | if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0) |
267 | goto fail; |
268 | |
269 | c->ssl_context = SSLCreateContext(NULL, s->listen ? kSSLServerSide : kSSLClientSide, kSSLStreamType); |
270 | if (!c->ssl_context) { |
271 | av_log(h, AV_LOG_ERROR, "Unable to create SSL context\n"); |
272 | ret = AVERROR(ENOMEM); |
273 | goto fail; |
274 | } |
275 | if (s->ca_file) { |
276 | if ((ret = load_ca(h)) < 0) |
277 | goto fail; |
278 | } |
279 | if (s->ca_file || !s->verify) |
280 | CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true); |
281 | if (s->cert_file) |
282 | if ((ret = load_cert(h)) < 0) |
283 | goto fail; |
284 | CHECK_ERROR(SSLSetPeerDomainName, c->ssl_context, s->host, strlen(s->host)); |
285 | CHECK_ERROR(SSLSetIOFuncs, c->ssl_context, tls_read_cb, tls_write_cb); |
286 | CHECK_ERROR(SSLSetConnection, c->ssl_context, h); |
287 | while (1) { |
288 | OSStatus status = SSLHandshake(c->ssl_context); |
289 | if (status == errSSLServerAuthCompleted) { |
290 | SecTrustRef peerTrust; |
291 | SecTrustResultType trustResult; |
292 | if (!s->verify) |
293 | continue; |
294 | |
295 | if (SSLCopyPeerTrust(c->ssl_context, &peerTrust) != noErr) { |
296 | ret = AVERROR(ENOMEM); |
297 | goto fail; |
298 | } |
299 | |
300 | if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr) { |
301 | ret = AVERROR_UNKNOWN; |
302 | goto fail; |
303 | } |
304 | |
305 | if (SecTrustEvaluate(peerTrust, &trustResult) != noErr) { |
306 | ret = AVERROR_UNKNOWN; |
307 | goto fail; |
308 | } |
309 | |
310 | if (trustResult == kSecTrustResultProceed || |
311 | trustResult == kSecTrustResultUnspecified) { |
312 | // certificate is trusted |
313 | status = errSSLWouldBlock; // so we call SSLHandshake again |
314 | } else if (trustResult == kSecTrustResultRecoverableTrustFailure) { |
315 | // not trusted, for some reason other than being expired |
316 | status = errSSLXCertChainInvalid; |
317 | } else { |
318 | // cannot use this certificate (fatal) |
319 | status = errSSLBadCert; |
320 | } |
321 | |
322 | if (peerTrust) |
323 | CFRelease(peerTrust); |
324 | } |
325 | if (status == noErr) |
326 | break; |
327 | |
328 | av_log(h, AV_LOG_ERROR, "Unable to negotiate TLS/SSL session: %i\n", (int)status); |
329 | ret = AVERROR(EIO); |
330 | goto fail; |
331 | } |
332 | |
333 | return 0; |
334 | fail: |
335 | tls_close(h); |
336 | return ret; |
337 | } |
338 | |
339 | static int map_ssl_error(OSStatus status, size_t processed) |
340 | { |
341 | switch (status) { |
342 | case noErr: |
343 | return processed; |
344 | case errSSLClosedGraceful: |
345 | case errSSLClosedNoNotify: |
346 | return 0; |
347 | default: |
348 | return (int)status; |
349 | } |
350 | } |
351 | |
352 | static int tls_read(URLContext *h, uint8_t *buf, int size) |
353 | { |
354 | TLSContext *c = h->priv_data; |
355 | size_t processed = 0; |
356 | int ret = SSLRead(c->ssl_context, buf, size, &processed); |
357 | ret = map_ssl_error(ret, processed); |
358 | if (ret > 0) |
359 | return ret; |
360 | if (ret == 0) |
361 | return AVERROR_EOF; |
362 | return print_tls_error(h, ret); |
363 | } |
364 | |
365 | static int tls_write(URLContext *h, const uint8_t *buf, int size) |
366 | { |
367 | TLSContext *c = h->priv_data; |
368 | size_t processed = 0; |
369 | int ret = SSLWrite(c->ssl_context, buf, size, &processed); |
370 | ret = map_ssl_error(ret, processed); |
371 | if (ret > 0) |
372 | return ret; |
373 | if (ret == 0) |
374 | return AVERROR_EOF; |
375 | return print_tls_error(h, ret); |
376 | } |
377 | |
378 | static int tls_get_file_handle(URLContext *h) |
379 | { |
380 | TLSContext *c = h->priv_data; |
381 | return ffurl_get_file_handle(c->tls_shared.tcp); |
382 | } |
383 | |
384 | static const AVOption options[] = { |
385 | TLS_COMMON_OPTIONS(TLSContext, tls_shared), |
386 | { NULL } |
387 | }; |
388 | |
389 | static const AVClass tls_class = { |
390 | .class_name = "tls", |
391 | .item_name = av_default_item_name, |
392 | .option = options, |
393 | .version = LIBAVUTIL_VERSION_INT, |
394 | }; |
395 | |
396 | const URLProtocol ff_tls_securetransport_protocol = { |
397 | .name = "tls", |
398 | .url_open2 = tls_open, |
399 | .url_read = tls_read, |
400 | .url_write = tls_write, |
401 | .url_close = tls_close, |
402 | .url_get_file_handle = tls_get_file_handle, |
403 | .priv_data_size = sizeof(TLSContext), |
404 | .flags = URL_PROTOCOL_FLAG_NETWORK, |
405 | .priv_data_class = &tls_class, |
406 | }; |
407 |