Commit b9f4af75 authored by David Fuhrmann's avatar David Fuhrmann

securetransport: Add TLS server module

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.
parent a07e33cd
...@@ -46,12 +46,30 @@ ...@@ -46,12 +46,30 @@
static int OpenClient (vlc_tls_creds_t *); static int OpenClient (vlc_tls_creds_t *);
static void CloseClient (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 () vlc_module_begin ()
set_description(N_("TLS support for OS X and iOS")) set_description(N_("TLS support for OS X and iOS"))
set_capability("tls client", 2) set_capability("tls client", 2)
set_callbacks(OpenClient, CloseClient) set_callbacks(OpenClient, CloseClient)
set_category(CAT_ADVANCED) set_category(CAT_ADVANCED)
set_subcategory(SUBCAT_ADVANCED_NETWORK) set_subcategory(SUBCAT_ADVANCED_NETWORK)
/*
* The server module currently uses an OSX only API, to be compatible with 10.6.
* If the module is needed on iOS, then the "modern" keychain lookup API need to be
* implemented.
*/
#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 () vlc_module_end ()
...@@ -61,6 +79,9 @@ vlc_module_end () ...@@ -61,6 +79,9 @@ vlc_module_end ()
struct vlc_tls_creds_sys struct vlc_tls_creds_sys
{ {
CFMutableArrayRef whitelist; CFMutableArrayRef whitelist;
/* valid in server mode */
CFArrayRef server_cert_chain;
}; };
struct vlc_tls_sys { struct vlc_tls_sys {
...@@ -71,22 +92,22 @@ struct vlc_tls_sys { ...@@ -71,22 +92,22 @@ struct vlc_tls_sys {
bool b_blocking_send; bool b_blocking_send;
bool b_handshaked; bool b_handshaked;
bool b_server_mode;
}; };
static int st_Error (vlc_tls_t *obj, int val) static int st_Error (vlc_tls_t *obj, int val)
{ {
switch (val) switch (val)
{ {
/* peer performed shutdown */
case errSSLClosedNoNotify:
case errSSLClosedGraceful:
msg_Dbg(obj, "Got shutdown notification");
return 0;
case errSSLWouldBlock: case errSSLWouldBlock:
errno = EAGAIN; errno = EAGAIN;
break; break;
case errSSLClosedGraceful:
case errSSLClosedAbort:
msg_Dbg(obj, "Connection closed with code %d", val);
errno = ECONNRESET;
break;
default: default:
msg_Err (obj, "Found error %d", val); msg_Err (obj, "Found error %d", val);
errno = ECONNRESET; errno = ECONNRESET;
...@@ -176,12 +197,20 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection, ...@@ -176,12 +197,20 @@ static OSStatus st_SocketWriteFunc (SSLConnectionRef connection,
} while (val >= 0 && (bytesSent += val) < dataLen); } while (val >= 0 && (bytesSent += val) < dataLen);
if(val < 0) { if(val < 0) {
if(errno == EAGAIN) { switch(errno) {
retValue = errSSLWouldBlock; case EAGAIN:
sys->b_blocking_send = true; retValue = errSSLWouldBlock;
} else { sys->b_blocking_send = true;
msg_Err(session, "error while writing: %d", errno); break;
retValue = ioErr;
case EPIPE:
case ECONNRESET:
retValue = errSSLClosedAbort;
break;
default:
msg_Err(session, "error while writing: %d", errno);
retValue = ioErr;
} }
} }
...@@ -363,7 +392,7 @@ static int st_Handshake (vlc_tls_t *session, const char *host, ...@@ -363,7 +392,7 @@ static int st_Handshake (vlc_tls_t *session, const char *host,
switch (retValue) { switch (retValue) {
case noErr: case noErr:
if(st_validateServerCertificate(session, host) != 0) { if (sys->b_server_mode == false && st_validateServerCertificate(session, host) != 0) {
return -1; return -1;
} }
msg_Dbg(session, "handshake completed successfully"); msg_Dbg(session, "handshake completed successfully");
...@@ -412,6 +441,10 @@ static int st_Send (void *opaque, const void *buf, size_t length) ...@@ -412,6 +441,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) * (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; size_t actualSize;
if (sys->i_send_buffered_bytes > 0) { if (sys->i_send_buffered_bytes > 0) {
ret = SSLWrite(sys->p_context, NULL, 0, &actualSize); ret = SSLWrite(sys->p_context, NULL, 0, &actualSize);
...@@ -422,9 +455,7 @@ static int st_Send (void *opaque, const void *buf, size_t length) ...@@ -422,9 +455,7 @@ static int st_Send (void *opaque, const void *buf, size_t length)
sys->i_send_buffered_bytes = 0; sys->i_send_buffered_bytes = 0;
} else if (ret == errSSLWouldBlock) { } else if (ret == errSSLWouldBlock) {
/* EAGAIN is not expected by the core in this situation, errno = againErr;
so use EINTR here */
errno = EINTR;
return -1; return -1;
} }
...@@ -433,9 +464,7 @@ static int st_Send (void *opaque, const void *buf, size_t length) ...@@ -433,9 +464,7 @@ static int st_Send (void *opaque, const void *buf, size_t length)
if (ret == errSSLWouldBlock) { if (ret == errSSLWouldBlock) {
sys->i_send_buffered_bytes = length; sys->i_send_buffered_bytes = length;
/* EAGAIN is not expected by the core in this situation, errno = againErr;
so use EINTR here */
errno = EINTR;
return -1; return -1;
} }
} }
...@@ -457,27 +486,33 @@ static int st_Recv (void *opaque, void *buf, size_t length) ...@@ -457,27 +486,33 @@ static int st_Recv (void *opaque, void *buf, size_t length)
if(ret == errSSLWouldBlock && actualSize) if(ret == errSSLWouldBlock && actualSize)
return 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; 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_UNUSED(crd);
vlc_tls_sys_t *sys = session->sys; vlc_tls_sys_t *sys = session->sys;
msg_Dbg(session, "close TLS session"); msg_Dbg(session, "close TLS session");
if(sys->b_handshaked) { if (sys->p_context) {
OSStatus ret = SSLClose(sys->p_context); if (sys->b_handshaked) {
if(ret != noErr) { OSStatus ret = SSLClose(sys->p_context);
msg_Err(session, "error closing ssl context"); if (ret != noErr) {
msg_Warn(session, "Cannot close ssl context");
}
} }
}
if (sys->p_context) {
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
CFRelease(sys->p_context); CFRelease(sys->p_context);
#else #else
...@@ -492,9 +527,9 @@ static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) { ...@@ -492,9 +527,9 @@ static void st_ClientSessionClose (vlc_tls_creds_t *crd, vlc_tls_t *session) {
/** /**
* Initializes a client-side TLS 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) { static int st_SessionOpenCommon (vlc_tls_creds_t *crd, vlc_tls_t *session,
msg_Dbg(session, "open TLS session for %s", hostname); int fd, bool b_server) {
vlc_tls_sys_t *sys = malloc (sizeof (*session->sys)); vlc_tls_sys_t *sys = malloc (sizeof (*session->sys));
if (unlikely(sys == NULL)) if (unlikely(sys == NULL))
...@@ -505,6 +540,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session, ...@@ -505,6 +540,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
sys->b_handshaked = false; sys->b_handshaked = false;
sys->b_blocking_send = false; sys->b_blocking_send = false;
sys->i_send_buffered_bytes = 0; sys->i_send_buffered_bytes = 0;
sys->p_context = NULL;
session->sys = sys; session->sys = sys;
session->sock.p_sys = session; session->sock.p_sys = session;
...@@ -514,15 +550,15 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session, ...@@ -514,15 +550,15 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
SSLContextRef p_context = NULL; SSLContextRef p_context = NULL;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
p_context = SSLCreateContext (NULL, kSSLClientSide, kSSLStreamType); p_context = SSLCreateContext(NULL, b_server ? kSSLServerSide : kSSLClientSide, kSSLStreamType);
if(p_context == NULL) { if (p_context == NULL) {
msg_Err(session, "cannot create ssl context"); msg_Err(session, "cannot create ssl context");
goto error; return -1;
} }
#else #else
if (SSLNewContext (false, &p_context) != noErr) { if (SSLNewContext(b_server, &p_context) != noErr) {
msg_Err(session, "error calling SSLNewContext"); msg_Err(session, "error calling SSLNewContext");
goto error; return -1;
} }
#endif #endif
...@@ -531,17 +567,32 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session, ...@@ -531,17 +567,32 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc); OSStatus ret = SSLSetIOFuncs (p_context, st_SocketReadFunc, st_SocketWriteFunc);
if(ret != noErr) { if(ret != noErr) {
msg_Err(session, "cannot set io functions"); msg_Err(session, "cannot set io functions");
goto error; return -1;
} }
ret = SSLSetConnection (p_context, session); ret = SSLSetConnection (p_context, session);
if(ret != noErr) { if(ret != noErr) {
msg_Err(session, "cannot set connection"); 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; goto error;
} }
ret = SSLSetPeerDomainName (p_context, hostname, strlen(hostname)); vlc_tls_sys_t *sys = session->sys;
if(ret != noErr) { sys->b_server_mode = false;
ret = SSLSetPeerDomainName(sys->p_context, hostname, strlen(hostname));
if (ret != noErr) {
msg_Err(session, "cannot set peer domain name"); msg_Err(session, "cannot set peer domain name");
goto error; goto error;
} }
...@@ -567,7 +618,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session, ...@@ -567,7 +618,7 @@ static int st_ClientSessionOpen (vlc_tls_creds_t *crd, vlc_tls_t *session,
return VLC_SUCCESS; return VLC_SUCCESS;
error: error:
st_ClientSessionClose(crd, session); st_SessionClose(crd, session);
return VLC_EGENERIC; return VLC_EGENERIC;
} }
...@@ -583,13 +634,13 @@ static int OpenClient (vlc_tls_creds_t *crd) { ...@@ -583,13 +634,13 @@ static int OpenClient (vlc_tls_creds_t *crd) {
return VLC_ENOMEM; return VLC_ENOMEM;
sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); sys->whitelist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
sys->server_cert_chain = NULL;
crd->sys = sys; crd->sys = sys;
crd->open = st_ClientSessionOpen; crd->open = st_ClientSessionOpen;
crd->close = st_ClientSessionClose; crd->close = st_SessionClose;
return VLC_SUCCESS; return VLC_SUCCESS;
} }
static void CloseClient (vlc_tls_creds_t *crd) { static void CloseClient (vlc_tls_creds_t *crd) {
...@@ -602,3 +653,169 @@ static void CloseClient (vlc_tls_creds_t *crd) { ...@@ -602,3 +653,169 @@ static void CloseClient (vlc_tls_creds_t *crd) {
free (sys); 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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment