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