[vlc-devel] [PATCH] securetransport: Add TLS server module

david.fuhrmann at gmail.com david.fuhrmann at gmail.com
Fri Jan 10 15:34:48 CET 2014


From: David Fuhrmann <david.fuhrmann at googlemail.com>

This module uses the string passed by --http-cert to search
the server certificate in the OSX keychain. The corresponding
private key is selected automatically.
---
 modules/misc/securetransport.c | 290 +++++++++++++++++++++++++++++++++++------
 1 file changed, 251 insertions(+), 39 deletions(-)

diff --git a/modules/misc/securetransport.c b/modules/misc/securetransport.c
index 818ff3a..ded4c3a 100644
--- a/modules/misc/securetransport.c
+++ b/modules/misc/securetransport.c
@@ -46,12 +46,25 @@
 static int  OpenClient  (vlc_tls_creds_t *);
 static void CloseClient (vlc_tls_creds_t *);
 
+static int  OpenServer  (vlc_tls_creds_t *crd, const char *cert, const char *key);
+static void CloseServer (vlc_tls_creds_t *);
+
 vlc_module_begin ()
     set_description(N_("TLS support for OS X and iOS"))
     set_capability("tls client", 2)
     set_callbacks(OpenClient, CloseClient)
     set_category(CAT_ADVANCED)
     set_subcategory(SUBCAT_ADVANCED_NETWORK)
+
+#if !TARGET_OS_IPHONE
+    add_submodule()
+        set_description(N_("TLS server support for OS X"))
+        set_capability("tls server", 2)
+        set_callbacks(OpenServer, CloseServer)
+        set_category(CAT_ADVANCED)
+        set_subcategory(SUBCAT_ADVANCED_NETWORK)
+#endif /* !TARGET_OS_IPHONE */
+
 vlc_module_end ()
 
 
