]> git.sesse.net Git - vlc/blob - modules/misc/securetransport.c
securetransport: add missing error checking
[vlc] / modules / misc / securetransport.c
1 /*****************************************************************************
2  * securetransport.c
3  *****************************************************************************
4  * Copyright (C) 2013 David Fuhrmann
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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 Ă–esser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_tls.h>
32 #include <vlc_dialog.h>
33
34 #include <Security/Security.h>
35 #include <Security/SecureTransport.h>
36 #include <TargetConditionals.h>
37
38 /* From MacErrors.h (cannot be included because it isn't present in iOS: */
39 #ifndef ioErr
40 # define ioErr -36
41 #endif
42
43 /*****************************************************************************
44  * Module descriptor
45  *****************************************************************************/
46 static int  OpenClient  (vlc_tls_creds_t *);
47 static void CloseClient (vlc_tls_creds_t *);
48
49 vlc_module_begin ()
50     set_description(N_("TLS support for OS X and iOS"))
51     set_capability("tls client", 2)
52     set_callbacks(OpenClient, CloseClient)
53     set_category(CAT_ADVANCED)
54     set_subcategory(SUBCAT_ADVANCED_NETWORK)
55 vlc_module_end ()
56
57
58 #define cfKeyHost CFSTR("host")
59 #define cfKeyCertificate CFSTR("certificate")
60
61 struct vlc_tls_creds_sys
62 {
63     CFMutableArrayRef whitelist;
64 };
65
66 struct vlc_tls_sys {
67     SSLContextRef p_context;
68     vlc_tls_creds_sys_t *p_cred;
69     size_t i_send_buffered_bytes;
70     int i_fd;
71
72     bool b_blocking_send;
73     bool b_handshaked;
74 };
75
76 static int st_Error (vlc_tls_t *obj, int val)
77 {
78     switch (val)
79     {
80         /* peer performed shutdown */
81         case errSSLClosedNoNotify:
82         case errSSLClosedGraceful:
83             msg_Dbg(obj, "Got shutdown notification");
84             return 0;
85
86         case errSSLWouldBlock:
87             errno = EAGAIN;
88             break;
89
90         default:
91             msg_Err (obj, "Found error %d", val);
92             errno = ECONNRESET;
93     }
94     return -1;
95 }
96
97 /*
98  * Read function called by secure transport for socket read.
99  *
100  * Function is based on Apples SSLSample sample code.
101  */
102 static OSStatus st_SocketReadFunc (SSLConnectionRef connection,
103                                    void *data,
104                                    size_t *dataLength) {
105
106     vlc_tls_t *session = (vlc_tls_t *)connection;
107     vlc_tls_sys_t *sys = session->sys;
108
109     size_t bytesToGo = *dataLength;
110     size_t initLen = bytesToGo;
111     UInt8 *currData = (UInt8 *)data;
112     OSStatus retValue = noErr;
113     ssize_t val;
114
115     for(;;) {
116         val = read(sys->i_fd, currData, bytesToGo);
117         if (val <= 0) {
118             if(val == 0) {
119                 msg_Dbg(session, "found eof");
120                 retValue = errSSLClosedGraceful;
121             } else { /* do the switch */
122                 switch(errno) {
123                     case ENOENT:
124                         /* connection closed */
125                         retValue = errSSLClosedGraceful;
126                         break;
127                     case ECONNRESET:
128                         retValue = errSSLClosedAbort;
129                         break;
130                     case EAGAIN:
131                         retValue = errSSLWouldBlock;
132                         sys->b_blocking_send = false;
133                         break;
134                     default:
135                         msg_Err(session, "try to read %d bytes, got error %d",
136                                 (int)bytesToGo, errno);
137                         retValue = ioErr;
138                         break;
139                 }
140             }
141             break;
142         } else {
143             bytesToGo -= val;
144             currData += val;
145         }
146
147         if(bytesToGo == 0) {
148             /* filled buffer with incoming data, done */
149             break;
150         }
151     }
152     *dataLength = initLen - bytesToGo;
153
154     return retValue;
155 }
156
157 /*
158  * Write function called by secure transport for socket read.
159  *
160  * Function is based on Apples SSLSample sample code.
161  */
162 static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
163                                     const void *data,
164                                     size_t *dataLength) {
165
166     vlc_tls_t *session = (vlc_tls_t *)connection;
167     vlc_tls_sys_t *sys = session->sys;
168
169     size_t bytesSent = 0;
170     size_t dataLen = *dataLength;
171     OSStatus retValue = noErr;
172     ssize_t val;
173
174     do {
175         val = write(sys->i_fd, (char *)data + bytesSent, dataLen - bytesSent);
176     } while (val >= 0 && (bytesSent += val) < dataLen);
177
178     if(val < 0) {
179         if(errno == EAGAIN) {
180             retValue = errSSLWouldBlock;
181             sys->b_blocking_send = true;
182         } else {
183             msg_Err(session, "error while writing: %d", errno);
184             retValue = ioErr;
185         }
186     }
187
188     *dataLength = bytesSent;
189     return retValue;
190 }
191
192 static int st_validateServerCertificate (vlc_tls_t *session, const char *hostname) {
193
194     int result = -1;
195     vlc_tls_sys_t *sys = session->sys;
196     SecCertificateRef leaf_cert = NULL;
197
198     SecTrustRef trust = NULL;
199     OSStatus ret = SSLCopyPeerTrust (sys->p_context, &trust);
200     if (ret != noErr || trust == NULL) {
201         msg_Err(session, "error getting certifictate chain");
202         return -1;
203     }
204
205     CFStringRef cfHostname = CFStringCreateWithCString(kCFAllocatorDefault,
206                                                        hostname,
207                                                        kCFStringEncodingUTF8);
208
209
210     /* enable default root / anchor certificates */
211     ret = SecTrustSetAnchorCertificates (trust, NULL);
212     if (ret != noErr) {
213         msg_Err(session, "error setting anchor certificates");
214         result = -1;
215         goto out;
216     }
217
218     SecTrustResultType trust_eval_result = 0;
219
220     ret = SecTrustEvaluate(trust, &trust_eval_result);
221     if(ret != noErr) {
222         msg_Err(session, "error calling SecTrustEvaluate");
223         result = -1;
224         goto out;
225     }
226
227     switch (trust_eval_result) {
228         case kSecTrustResultUnspecified:
229         case kSecTrustResultProceed:
230             msg_Dbg(session, "cerfificate verification successful, result is %d", trust_eval_result);
231             result = 0;
232             goto out;
233
234         case kSecTrustResultRecoverableTrustFailure:
235         case kSecTrustResultDeny:
236         default:
237             msg_Warn(session, "cerfificate verification failed, result is %d", trust_eval_result);
238     }
239
240     /* get leaf certificate */
241     /* SSLCopyPeerCertificates is only available on OSX 10.5 or later */
242 #if !TARGET_OS_IPHONE
243     CFArrayRef cert_chain = NULL;
244     ret = SSLCopyPeerCertificates (sys->p_context, &cert_chain);
245     if (ret != noErr || !cert_chain) {
246         result = -1;
247         goto out;
248     }
249
250     if (CFArrayGetCount (cert_chain) == 0) {
251         CFRelease (cert_chain);
252         result = -1;
253         goto out;
254     }
255
256     leaf_cert = (SecCertificateRef)CFArrayGetValueAtIndex (cert_chain, 0);
257     CFRetain (leaf_cert);
258     CFRelease (cert_chain);
259 #else
260     /* SecTrustGetCertificateAtIndex is only available on 10.7 or iOS */
261     if (SecTrustGetCertificateCount (trust) == 0) {
262         result = -1;
263         goto out;
264     }
265
266     leaf_cert = SecTrustGetCertificateAtIndex (trust, 0);
267     CFRetain (leaf_cert);
268 #endif
269
270
271     /* check if leaf already accepted */
272     CFIndex max = CFArrayGetCount (sys->p_cred->whitelist);
273     for (CFIndex i = 0; i < max; ++i) {
274         CFDictionaryRef dict = CFArrayGetValueAtIndex (sys->p_cred->whitelist, i);
275         CFStringRef knownHost = (CFStringRef)CFDictionaryGetValue (dict, cfKeyHost);
276         SecCertificateRef knownCert = (SecCertificateRef)CFDictionaryGetValue (dict, cfKeyCertificate);
277
278         if (!knownHost || !knownCert)
279             continue;
280
281         if (CFEqual (knownHost, cfHostname) && CFEqual (knownCert, leaf_cert)) {
282             msg_Warn(session, "certificate already accepted, continuing");
283             result = 0;
284             goto out;
285         }
286     }
287
288     /* We do not show more certificate details yet because there is no proper API to get
289        a summary of the certificate. SecCertificateCopySubjectSummary is the only method
290        available on iOS and 10.6. More promising API functions such as
291        SecCertificateCopyLongDescription also print out the subject only, more or less.
292        But only showing the certificate subject is of no real help for the user.
293        We could use SecCertificateCopyValues, but then we need to parse all OID values for
294        ourself. This is too mad for just printing information the user will never check
295        anyway.
296      */
297
298     const char *msg = N_("You attempted to reach %s. "
299              "However the security certificate presented by the server "
300              "is unknown and could not be authenticated by any trusted "
301              "Certification Authority. "
302              "This problem may be caused by a configuration error "
303              "or an attempt to breach your security or your privacy.\n\n"
304              "If in doubt, abort now.\n");
305     int answer = dialog_Question (session, _("Insecure site"), vlc_gettext (msg),
306                                   _("Abort"), _("Accept certificate temporarily"), NULL, hostname);
307
308     if(answer == 2) {
309         msg_Warn(session, "Proceeding despite of failed certificate validation");
310
311         /* save leaf certificate in whitelist */
312         const void *keys[] = {cfKeyHost, cfKeyCertificate};
313         const void *values[] = {cfHostname, leaf_cert};
314         CFDictionaryRef dict = CFDictionaryCreate (kCFAllocatorDefault,
315                                                    keys, values, 2,
316                                                    &kCFTypeDictionaryKeyCallBacks,
317                                                    &kCFTypeDictionaryValueCallBacks);
318         if(!dict) {
319             msg_Err (session, "error creating dict");
320             result = -1;
321             goto out;
322         }
323
324         CFArrayAppendValue (sys->p_cred->whitelist, dict);
325         CFRelease (dict);
326
327         result = 0;
328         goto out;
329
330     } else {
331         result = -1;
332         goto out;
333     }
334
335 out:
336     CFRelease (trust);
337
338     if (cfHostname)
339         CFRelease (cfHostname);
340     if (leaf_cert)
341         CFRelease (leaf_cert);
342
343     return result;
344 }
345
346 /*
347  * @return -1 on fatal error, 0 on successful handshake completion,
348  * 1 if more would-be blocking recv is needed,
349  * 2 if more would-be blocking send is required.
350  */
351 static int st_Handshake (vlc_tls_t *session, const char *host,
352                                         const char *service) {
353     VLC_UNUSED(service);
354
355     vlc_tls_sys_t *sys = session->sys;
356
357     OSStatus retValue = SSLHandshake(sys->p_context);
358
359     if (retValue == errSSLWouldBlock) {
360         msg_Dbg(session, "handshake is blocked, try again later");
361         return 1 + (sys->b_blocking_send ? 1 : 0);
362     }
363
364     switch (retValue) {
365         case noErr:
366             if(st_validateServerCertificate(session, host) != 0) {
367                 return -1;
368             }
369             msg_Dbg(session, "handshake completed successfully");
370             sys->b_handshaked = true;
371             return 0;
372
373         case errSSLServerAuthCompleted:
374             return st_Handshake (session, host, service);
375
376         case errSSLConnectionRefused:
377             msg_Err(session, "connection was refused");
378             return -1;
379         case errSSLNegotiation:
380             msg_Err(session, "cipher suite negotiation failed");
381             return -1;
382         case errSSLFatalAlert:
383             msg_Err(session, "fatal error occured during handshake");
384             return -1;
385
386         default:
387             msg_Err(session, "handshake returned error %d", (int)retValue);
388             return -1;
389     }
390 }
391
392 /**
393  * Sends data through a TLS session.
394  */
395 static int st_Send (void *opaque, const void *buf, size_t length)
396 {
397     vlc_tls_t *session = opaque;
398     vlc_tls_sys_t *sys = session->sys;
399     OSStatus ret = noErr;
400
401     /*
402      * SSLWrite does not return the number of bytes actually written to
403      * the socket, but the number of bytes written to the internal cache.
404      *
405      * If return value is errSSLWouldBlock, the underlying socket cannot
406      * send all data, but the data is already cached. In this situation,
407      * we need to call SSLWrite again. To ensure this call even for the
408      * last bytes, we return EAGAIN. On the next call, we give no new data
409      * to SSLWrite until the error is not errSSLWouldBlock anymore.
410      *
411      * This code is adapted the same way as done in curl.
412      * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
413      */
414
415     size_t actualSize;
416     if (sys->i_send_buffered_bytes > 0) {
417         ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
418
419         if (ret == noErr) {
420             /* actualSize remains zero because no new data send */
421             actualSize = sys->i_send_buffered_bytes;
422             sys->i_send_buffered_bytes = 0;
423
424         } else if (ret == errSSLWouldBlock) {
425             /* EAGAIN is not expected by the core in this situation,
426              so use EINTR here */
427             errno = EINTR;
428             return -1;
429         }
430
431     } else {
432         ret = SSLWrite(sys->p_context, buf, length, &actualSize);
433
434         if (ret == errSSLWouldBlock) {
435             sys->i_send_buffered_bytes = length;
436             /* EAGAIN is not expected by the core in this situation,
437                so use EINTR here */
438             errno = EINTR;
439             return -1;
440         }
441     }
442
443     return ret != noErr ? st_Error(session, ret) : actualSize;
444 }
445
446 /**
447  * Receives data through a TLS session.
448  */
449 static int st_Recv (void *opaque, void *buf, size_t length)
450 {
451     vlc_tls_t *session = opaque;
452     vlc_tls_sys_t *sys = session->sys;
453
454     size_t actualSize;
455     OSStatus ret = SSLRead(sys->p_context, buf, length, &actualSize);
456
457     if(ret == errSSLWouldBlock && actualSize)
458         return actualSize;
459
460     return ret != noErr ? st_Error(session, ret) : actualSize;
461 }
462
463 /**
464  * Closes a client-side TLS credentials.
465  */
466 static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
467
468     VLC_UNUSED(crd);
469
470     vlc_tls_sys_t *sys = session->sys;
471     msg_Dbg(session, "close TLS session");
472
473     if(sys->b_handshaked) {
474         OSStatus ret = SSLClose(sys->p_context);
475         if(ret != noErr) {
476             msg_Err(session, "error closing ssl context");
477         }
478     }
479
480     if (sys->p_context) {
481 #if TARGET_OS_IPHONE
482         CFRelease(sys->p_context);
483 #else
484         if(SSLDisposeContext(sys->p_context) != noErr) {
485             msg_Err(session, "error deleting context");
486         }
487 #endif
488     }
489     free (sys);
490 }
491
492 /**
493  * Initializes a client-side TLS session.
494  */
495 static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
496                                      int fd, const char *hostname) {
497     msg_Dbg(session, "open TLS session for %s", hostname);
498
499     vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
500     if (unlikely(sys == NULL))
501         return VLC_ENOMEM;
502
503     sys->p_cred = crd->sys;
504     sys->i_fd = fd;
505     sys->b_handshaked = false;
506     sys->b_blocking_send = false;
507     sys->i_send_buffered_bytes = 0;
508
509     session->sys = sys;
510     session->sock.p_sys = session;
511     session->sock.pf_send = st_Send;
512     session->sock.pf_recv = st_Recv;
513     session->handshake = st_Handshake;
514
515     SSLContextRef p_context = NULL;
516 #if TARGET_OS_IPHONE
517     p_context = SSLCreateContext (NULL, kSSLClientSide, kSSLStreamType);
518     if(p_context == NULL) {
519         msg_Err(session, "cannot create ssl context");
520         goto error;
521     }
522 #else
523     if (SSLNewContext (false, &p_context) != noErr) {
524         msg_Err(session, "error calling SSLNewContext");
525         goto error;
526     }
527 #endif
528
529     sys->p_context = p_context;
530
531     OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc);
532     if(ret != noErr) {
533         msg_Err(session, "cannot set io functions");
534         goto error;
535     }
536
537     ret = SSLSetConnection (p_context, session);
538     if(ret != noErr) {
539         msg_Err(session, "cannot set connection");
540         goto error;
541     }
542
543     ret = SSLSetPeerDomainName (p_context, hostname, strlen(hostname));
544     if(ret != noErr) {
545         msg_Err(session, "cannot set peer domain name");
546         goto error;
547     }
548
549     /* disable automatic validation. We do so manually to also handle invalid
550        certificates */
551
552     /* this has effect only on iOS 5 and OSX 10.8 or later ... */
553     ret = SSLSetSessionOption (sys->p_context, kSSLSessionOptionBreakOnServerAuth, true);
554     if(ret != noErr) {
555         msg_Err (session, "cannot set session option");
556         goto error;
557     }
558 #if !TARGET_OS_IPHONE
559     /* ... thus calling this for earlier osx versions, which is not available on iOS in turn */
560     ret = SSLSetEnableCertVerify (sys->p_context, false);
561     if(ret != noErr) {
562         msg_Err (session, "error setting enable cert verify");
563         goto error;
564     }
565 #endif
566
567     return VLC_SUCCESS;
568
569 error:
570     st_ClientSessionClose(crd, session);
571     return VLC_EGENERIC;
572 }
573
574 /**
575  * Initializes a client-side TLS credentials.
576  */
577 static int OpenClient (vlc_tls_creds_t *crd) {
578
579     msg_Dbg(crd, "open st client");
580
581     vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
582     if (unlikely(sys == NULL))
583         return VLC_ENOMEM;
584
585     sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
586
587     crd->sys = sys;
588     crd->open = st_ClientSessionOpen;
589     crd->close = st_ClientSessionClose;
590
591     return VLC_SUCCESS;
592
593 }
594
595 static void CloseClient (vlc_tls_creds_t *crd) {
596     msg_Dbg(crd, "close secure transport client");
597
598     vlc_tls_creds_sys_t *sys = crd->sys;
599
600     if (sys->whitelist)
601         CFRelease(sys->whitelist);
602
603     free (sys);
604 }