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