mirror of
https://github.com/curl/curl.git
synced 2024-11-27 05:50:21 +08:00
f92779198d
Previously the curl_multi interface would freeze if darwinssl was enabled and at least one of the handles tried to connect to a Web site using HTTPS. Removed the "wouldblock" state darwinssl was using because I figured out a solution for our "would block but in which direction?" dilemma.
921 lines
28 KiB
C
921 lines
28 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) 2012, Nick Zitzmann, <nickzman@gmail.com>.
|
|
* Copyright (C) 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at http://curl.haxx.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* Source file for all iOS and Mac OS X SecureTransport-specific code for the
|
|
* TLS/SSL layer. No code but sslgen.c should ever call or use these functions.
|
|
*/
|
|
|
|
#include "setup.h"
|
|
|
|
#ifdef USE_DARWINSSL
|
|
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#include <Security/Security.h>
|
|
#include <Security/SecureTransport.h>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <CommonCrypto/CommonDigest.h>
|
|
|
|
#include "urldata.h"
|
|
#include "sendf.h"
|
|
#include "inet_pton.h"
|
|
#include "connect.h"
|
|
#include "select.h"
|
|
#include "sslgen.h"
|
|
#include "curl_darwinssl.h"
|
|
|
|
#define _MPRINTF_REPLACE /* use our functions only */
|
|
#include <curl/mprintf.h>
|
|
|
|
#include "curl_memory.h"
|
|
/* The last #include file should be: */
|
|
#include "memdebug.h"
|
|
|
|
/* From MacTypes.h (which we can't include because it isn't present in iOS: */
|
|
#define ioErr -36
|
|
|
|
/* The following two functions were ripped from Apple sample code,
|
|
* with some modifications: */
|
|
static OSStatus SocketRead(SSLConnectionRef connection,
|
|
void *data, /* owned by
|
|
* caller, data
|
|
* RETURNED */
|
|
size_t *dataLength) /* IN/OUT */
|
|
{
|
|
UInt32 bytesToGo = *dataLength;
|
|
UInt32 initLen = bytesToGo;
|
|
UInt8 *currData = (UInt8 *)data;
|
|
/*int sock = *(int *)connection;*/
|
|
struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection;
|
|
int sock = connssl->ssl_sockfd;
|
|
OSStatus rtn = noErr;
|
|
UInt32 bytesRead;
|
|
int rrtn;
|
|
int theErr;
|
|
|
|
*dataLength = 0;
|
|
|
|
for(;;) {
|
|
bytesRead = 0;
|
|
rrtn = read(sock, currData, bytesToGo);
|
|
if(rrtn <= 0) {
|
|
/* this is guesswork... */
|
|
theErr = errno;
|
|
if((rrtn == 0) && (theErr == 0)) {
|
|
/* try fix for iSync */
|
|
rtn = errSSLClosedGraceful;
|
|
}
|
|
else /* do the switch */
|
|
switch(theErr) {
|
|
case ENOENT:
|
|
/* connection closed */
|
|
rtn = errSSLClosedGraceful;
|
|
break;
|
|
case ECONNRESET:
|
|
rtn = errSSLClosedAbort;
|
|
break;
|
|
case EAGAIN:
|
|
rtn = errSSLWouldBlock;
|
|
connssl->ssl_direction = false;
|
|
break;
|
|
default:
|
|
rtn = ioErr;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
bytesRead = rrtn;
|
|
}
|
|
bytesToGo -= bytesRead;
|
|
currData += bytesRead;
|
|
|
|
if(bytesToGo == 0) {
|
|
/* filled buffer with incoming data, done */
|
|
break;
|
|
}
|
|
}
|
|
*dataLength = initLen - bytesToGo;
|
|
|
|
return rtn;
|
|
}
|
|
|
|
static OSStatus SocketWrite(SSLConnectionRef connection,
|
|
const void *data,
|
|
size_t *dataLength) /* IN/OUT */
|
|
{
|
|
UInt32 bytesSent = 0;
|
|
/*int sock = *(int *)connection;*/
|
|
struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection;
|
|
int sock = connssl->ssl_sockfd;
|
|
int length;
|
|
UInt32 dataLen = *dataLength;
|
|
const UInt8 *dataPtr = (UInt8 *)data;
|
|
OSStatus ortn;
|
|
int theErr;
|
|
|
|
*dataLength = 0;
|
|
|
|
do {
|
|
length = write(sock,
|
|
(char*)dataPtr + bytesSent,
|
|
dataLen - bytesSent);
|
|
} while((length > 0) &&
|
|
( (bytesSent += length) < dataLen) );
|
|
|
|
if(length <= 0) {
|
|
theErr = errno;
|
|
if(theErr == EAGAIN) {
|
|
ortn = errSSLWouldBlock;
|
|
connssl->ssl_direction = true;
|
|
}
|
|
else {
|
|
ortn = ioErr;
|
|
}
|
|
}
|
|
else {
|
|
ortn = noErr;
|
|
}
|
|
*dataLength = bytesSent;
|
|
return ortn;
|
|
}
|
|
|
|
CF_INLINE const char *CipherNameForNumber(SSLCipherSuite cipher) {
|
|
switch (cipher) {
|
|
case SSL_RSA_WITH_NULL_MD5:
|
|
return "SSL_RSA_WITH_NULL_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_NULL_SHA:
|
|
return "SSL_RSA_WITH_NULL_SHA";
|
|
break;
|
|
case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
|
|
return "SSL_RSA_EXPORT_WITH_RC4_40_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_RC4_128_MD5:
|
|
return "SSL_RSA_WITH_RC4_128_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_RC4_128_SHA:
|
|
return "SSL_RSA_WITH_RC4_128_SHA";
|
|
break;
|
|
case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
|
|
return "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_IDEA_CBC_SHA:
|
|
return "SSL_RSA_WITH_IDEA_CBC_SHA";
|
|
break;
|
|
case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_RSA_WITH_DES_CBC_SHA:
|
|
return "SSL_RSA_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_RSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_DH_DSS_WITH_DES_CBC_SHA:
|
|
return "SSL_DH_DSS_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_DH_RSA_WITH_DES_CBC_SHA:
|
|
return "SSL_DH_RSA_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_DSS_WITH_DES_CBC_SHA:
|
|
return "SSL_DHE_DSS_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_RSA_WITH_DES_CBC_SHA:
|
|
return "SSL_DHE_RSA_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
|
|
return "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5";
|
|
break;
|
|
case SSL_DH_anon_WITH_RC4_128_MD5:
|
|
return "SSL_DH_anon_WITH_RC4_128_MD5";
|
|
break;
|
|
case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
|
|
return "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA";
|
|
break;
|
|
case SSL_DH_anon_WITH_DES_CBC_SHA:
|
|
return "SSL_DH_anon_WITH_DES_CBC_SHA";
|
|
break;
|
|
case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
|
|
return "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
|
|
return "SSL_FORTEZZA_DMS_WITH_NULL_SHA";
|
|
break;
|
|
case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA:
|
|
return "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA";
|
|
break;
|
|
case TLS_RSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_RSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
|
|
return "TLS_DH_DSS_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_DH_RSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
|
|
return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_DH_anon_WITH_AES_128_CBC_SHA:
|
|
return "TLS_DH_anon_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_RSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_RSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
|
|
return "TLS_DH_DSS_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_DH_RSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
|
|
return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_DH_anon_WITH_AES_256_CBC_SHA:
|
|
return "TLS_DH_anon_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_NULL_SHA:
|
|
return "TLS_ECDH_ECDSA_WITH_NULL_SHA";
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
|
|
return "TLS_ECDH_ECDSA_WITH_RC4_128_SHA";
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
|
|
return "TLS_ECDHE_ECDSA_WITH_NULL_SHA";
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
|
|
return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA";
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_NULL_SHA:
|
|
return "TLS_ECDH_RSA_WITH_NULL_SHA";
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_RC4_128_SHA:
|
|
return "TLS_ECDH_RSA_WITH_RC4_128_SHA";
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_NULL_SHA:
|
|
return "TLS_ECDHE_RSA_WITH_NULL_SHA";
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
|
|
return "TLS_ECDHE_RSA_WITH_RC4_128_SHA";
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
|
|
return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
|
|
return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_anon_WITH_NULL_SHA:
|
|
return "TLS_ECDH_anon_WITH_NULL_SHA";
|
|
break;
|
|
case TLS_ECDH_anon_WITH_RC4_128_SHA:
|
|
return "TLS_ECDH_anon_WITH_RC4_128_SHA";
|
|
break;
|
|
case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
|
|
return "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
|
|
return "TLS_ECDH_anon_WITH_AES_128_CBC_SHA";
|
|
break;
|
|
case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
|
|
return "TLS_ECDH_anon_WITH_AES_256_CBC_SHA";
|
|
break;
|
|
case SSL_RSA_WITH_RC2_CBC_MD5:
|
|
return "SSL_RSA_WITH_RC2_CBC_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_IDEA_CBC_MD5:
|
|
return "SSL_RSA_WITH_IDEA_CBC_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_DES_CBC_MD5:
|
|
return "SSL_RSA_WITH_DES_CBC_MD5";
|
|
break;
|
|
case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
|
|
return "SSL_RSA_WITH_3DES_EDE_CBC_MD5";
|
|
break;
|
|
}
|
|
return "(NONE)";
|
|
}
|
|
|
|
static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
|
int sockindex)
|
|
{
|
|
struct SessionHandle *data = conn->data;
|
|
curl_socket_t sockfd = conn->sock[sockindex];
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
bool sni = true;
|
|
#ifdef ENABLE_IPV6
|
|
struct in6_addr addr;
|
|
#else
|
|
struct in_addr addr;
|
|
#endif
|
|
/*SSLConnectionRef ssl_connection;*/
|
|
OSStatus err = noErr;
|
|
|
|
if(connssl->ssl_ctx)
|
|
(void)SSLDisposeContext(connssl->ssl_ctx);
|
|
err = SSLNewContext(false, &(connssl->ssl_ctx));
|
|
if(err != noErr) {
|
|
failf(data, "SSL: couldn't create a context: OSStatus %d", err);
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
/* check to see if we've been told to use an explicit SSL/TLS version */
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, kSSLProtocolAll, false);
|
|
switch(data->set.ssl.version) {
|
|
default:
|
|
case CURL_SSLVERSION_DEFAULT:
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx,
|
|
kSSLProtocol3,
|
|
true);
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx,
|
|
kTLSProtocol1,
|
|
true);
|
|
break;
|
|
case CURL_SSLVERSION_TLSv1:
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx,
|
|
kTLSProtocol1,
|
|
true);
|
|
break;
|
|
case CURL_SSLVERSION_SSLv2:
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx,
|
|
kSSLProtocol2,
|
|
true);
|
|
break;
|
|
case CURL_SSLVERSION_SSLv3:
|
|
(void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx,
|
|
kSSLProtocol3,
|
|
true);
|
|
break;
|
|
}
|
|
|
|
/* No need to load certificates here. SecureTransport uses the Keychain
|
|
* (which is also part of the Security framework) to evaluate trust. */
|
|
|
|
/* SSL always tries to verify the peer, this only says whether it should
|
|
* fail to connect if the verification fails, or if it should continue
|
|
* anyway. In the latter case the result of the verification is checked with
|
|
* SSL_get_verify_result() below. */
|
|
err = SSLSetEnableCertVerify(connssl->ssl_ctx,
|
|
data->set.ssl.verifypeer?true:false);
|
|
if(err != noErr) {
|
|
failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err);
|
|
return CURLE_SSL_CONNECT_ERROR;
|
|
}
|
|
|
|
/* If this is a domain name and not an IP address, then configure SNI: */
|
|
if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) &&
|
|
#ifdef ENABLE_IPV6
|
|
(0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) &&
|
|
#endif
|
|
sni) {
|
|
err = SSLSetPeerDomainName(connssl->ssl_ctx, conn->host.name,
|
|
strlen(conn->host.name));
|
|
if(err != noErr) {
|
|
infof(data, "WARNING: SSL: SSLSetPeerDomainName() failed: OSStatus %d",
|
|
err);
|
|
}
|
|
}
|
|
|
|
err = SSLSetIOFuncs(connssl->ssl_ctx, SocketRead, SocketWrite);
|
|
if(err != noErr) {
|
|
failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err);
|
|
return CURLE_SSL_CONNECT_ERROR;
|
|
}
|
|
|
|
/* pass the raw socket into the SSL layers */
|
|
/* We need to store the FD in a constant memory address, because
|
|
* SSLSetConnection() will not copy that address. I've found that
|
|
* conn->sock[sockindex] may change on its own. */
|
|
connssl->ssl_sockfd = sockfd;
|
|
/*ssl_connection = &(connssl->ssl_sockfd);
|
|
err = SSLSetConnection(connssl->ssl_ctx, ssl_connection);*/
|
|
err = SSLSetConnection(connssl->ssl_ctx, connssl);
|
|
if(err != noErr) {
|
|
failf(data, "SSL: SSLSetConnection() failed: %d", err);
|
|
return CURLE_SSL_CONNECT_ERROR;
|
|
}
|
|
|
|
connssl->connecting_state = ssl_connect_2;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode
|
|
darwinssl_connect_step2(struct connectdata *conn, int sockindex)
|
|
{
|
|
struct SessionHandle *data = conn->data;
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
OSStatus err;
|
|
SSLCipherSuite cipher;
|
|
|
|
DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
|
|
|| ssl_connect_2_reading == connssl->connecting_state
|
|
|| ssl_connect_2_writing == connssl->connecting_state);
|
|
|
|
/* Here goes nothing: */
|
|
err = SSLHandshake(connssl->ssl_ctx);
|
|
|
|
if(err != noErr) {
|
|
switch (err) {
|
|
case errSSLWouldBlock: /* they're not done with us yet */
|
|
connssl->connecting_state = connssl->ssl_direction ?
|
|
ssl_connect_2_writing : ssl_connect_2_reading;
|
|
return CURLE_OK;
|
|
break;
|
|
|
|
case errSSLServerAuthCompleted:
|
|
/* the documentation says we need to call SSLHandshake() again */
|
|
return darwinssl_connect_step2(conn, sockindex);
|
|
|
|
case errSSLXCertChainInvalid:
|
|
case errSSLUnknownRootCert:
|
|
case errSSLNoRootCert:
|
|
case errSSLCertExpired:
|
|
failf(data, "SSL certificate problem: OSStatus %d", err);
|
|
return CURLE_SSL_CACERT;
|
|
break;
|
|
|
|
default:
|
|
failf(data, "Unknown SSL protocol error in connection to %s:%d",
|
|
conn->host.name, err);
|
|
return CURLE_SSL_CONNECT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* we have been connected fine, we're not waiting for anything else. */
|
|
connssl->connecting_state = ssl_connect_3;
|
|
|
|
/* Informational message */
|
|
(void)SSLGetNegotiatedCipher(connssl->ssl_ctx, &cipher);
|
|
infof (data, "SSL connection using %s\n", CipherNameForNumber(cipher));
|
|
|
|
return CURLE_OK;
|
|
}
|
|
}
|
|
|
|
static CURLcode
|
|
darwinssl_connect_step3(struct connectdata *conn,
|
|
int sockindex)
|
|
{
|
|
struct SessionHandle *data = conn->data;
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
CFStringRef server_cert_summary;
|
|
char server_cert_summary_c[128];
|
|
CFArrayRef server_certs;
|
|
SecCertificateRef server_cert;
|
|
OSStatus err;
|
|
CFIndex i, count;
|
|
|
|
/* There is no step 3!
|
|
* Well, okay, if verbose mode is on, let's print the details of the
|
|
* server certificates. */
|
|
err = SSLCopyPeerCertificates(connssl->ssl_ctx, &server_certs);
|
|
if(err == noErr) {
|
|
count = CFArrayGetCount(server_certs);
|
|
for(i = 0L ; i < count ; i++) {
|
|
server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i);
|
|
|
|
server_cert_summary = SecCertificateCopySubjectSummary(server_cert);
|
|
memset(server_cert_summary_c, 0, 128);
|
|
if(CFStringGetCString(server_cert_summary,
|
|
server_cert_summary_c,
|
|
128,
|
|
kCFStringEncodingUTF8)) {
|
|
infof(data, "Server certificate: %s\n", server_cert_summary_c);
|
|
}
|
|
CFRelease(server_cert_summary);
|
|
}
|
|
CFRelease(server_certs);
|
|
}
|
|
|
|
connssl->connecting_state = ssl_connect_done;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static Curl_recv darwinssl_recv;
|
|
static Curl_send darwinssl_send;
|
|
|
|
static CURLcode
|
|
darwinssl_connect_common(struct connectdata *conn,
|
|
int sockindex,
|
|
bool nonblocking,
|
|
bool *done)
|
|
{
|
|
CURLcode retcode;
|
|
struct SessionHandle *data = conn->data;
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
curl_socket_t sockfd = conn->sock[sockindex];
|
|
long timeout_ms;
|
|
int what;
|
|
|
|
/* check if the connection has already been established */
|
|
if(ssl_connection_complete == connssl->state) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
if(ssl_connect_1==connssl->connecting_state) {
|
|
/* Find out how much more time we're allowed */
|
|
timeout_ms = Curl_timeleft(data, NULL, TRUE);
|
|
|
|
if(timeout_ms < 0) {
|
|
/* no need to continue if time already is up */
|
|
failf(data, "SSL connection timeout");
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
retcode = darwinssl_connect_step1(conn, sockindex);
|
|
if(retcode)
|
|
return retcode;
|
|
}
|
|
|
|
while(ssl_connect_2 == connssl->connecting_state ||
|
|
ssl_connect_2_reading == connssl->connecting_state ||
|
|
ssl_connect_2_writing == connssl->connecting_state) {
|
|
|
|
/* check allowed time left */
|
|
timeout_ms = Curl_timeleft(data, NULL, TRUE);
|
|
|
|
if(timeout_ms < 0) {
|
|
/* no need to continue if time already is up */
|
|
failf(data, "SSL connection timeout");
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
|
|
/* if ssl is expecting something, check if it's available. */
|
|
if(connssl->connecting_state == ssl_connect_2_reading
|
|
|| connssl->connecting_state == ssl_connect_2_writing) {
|
|
|
|
curl_socket_t writefd = ssl_connect_2_writing ==
|
|
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
|
|
curl_socket_t readfd = ssl_connect_2_reading ==
|
|
connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
|
|
|
|
what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms);
|
|
if(what < 0) {
|
|
/* fatal error */
|
|
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
|
return CURLE_SSL_CONNECT_ERROR;
|
|
}
|
|
else if(0 == what) {
|
|
if(nonblocking) {
|
|
*done = FALSE;
|
|
return CURLE_OK;
|
|
}
|
|
else {
|
|
/* timeout */
|
|
failf(data, "SSL connection timeout");
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
}
|
|
/* socket is readable or writable */
|
|
}
|
|
|
|
/* Run transaction, and return to the caller if it failed or if this
|
|
* connection is done nonblocking and this loop would execute again. This
|
|
* permits the owner of a multi handle to abort a connection attempt
|
|
* before step2 has completed while ensuring that a client using select()
|
|
* or epoll() will always have a valid fdset to wait on.
|
|
*/
|
|
retcode = darwinssl_connect_step2(conn, sockindex);
|
|
if(retcode || (nonblocking &&
|
|
(ssl_connect_2 == connssl->connecting_state ||
|
|
ssl_connect_2_reading == connssl->connecting_state ||
|
|
ssl_connect_2_writing == connssl->connecting_state)))
|
|
return retcode;
|
|
|
|
} /* repeat step2 until all transactions are done. */
|
|
|
|
|
|
if(ssl_connect_3==connssl->connecting_state) {
|
|
retcode = darwinssl_connect_step3(conn, sockindex);
|
|
if(retcode)
|
|
return retcode;
|
|
}
|
|
|
|
if(ssl_connect_done==connssl->connecting_state) {
|
|
connssl->state = ssl_connection_complete;
|
|
conn->recv[sockindex] = darwinssl_recv;
|
|
conn->send[sockindex] = darwinssl_send;
|
|
*done = TRUE;
|
|
}
|
|
else
|
|
*done = FALSE;
|
|
|
|
/* Reset our connect state machine */
|
|
connssl->connecting_state = ssl_connect_1;
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
CURLcode
|
|
Curl_darwinssl_connect_nonblocking(struct connectdata *conn,
|
|
int sockindex,
|
|
bool *done)
|
|
{
|
|
return darwinssl_connect_common(conn, sockindex, TRUE, done);
|
|
}
|
|
|
|
CURLcode
|
|
Curl_darwinssl_connect(struct connectdata *conn,
|
|
int sockindex)
|
|
{
|
|
CURLcode retcode;
|
|
bool done = FALSE;
|
|
|
|
retcode = darwinssl_connect_common(conn, sockindex, FALSE, &done);
|
|
|
|
if(retcode)
|
|
return retcode;
|
|
|
|
DEBUGASSERT(done);
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
void Curl_darwinssl_close(struct connectdata *conn, int sockindex)
|
|
{
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
|
|
(void)SSLClose(connssl->ssl_ctx);
|
|
(void)SSLDisposeContext(connssl->ssl_ctx);
|
|
connssl->ssl_ctx = NULL;
|
|
connssl->ssl_sockfd = 0;
|
|
}
|
|
|
|
void Curl_darwinssl_close_all(struct SessionHandle *data)
|
|
{
|
|
/* SecureTransport doesn't separate sessions from contexts, so... */
|
|
(void)data;
|
|
}
|
|
|
|
int Curl_darwinssl_shutdown(struct connectdata *conn, int sockindex)
|
|
{
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
struct SessionHandle *data = conn->data;
|
|
ssize_t nread;
|
|
int what;
|
|
int rc;
|
|
char buf[120];
|
|
|
|
if(!connssl->ssl_ctx)
|
|
return 0;
|
|
|
|
if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE)
|
|
return 0;
|
|
|
|
Curl_darwinssl_close(conn, sockindex);
|
|
|
|
rc = 0;
|
|
|
|
what = Curl_socket_ready(conn->sock[sockindex],
|
|
CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
|
|
|
|
for(;;) {
|
|
if(what < 0) {
|
|
/* anything that gets here is fatally bad */
|
|
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
|
|
if(!what) { /* timeout */
|
|
failf(data, "SSL shutdown timeout");
|
|
break;
|
|
}
|
|
|
|
/* Something to read, let's do it and hope that it is the close
|
|
notify alert from the server. No way to SSL_Read now, so use read(). */
|
|
|
|
nread = read(conn->sock[sockindex], buf, sizeof(buf));
|
|
|
|
if(nread < 0) {
|
|
failf(data, "read: %s", strerror(errno));
|
|
rc = -1;
|
|
}
|
|
|
|
if(nread <= 0)
|
|
break;
|
|
|
|
what = Curl_socket_ready(conn->sock[sockindex], CURL_SOCKET_BAD, 0);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
size_t Curl_darwinssl_version(char *buffer, size_t size)
|
|
{
|
|
return snprintf(buffer, size, "SecureTransport");
|
|
}
|
|
|
|
/*
|
|
* This function uses SSLGetSessionState to determine connection status.
|
|
*
|
|
* Return codes:
|
|
* 1 means the connection is still in place
|
|
* 0 means the connection has been closed
|
|
* -1 means the connection status is unknown
|
|
*/
|
|
int Curl_darwinssl_check_cxn(struct connectdata *conn)
|
|
{
|
|
struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET];
|
|
OSStatus err;
|
|
SSLSessionState state;
|
|
|
|
if(connssl->ssl_ctx) {
|
|
err = SSLGetSessionState(connssl->ssl_ctx, &state);
|
|
if(err == noErr)
|
|
return state == kSSLConnected || state == kSSLHandshake;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Curl_darwinssl_data_pending(const struct connectdata *conn,
|
|
int connindex)
|
|
{
|
|
const struct ssl_connect_data *connssl = &conn->ssl[connindex];
|
|
OSStatus err;
|
|
size_t buffer;
|
|
|
|
if(connssl->ssl_ctx) { /* SSL is in use */
|
|
err = SSLGetBufferedReadSize(connssl->ssl_ctx, &buffer);
|
|
if(err == noErr)
|
|
return buffer > 0UL;
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Curl_darwinssl_random(struct SessionHandle *data,
|
|
unsigned char *entropy,
|
|
size_t length)
|
|
{
|
|
/* arc4random_buf() isn't available on cats older than Lion, so let's
|
|
do this manually for the benefit of the older cats. */
|
|
size_t i;
|
|
u_int32_t random = 0;
|
|
|
|
for(i = 0 ; i < length ; i++) {
|
|
if(i % sizeof(u_int32_t) == 0)
|
|
random = arc4random();
|
|
entropy[i] = random & 0xFF;
|
|
random >>= 8;
|
|
}
|
|
i = random = 0;
|
|
(void)data;
|
|
}
|
|
|
|
void Curl_darwinssl_md5sum(unsigned char *tmp, /* input */
|
|
size_t tmplen,
|
|
unsigned char *md5sum, /* output */
|
|
size_t md5len)
|
|
{
|
|
(void)md5len;
|
|
(void)CC_MD5(tmp, tmplen, md5sum);
|
|
}
|
|
|
|
static ssize_t darwinssl_send(struct connectdata *conn,
|
|
int sockindex,
|
|
const void *mem,
|
|
size_t len,
|
|
CURLcode *curlcode)
|
|
{
|
|
/*struct SessionHandle *data = conn->data;*/
|
|
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
|
size_t processed;
|
|
OSStatus err = SSLWrite(connssl->ssl_ctx, mem, len, &processed);
|
|
|
|
if(err != noErr) {
|
|
switch (err) {
|
|
case errSSLWouldBlock: /* we're not done yet; keep sending */
|
|
*curlcode = CURLE_AGAIN;
|
|
return -1;
|
|
break;
|
|
|
|
default:
|
|
failf(conn->data, "SSLWrite() return error %d", err);
|
|
*curlcode = CURLE_SEND_ERROR;
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
return (ssize_t)processed;
|
|
}
|
|
|
|
static ssize_t darwinssl_recv(struct connectdata *conn,
|
|
int num,
|
|
char *buf,
|
|
size_t buffersize,
|
|
CURLcode *curlcode)
|
|
{
|
|
/*struct SessionHandle *data = conn->data;*/
|
|
struct ssl_connect_data *connssl = &conn->ssl[num];
|
|
size_t processed;
|
|
OSStatus err = SSLRead(connssl->ssl_ctx, buf, buffersize, &processed);
|
|
|
|
if(err != noErr) {
|
|
switch (err) {
|
|
case errSSLWouldBlock: /* we're not done yet; keep reading */
|
|
*curlcode = CURLE_AGAIN;
|
|
return -1;
|
|
break;
|
|
|
|
default:
|
|
failf(conn->data, "SSLRead() return error %d", err);
|
|
*curlcode = CURLE_RECV_ERROR;
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
return (ssize_t)processed;
|
|
}
|
|
|
|
#endif /* USE_DARWINSSL */
|