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