@@ -61,6 +74,9 @@ vlc_module_end ()
 struct vlc_tls_creds_sys
 {
     CFMutableArrayRef whitelist;
+
+    /* valid in server mode */
+    CFArrayRef server_cert_chain;
 };
 
 struct vlc_tls_sys {
@@ -71,22 +87,22 @@ struct vlc_tls_sys {
 
     bool b_blocking_send;
     bool b_handshaked;
+    bool b_server_mode;
 };
 
 static int st_Error (vlc_tls_t *obj, int val)
 {
     switch (val)
     {
-        /* peer performed shutdown */
-        case errSSLClosedNoNotify:
-        case errSSLClosedGraceful:
-            msg_Dbg(obj, "Got shutdown notification");
-            return 0;
-
         case errSSLWouldBlock:
             errno = EAGAIN;
             break;
 
+        case errSSLClosedGraceful:
+        case errSSLClosedAbort:
+            msg_Dbg (obj, "Connection closed with code %d", val);
+            errno = ECONNRESET;
+            break;
         default:
             msg_Err (obj, "Found error %d", val);
             errno = ECONNRESET;
@@ -176,12 +192,20 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
     } while (val >= 0 && (bytesSent += val) < dataLen);
 
     if(val < 0) {
-        if(errno == EAGAIN) {
-            retValue = errSSLWouldBlock;
-            sys->b_blocking_send = true;
-        } else {
-            msg_Err(session, "error while writing: %d", errno);
-            retValue = ioErr;
+        switch(errno) {
+            case EAGAIN:
+                retValue = errSSLWouldBlock;
+                sys->b_blocking_send = true;
+                break;
+
+            case EPIPE:
+            case ECONNRESET:
+                retValue = errSSLClosedAbort;
+                break;
+
+            default:
+                msg_Err(session, "error while writing: %d", errno);
+                retValue = ioErr;
         }
     }
 
@@ -363,7 +387,7 @@ static int st_Handshake (vlc_tls_t *session, const char *host,
 
     switch (retValue) {
         case noErr:
-            if(st_validateServerCertificate(session, host) != 0) {
+            if(sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
                 return -1;
             }
             msg_Dbg(session, "handshake completed successfully");
@@ -412,6 +436,10 @@ static int st_Send (void *opaque, const void *buf, size_t length)
      * (https://github.com/bagder/curl/blob/master/lib/curl_darwinssl.c#L2067)
      */
 
+    /* EAGAIN is not expected by net_Write in this situation,
+       so use EINTR here */
+    int againErr = sys->b_server_mode ? EAGAIN : EINTR;
+
     size_t actualSize;
     if (sys->i_send_buffered_bytes > 0) {
         ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
@@ -422,9 +450,7 @@ static int st_Send (void *opaque, const void *buf, size_t length)
             sys->i_send_buffered_bytes = 0;
 
         } else if (ret == errSSLWouldBlock) {
-            /* EAGAIN is not expected by the core in this situation,
-             so use EINTR here */
-            errno = EINTR;
+            errno = againErr;
             return -1;
         }
 
@@ -433,9 +459,7 @@ static int st_Send (void *opaque, const void *buf, size_t length)
 
         if (ret == errSSLWouldBlock) {
             sys->i_send_buffered_bytes = length;
-            /* EAGAIN is not expected by the core in this situation,
-               so use EINTR here */
-            errno = EINTR;
+            errno = againErr;
             return -1;
         }
     }
@@ -457,27 +481,33 @@ static int st_Recv (void *opaque, void *buf, size_t length)
     if(ret == errSSLWouldBlock && actualSize)
         return actualSize;
 
+    /* peer performed shutdown */
+    if (ret == errSSLClosedNoNotify || ret == errSSLClosedGraceful) {
+        msg_Dbg(session, "Got close notification with code %d", ret);
+        return 0;
+    }
+
     return ret != noErr ? st_Error(session, ret) : actualSize;
 }
 
 /**
- * Closes a client-side TLS credentials.
+ * Closes a TLS session.
  */
-static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
+static void st_SessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
 
     VLC_UNUSED(crd);
 
     vlc_tls_sys_t *sys = session->sys;
     msg_Dbg(session, "close TLS session");
 
-    if(sys->b_handshaked) {
-        OSStatus ret = SSLClose(sys->p_context);
-        if(ret != noErr) {
-            msg_Err(session, "error closing ssl context");
+    if (sys->p_context) {
+        if(sys->b_handshaked) {
+            OSStatus ret = SSLClose(sys->p_context);
+            if(ret != noErr) {
+                msg_Warn(session, "Cannot close ssl context");
+            }
         }
-    }
 
-    if (sys->p_context) {
 #if TARGET_OS_IPHONE
         CFRelease(sys->p_context);
 #else
@@ -492,9 +522,9 @@ static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
 /**
  * Initializes a client-side TLS session.
  */
-static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
-                                     int fd, const char *hostname) {
-    msg_Dbg(session, "open TLS session for %s", hostname);
+
+static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *session,
+                                 int fd, bool b_server) {
 
     vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
     if (unlikely(sys == NULL))
@@ -505,6 +535,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
     sys->b_handshaked = false;
     sys->b_blocking_send = false;
     sys->i_send_buffered_bytes = 0;
+    sys->p_context = NULL;
 
     session->sys = sys;
     session->sock.p_sys = session;
@@ -514,15 +545,15 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
 
     SSLContextRef p_context = NULL;
 #if TARGET_OS_IPHONE
-    p_context = SSLCreateContext (NULL, kSSLClientSide, kSSLStreamType);
+    p_context = SSLCreateContext (NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
     if(p_context == NULL) {
         msg_Err(session, "cannot create ssl context");
-        goto error;
+        return -1;
     }
 #else
-    if (SSLNewContext (false, &p_context) != noErr) {
+    if (SSLNewContext (b_server, &p_context) != noErr) {
         msg_Err(session, "error calling SSLNewContext");
-        goto error;
+        return -1;
     }
 #endif
 
@@ -531,16 +562,31 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
     OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc);
     if(ret != noErr) {
         msg_Err(session, "cannot set io functions");
-        goto error;
+        return -1;
     }
 
     ret = SSLSetConnection (p_context, session);
     if(ret != noErr) {
         msg_Err(session, "cannot set connection");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
+                                     int fd, const char *hostname) {
+    msg_Dbg(session, "open TLS session for %s", hostname);
+
+    int ret = st_SessionOpenCommon (crd, session, fd, false);
+    if(ret != noErr) {
         goto error;
     }
 
-    ret = SSLSetPeerDomainName (p_context, hostname, strlen(hostname));
+    vlc_tls_sys_t *sys = session->sys;
+    sys->b_server_mode = false;
+
+    ret = SSLSetPeerDomainName (sys->p_context, hostname, strlen(hostname));
     if(ret != noErr) {
         msg_Err(session, "cannot set peer domain name");
         goto error;
@@ -567,7 +613,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
     return VLC_SUCCESS;
 
 error:
-    st_ClientSessionClose(crd, session);
+    st_SessionClose (crd, session);
     return VLC_EGENERIC;
 }
 
@@ -583,13 +629,13 @@ static int OpenClient (vlc_tls_creds_t *crd) {
         return VLC_ENOMEM;
 
     sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+    sys->server_cert_chain = NULL;
 
     crd->sys = sys;
     crd->open = st_ClientSessionOpen;
-    crd->close = st_ClientSessionClose;
+    crd->close = st_SessionClose;
 
     return VLC_SUCCESS;
-
 }
 
 static void CloseClient (vlc_tls_creds_t *crd) {
@@ -602,3 +648,169 @@ static void CloseClient (vlc_tls_creds_t *crd) {
 
     free (sys);
 }
+
+/* Begin of server-side methods */
+#if !TARGET_OS_IPHONE
+
+/**
+ * Initializes a server-side TLS session.
+ */
+static int st_ServerSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
+                                 int fd, const char *hostname) {
+
+    VLC_UNUSED(hostname);
+    msg_Dbg(session, "open TLS server session");
+
+    int ret = st_SessionOpenCommon (crd, session, fd, true);
+    if(ret != noErr) {
+        goto error;
+    }
+
+    vlc_tls_sys_t *sys = session->sys;
+    sys->b_server_mode = true;
+
+    ret = SSLSetCertificate (sys->p_context, crd->sys->server_cert_chain);
+    if (ret != noErr) {
+        msg_Err (session, "cannot set server certificate");
+        goto error;
+    }
+
+    return VLC_SUCCESS;
+
+error:
+    st_SessionClose (crd, session);
+    return VLC_EGENERIC;
+}
+
+/**
+ * Initializes server-side TLS credentials.
+ */
+static int OpenServer (vlc_tls_creds_t *crd, const char *cert, const char *key) {
+
+    /*
+     * This function expects the label of the certificate in "cert", stored
+     * in the MacOS keychain. The appropriate private key is found automatically.
+     */
+    VLC_UNUSED (key);
+    OSStatus ret;
+
+    msg_Dbg(crd, "open st server");
+
+    vlc_tls_creds_sys_t *sys = malloc (sizeof (*sys));
+    if (unlikely (sys == NULL))
+        return VLC_ENOMEM;
+
+    /*
+     * Get the server certificate.
+     *
+     * This API is deprecated, but the replacement SecItemCopyMatching
+     * only works on >= 10.7
+     */
+    SecKeychainAttribute attrib = { kSecLabelItemAttr, strlen(cert), (void *)cert };
+    SecKeychainAttributeList attrList = { 1, &attrib };
+
+    SecKeychainSearchRef searchReference = NULL;
+    ret = SecKeychainSearchCreateFromAttributes (NULL, kSecCertificateItemClass,
+                                                 &attrList, &searchReference);
+    if (ret != noErr || searchReference == NULL) {
+        msg_Err (crd, "Cannot find certificate with alias %s", cert);
+        return VLC_EGENERIC;
+    }
+
+    SecKeychainItemRef itemRef = NULL;
+    ret = SecKeychainSearchCopyNext (searchReference, &itemRef);
+    if (ret != noErr) {
+        msg_Err (crd, "Cannot get certificate with alias %s, error: %d", cert, ret);
+        return VLC_EGENERIC;
+    }
+    CFRelease (searchReference);
+
+    /* cast allowed according to documentation */
+    SecCertificateRef certificate = (SecCertificateRef)itemRef;
+
+    SecIdentityRef cert_identity = NULL;
+    ret = SecIdentityCreateWithCertificate (NULL, certificate, &cert_identity);
+    if (ret != noErr) {
+        msg_Err (crd, "Cannot get private key for certificate");
+        CFRelease(certificate);
+        return VLC_EGENERIC;
+    }
+
+    /*
+     * We try to validate the server certificate, but do not care about the result.
+     * The only aim is to get the certificate chain.
+     */
+    SecPolicyRef policy = SecPolicyCreateSSL (true, NULL);
+    SecTrustRef trust_ref = NULL;
+    int result = VLC_SUCCESS;
+
+    /* According to docu its fine to pass just one certificate */
+    ret = SecTrustCreateWithCertificates ((CFArrayRef)certificate, policy, &trust_ref);
+    if(ret != noErr) {
+        msg_Err (crd, "Cannot create trust");
+        result = VLC_EGENERIC;
+        goto out;
+    }
+
+    SecTrustResultType status;
+    ret = SecTrustEvaluate (trust_ref, &status);
+    if(ret != noErr) {
+        msg_Err (crd, "Error evaluating trust");
+        result = VLC_EGENERIC;
+        goto out;
+    }
+
+    CFArrayRef cert_chain = NULL;
+    CSSM_TP_APPLE_EVIDENCE_INFO *status_chain;
+    ret = SecTrustGetResult (trust_ref, &status, &cert_chain, &status_chain);
+    if(ret != noErr || !cert_chain) {
+        msg_Err (crd, "error while getting certificate chain");
+        result = VLC_EGENERIC;
+        goto out;
+    }
+
+    CFIndex num_cert_chain = CFArrayGetCount (cert_chain);
+
+    /* Build up the certificate chain array expected by SSLSetCertificate */
+    CFMutableArrayRef server_cert_chain = CFArrayCreateMutable(kCFAllocatorDefault, num_cert_chain, &kCFTypeArrayCallBacks);
+    CFArrayAppendValue (server_cert_chain, cert_identity);
+
+    msg_Dbg (crd, "Found certificate chain with %ld entries for server certificate", num_cert_chain);
+    if (num_cert_chain > 1)
+        CFArrayAppendArray (server_cert_chain, cert_chain, CFRangeMake(1, num_cert_chain - 1));
+    CFRelease (cert_chain);
+
+
+    sys->server_cert_chain = server_cert_chain;
+    sys->whitelist = NULL;
+
+    crd->sys = sys;
+    crd->open = st_ServerSessionOpen;
+    crd->close = st_SessionClose;
+
+out:
+    if (policy)
+        CFRelease (policy);
+    if (trust_ref)
+        CFRelease (trust_ref);
+
+    if (certificate)
+        CFRelease (certificate);
+    if (cert_identity)
+        CFRelease (cert_identity);
+
+    return result;
+}
+
+static void CloseServer (vlc_tls_creds_t *crd) {
+    msg_Dbg (crd, "close secure transport server");
+
+    vlc_tls_creds_sys_t *sys = crd->sys;
+
+    if (sys->server_cert_chain)
+        CFRelease (sys->server_cert_chain);
+
+    free (sys);
+}
+
+#endif /* !TARGET_OS_IPHONE */
\ No newline at end of file
-- 
1.8.3.4 (Apple Git-47)




More information about the vlc-devel mailing list