Newer
Older
Daniel Stenberg
committed
static int asn1_output(const ASN1_UTCTIME *tm,
char *buf,
size_t sizeofbuf)
{
const char *asn1_string;
int gmt=FALSE;
int i;
int year=0,month=0,day=0,hour=0,minute=0,second=0;
i=tm->length;
asn1_string=(const char *)tm->data;
if(i < 10)
return 1;
if(asn1_string[i-1] == 'Z')
gmt=TRUE;
for(i=0; i<10; i++)
if((asn1_string[i] > '9') || (asn1_string[i] < '0'))
return 2;
year= (asn1_string[0]-'0')*10+(asn1_string[1]-'0');
if(year < 50)
year+=100;
month= (asn1_string[2]-'0')*10+(asn1_string[3]-'0');
if((month > 12) || (month < 1))
return 3;
day= (asn1_string[4]-'0')*10+(asn1_string[5]-'0');
hour= (asn1_string[6]-'0')*10+(asn1_string[7]-'0');
minute= (asn1_string[8]-'0')*10+(asn1_string[9]-'0');
if((asn1_string[10] >= '0') && (asn1_string[10] <= '9') &&
(asn1_string[11] >= '0') && (asn1_string[11] <= '9'))
second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0');
Daniel Stenberg
committed
Daniel Stenberg
committed
snprintf(buf, sizeofbuf,
"%04d-%02d-%02d %02d:%02d:%02d %s",
year+1900, month, day, hour, minute, second, (gmt?"GMT":""));
return 0;
}
/* ====================================================== */
Daniel Stenberg
committed
/*
* Match a hostname against a wildcard pattern.
* E.g.
* "foo.host.com" matches "*.host.com".
*
* We are a bit more liberal than RFC2818 describes in that we
* accept multiple "*" in pattern (similar to what some other browsers do).
* E.g.
* "abc.def.domain.com" should strickly not match "*.domain.com", but we
* don't consider "." to be important in CERT checking.
*/
#define HOST_NOMATCH 0
#define HOST_MATCH 1
Daniel Stenberg
committed
static int hostmatch(const char *hostname, const char *pattern)
char c = *pattern++;
Daniel Stenberg
committed
if(c == '\0')
return (*hostname ? HOST_NOMATCH : HOST_MATCH);
Daniel Stenberg
committed
if(c == '*') {
Daniel Stenberg
committed
if(c == '\0') /* "*\0" matches anything remaining */
Daniel Stenberg
committed
Daniel Stenberg
committed
while(*hostname) {
/* The only recursive function in libcurl! */
Daniel Stenberg
committed
if(hostmatch(hostname++,pattern) == HOST_MATCH)
return HOST_MATCH;
}
if(Curl_raw_toupper(c) != Curl_raw_toupper(*hostname++))
}
static int
Daniel Stenberg
committed
cert_hostcheck(const char *match_pattern, const char *hostname)
Daniel Stenberg
committed
if(!match_pattern || !*match_pattern ||
Daniel Stenberg
committed
!hostname || !*hostname) /* sanity check */
Daniel Stenberg
committed
if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */
Daniel Stenberg
committed
if(hostmatch(hostname,match_pattern) == HOST_MATCH)
return 0;
}
/* Quote from RFC2818 section 3.1 "Server Identity"
If a subjectAltName extension of type dNSName is present, that MUST
be used as the identity. Otherwise, the (most specific) Common Name
field in the Subject field of the certificate MUST be used. Although
the use of the Common Name is existing practice, it is deprecated and
Certification Authorities are encouraged to use the dNSName instead.
Matching is performed using the matching rules specified by
[RFC2459]. If more than one identity of a given type is present in
the certificate (e.g., more than one dNSName name, a match in any one
of the set is considered acceptable.) Names may contain the wildcard
character * which is considered to match any single domain name
component or component fragment. E.g., *.a.com matches foo.a.com but
not bar.foo.a.com. f*.com matches foo.com but not bar.com.
In some cases, the URI is specified as an IP address rather than a
hostname. In this case, the iPAddress subjectAltName must be present
in the certificate and must exactly match the IP in the URI.
Daniel Stenberg
committed
static CURLcode verifyhost(struct connectdata *conn,
X509 *server_cert)
Daniel Stenberg
committed
int matched = -1; /* -1 is no alternative match yet, 1 means match and 0
means mismatch */
int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
Yang Tse
committed
size_t addrlen = 0;
struct SessionHandle *data = conn->data;
STACK_OF(GENERAL_NAME) *altnames;
#ifdef ENABLE_IPV6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
Daniel Stenberg
committed
CURLcode res = CURLE_OK;
Daniel Stenberg
committed
#ifdef ENABLE_IPV6
Daniel Stenberg
committed
if(conn->bits.ipv6_ip &&
Curl_inet_pton(AF_INET6, conn->host.name, &addr)) {
target = GEN_IPADD;
addrlen = sizeof(struct in6_addr);
}
else
#endif
if(Curl_inet_pton(AF_INET, conn->host.name, &addr)) {
target = GEN_IPADD;
addrlen = sizeof(struct in_addr);
Daniel Stenberg
committed
/* get a "list" of alternative names */
Daniel Stenberg
committed
altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
Daniel Stenberg
committed
if(altnames) {
int numalts;
int i;
Daniel Stenberg
committed
/* get amount of alternatives, RFC2459 claims there MUST be at least
one, but we don't depend on it... */
numalts = sk_GENERAL_NAME_num(altnames);
/* loop through all alternatives while none has matched */
for(i=0; (i<numalts) && (matched != 1); i++) {
/* get a handle to alternative name number i */
const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
/* only check alternatives of the same type the target is */
if(check->type == target) {
/* get data and length */
const char *altptr = (char *)ASN1_STRING_data(check->d.ia5);
size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5);
case GEN_DNS: /* name/pattern comparison */
/* The OpenSSL man page explicitly says: "In general it cannot be
assumed that the data returned by ASN1_STRING_data() is null
terminated or does not contain embedded nulls." But also that
"The actual format of the data will depend on the actual string
type itself: for example for and IA5String the data will be ASCII"
Gisle researched the OpenSSL sources:
"I checked the 0.9.6 and 0.9.8 sources before my patch and
it always 0-terminates an IA5String."
*/
Daniel Stenberg
committed
if((altlen == strlen(altptr)) &&
/* if this isn't true, there was an embedded zero in the name
string and we cannot match it. */
cert_hostcheck(altptr, conn->host.name))
Daniel Stenberg
committed
matched = 1;
else
matched = 0;
Daniel Stenberg
committed
case GEN_IPADD: /* IP address comparison */
/* compare alternative IP address if the data chunk is the same size
our server IP address is */
if((altlen == addrlen) && !memcmp(altptr, &addr, altlen))
Daniel Stenberg
committed
matched = 1;
else
matched = 0;
}
}
}
Daniel Stenberg
committed
Daniel Stenberg
committed
if(matched == 1)
/* an alternative name matched the server hostname */
Daniel Stenberg
committed
infof(data, "\t subjectAltName: %s matched\n", conn->host.dispname);
Daniel Stenberg
committed
else if(matched == 0) {
Daniel Stenberg
committed
/* an alternative name field existed, but didn't match and then
we MUST fail */
infof(data, "\t subjectAltName does not match %s\n", conn->host.dispname);
res = CURLE_PEER_FAILED_VERIFICATION;
}
/* we have to look to the last occurrence of a commonName in the
Daniel Stenberg
committed
distinguished one to get the most significant one. */
int j,i=-1 ;
/* The following is done because of a bug in 0.9.6b */
Daniel Stenberg
committed
Daniel Stenberg
committed
unsigned char *nulstr = (unsigned char *)"";
unsigned char *peer_CN = nulstr;
Daniel Stenberg
committed
X509_NAME *name = X509_get_subject_name(server_cert) ;
Daniel Stenberg
committed
if(name)
while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0)
Daniel Stenberg
committed
i=j;
Daniel Stenberg
committed
/* we have the name entry and we will now convert this to a string
that we can use for comparison. Doing this we support BMPstring,
UTF8 etc. */
Daniel Stenberg
committed
if(i>=0) {
ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i));
/* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
is already UTF-8 encoded. We check for this case and copy the raw
string manually to avoid the problem. This code can be made
conditional in the future when OpenSSL has been fixed. Work-around
brought by Alexis S. L. Carvalho. */
if(tmp) {
if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
Guenter Knauf
committed
j = ASN1_STRING_length(tmp);
Daniel Stenberg
committed
if(j >= 0) {
peer_CN = OPENSSL_malloc(j+1);
if(peer_CN) {
Daniel Stenberg
committed
memcpy(peer_CN, ASN1_STRING_data(tmp), j);
peer_CN[j] = '\0';
}
}
}
else /* not a UTF8 name */
j = ASN1_STRING_to_UTF8(&peer_CN, tmp);
Daniel Stenberg
committed
if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != j)) {
Daniel Stenberg
committed
/* there was a terminating zero before the end of string, this
cannot match and we return failure! */
failf(data, "SSL: illegal cert name field");
res = CURLE_PEER_FAILED_VERIFICATION;
}
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
Daniel Stenberg
committed
if(peer_CN == nulstr)
Daniel Stenberg
committed
peer_CN = NULL;
else {
/* convert peer_CN from UTF8 */
CURLcode rc = Curl_convert_from_utf8(data, peer_CN, strlen(peer_CN));
/* Curl_convert_from_utf8 calls failf if unsuccessful */
Daniel Stenberg
committed
OPENSSL_free(peer_CN);
return rc;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(res)
/* error already detected, pass through */
;
else if(!peer_CN) {
Daniel Stenberg
committed
failf(data,
"SSL: unable to obtain common name from peer certificate");
Daniel Stenberg
committed
res = CURLE_PEER_FAILED_VERIFICATION;
else if(!cert_hostcheck((const char *)peer_CN, conn->host.name)) {
Daniel Stenberg
committed
if(data->set.ssl.verifyhost > 1) {
failf(data, "SSL: certificate subject name '%s' does not match "
"target host name '%s'", peer_CN, conn->host.dispname);
res = CURLE_PEER_FAILED_VERIFICATION;
}
else
Daniel Stenberg
committed
infof(data, "\t common name: %s (does not match '%s')\n",
peer_CN, conn->host.dispname);
Daniel Stenberg
committed
else {
infof(data, "\t common name: %s (matched)\n", peer_CN);
}
Daniel Stenberg
committed
if(peer_CN)
OPENSSL_free(peer_CN);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
return res;
#endif /* USE_SSLEAY */
Daniel Stenberg
committed
/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions
and thus this cannot be done there. */
#ifdef SSL_CTRL_SET_MSG_CALLBACK
static const char *ssl_msg_type(int ssl_ver, int msg)
{
Daniel Stenberg
committed
if(ssl_ver == SSL2_VERSION_MAJOR) {
Daniel Stenberg
committed
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
switch (msg) {
case SSL2_MT_ERROR:
return "Error";
case SSL2_MT_CLIENT_HELLO:
return "Client hello";
case SSL2_MT_CLIENT_MASTER_KEY:
return "Client key";
case SSL2_MT_CLIENT_FINISHED:
return "Client finished";
case SSL2_MT_SERVER_HELLO:
return "Server hello";
case SSL2_MT_SERVER_VERIFY:
return "Server verify";
case SSL2_MT_SERVER_FINISHED:
return "Server finished";
case SSL2_MT_REQUEST_CERTIFICATE:
return "Request CERT";
case SSL2_MT_CLIENT_CERTIFICATE:
return "Client CERT";
}
}
Daniel Stenberg
committed
else if(ssl_ver == SSL3_VERSION_MAJOR) {
Daniel Stenberg
committed
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
switch (msg) {
case SSL3_MT_HELLO_REQUEST:
return "Hello request";
case SSL3_MT_CLIENT_HELLO:
return "Client hello";
case SSL3_MT_SERVER_HELLO:
return "Server hello";
case SSL3_MT_CERTIFICATE:
return "CERT";
case SSL3_MT_SERVER_KEY_EXCHANGE:
return "Server key exchange";
case SSL3_MT_CLIENT_KEY_EXCHANGE:
return "Client key exchange";
case SSL3_MT_CERTIFICATE_REQUEST:
return "Request CERT";
case SSL3_MT_SERVER_DONE:
return "Server finished";
case SSL3_MT_CERTIFICATE_VERIFY:
return "CERT verify";
case SSL3_MT_FINISHED:
return "Finished";
}
}
return "Unknown";
}
static const char *tls_rt_type(int type)
{
return (
type == SSL3_RT_CHANGE_CIPHER_SPEC ? "TLS change cipher, " :
type == SSL3_RT_ALERT ? "TLS alert, " :
type == SSL3_RT_HANDSHAKE ? "TLS handshake, " :
type == SSL3_RT_APPLICATION_DATA ? "TLS app data, " :
"TLS Unknown, ");
}
/*
* Our callback from the SSL/TLS layers.
*/
static void ssl_tls_trace(int direction, int ssl_ver, int content_type,
const void *buf, size_t len, const SSL *ssl,
struct connectdata *conn)
{
Daniel Stenberg
committed
const char *msg_name, *tls_rt_name;
char ssl_buf[1024];
int ver, msg_type, txt_len;
Daniel Stenberg
committed
if(!conn || !conn->data || !conn->data->set.fdebug ||
Daniel Stenberg
committed
return;
data = conn->data;
ssl_ver >>= 8;
ver = (ssl_ver == SSL2_VERSION_MAJOR ? '2' :
ssl_ver == SSL3_VERSION_MAJOR ? '3' : '?');
/* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL
* always pass-up content-type as 0. But the interesting message-type
Daniel Stenberg
committed
* is at 'buf[0]'.
*/
Daniel Stenberg
committed
if(ssl_ver == SSL3_VERSION_MAJOR && content_type != 0)
Daniel Stenberg
committed
tls_rt_name = tls_rt_type(content_type);
else
tls_rt_name = "";
msg_type = *(char*)buf;
msg_name = ssl_msg_type(ssl_ver, msg_type);
txt_len = snprintf(ssl_buf, sizeof(ssl_buf), "SSLv%c, %s%s (%d):\n",
ver, tls_rt_name, msg_name, msg_type);
Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len, NULL);
Daniel Stenberg
committed
Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL);
Daniel Stenberg
committed
(void) ssl;
}
#endif
Daniel Stenberg
committed
#ifdef USE_SSLEAY
/* ====================================================== */
Daniel Stenberg
committed
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
# define use_sni(x) sni = (x)
#else
Daniel Stenberg
committed
static CURLcode
ossl_connect_step1(struct connectdata *conn,
int sockindex)
Daniel Stenberg
committed
CURLcode retcode = CURLE_OK;
Daniel Stenberg
committed
struct SessionHandle *data = conn->data;
SSL_METHOD_QUAL SSL_METHOD *req_method=NULL;
Daniel Stenberg
committed
void *ssl_sessionid=NULL;
Daniel Stenberg
committed
X509_LOOKUP *lookup=NULL;
curl_socket_t sockfd = conn->sock[sockindex];
Daniel Stenberg
committed
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
long ctx_options;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
#ifdef ENABLE_IPV6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
#endif
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
Daniel Stenberg
committed
/* Make funny stuff to get random input */
Curl_ossl_seed(data);
/* check to see if we've been told to use an explicit SSL/TLS version */
Daniel Stenberg
committed
switch(data->set.ssl.version) {
case CURL_SSLVERSION_DEFAULT:
if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) {
infof(data, "Set version TLSv1 for SRP authorisation\n");
req_method = TLSv1_client_method() ;
/* we try to figure out version */
req_method = SSLv23_client_method();
case CURL_SSLVERSION_TLSv1:
req_method = TLSv1_client_method();
break;
case CURL_SSLVERSION_SSLv2:
#ifdef OPENSSL_NO_SSL2
failf(data, "OpenSSL was built without SSLv2 support");
return CURLE_NOT_BUILT_IN;
#else
if(data->set.ssl.authtype == CURL_TLSAUTH_SRP)
return CURLE_SSL_CONNECT_ERROR;
#endif
req_method = SSLv2_client_method();
case CURL_SSLVERSION_SSLv3:
if(data->set.ssl.authtype == CURL_TLSAUTH_SRP)
return CURLE_SSL_CONNECT_ERROR;
#endif
req_method = SSLv3_client_method();
Daniel Stenberg
committed
Daniel Stenberg
committed
if(connssl->ctx)
Daniel Stenberg
committed
SSL_CTX_free(connssl->ctx);
Daniel Stenberg
committed
connssl->ctx = SSL_CTX_new(req_method);
Daniel Stenberg
committed
if(!connssl->ctx) {
failf(data, "SSL: couldn't create a context: %s",
ERR_error_string(ERR_peek_error(), NULL));
Daniel Stenberg
committed
return CURLE_OUT_OF_MEMORY;
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(connssl->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
Daniel Stenberg
committed
#ifdef SSL_CTRL_SET_MSG_CALLBACK
Daniel Stenberg
committed
if(data->set.fdebug && data->set.verbose) {
/* the SSL trace callback is only used for verbose logging so we only
inform about failures of setting it */
Daniel Stenberg
committed
if(!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK,
Daniel Stenberg
committed
else if(!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0,
infof(data, "SSL: couldn't set callback argument!\n");
Daniel Stenberg
committed
}
#endif
/* OpenSSL contains code to work-around lots of bugs and flaws in various
SSL-implementations. SSL_CTX_set_options() is used to enabled those
work-arounds. The man page for this option states that SSL_OP_ALL enables
Daniel Stenberg
committed
all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
enable the bug workaround options if compatibility with somewhat broken
implementations is desired."
The "-no_ticket" option was introduced in Openssl0.9.8j. It's a flag to
disable "rfc4507bis session ticket support". rfc4507bis was later turned
into the proper RFC5077 it seems: http://tools.ietf.org/html/rfc5077
The enabled extension concerns the session management. I wonder how often
libcurl stops a connection and then resumes a TLS session. also, sending
the session data is some overhead. .I suggest that you just use your
proposed patch (which explicitly disables TICKET).
If someone writes an application with libcurl and openssl who wants to
enable the feature, one can do this in the SSL callback.
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
interoperability with web server Netscape Enterprise Server 2.0.1 which
was released back in 1996.
Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
CVE-2010-4180 when using previous OpenSSL versions we no longer enable
this option regardless of OpenSSL version and SSL_OP_ALL definition.
OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
(http://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
SSL_OP_ALL that _disables_ that work-around despite the fact that
SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
must not be set.
*/
ctx_options = SSL_OP_ALL;
#ifdef SSL_OP_NO_TICKET
ctx_options |= SSL_OP_NO_TICKET;
#endif
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
/* mitigate CVE-2010-4180 */
ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
#endif
Daniel Stenberg
committed
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
/* unless the user explicitly ask to allow the protocol vulnerability we
use the work-around */
if(!conn->data->set.ssl_enable_beast)
ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
/* disable SSLv2 in the default case (i.e. allow SSLv3 and TLSv1) */
if(data->set.ssl.version == CURL_SSLVERSION_DEFAULT)
ctx_options |= SSL_OP_NO_SSLv2;
SSL_CTX_set_options(connssl->ctx, ctx_options);
Daniel Stenberg
committed
#if 0
/*
* Not sure it's needed to tell SSL_connect() that socket is
* non-blocking. It doesn't seem to care, but just return with
* SSL_ERROR_WANT_x.
*/
Daniel Stenberg
committed
if(data->state.used_interface == Curl_if_multi)
Daniel Stenberg
committed
SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL);
#endif
Daniel Stenberg
committed
if(data->set.str[STRING_CERT] || data->set.str[STRING_CERT_TYPE]) {
if(!cert_stuff(conn,
Daniel Stenberg
committed
connssl->ctx,
Daniel Stenberg
committed
data->set.str[STRING_CERT],
data->set.str[STRING_CERT_TYPE],
data->set.str[STRING_KEY],
data->set.str[STRING_KEY_TYPE])) {
/* failf() is already done in cert_stuff() */
return CURLE_SSL_CERTPROBLEM;
Daniel Stenberg
committed
if(data->set.str[STRING_SSL_CIPHER_LIST]) {
Daniel Stenberg
committed
if(!SSL_CTX_set_cipher_list(connssl->ctx,
Daniel Stenberg
committed
data->set.str[STRING_SSL_CIPHER_LIST])) {
failf(data, "failed setting cipher list");
return CURLE_SSL_CIPHER;
#ifdef USE_TLS_SRP
if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) {
infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username);
if(!SSL_CTX_set_srp_username(connssl->ctx, data->set.ssl.username)) {
failf(data, "Unable to set SRP user name");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!SSL_CTX_set_srp_password(connssl->ctx,data->set.ssl.password)) {
failf(data, "failed setting SRP password");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!data->set.str[STRING_SSL_CIPHER_LIST]) {
infof(data, "Setting cipher list SRP\n");
if(!SSL_CTX_set_cipher_list(connssl->ctx, "SRP")) {
failf(data, "failed setting SRP cipher list");
return CURLE_SSL_CIPHER;
}
}
}
#endif
Daniel Stenberg
committed
if(data->set.str[STRING_SSL_CAFILE] || data->set.str[STRING_SSL_CAPATH]) {
/* tell SSL where to find CA certificates that are used to verify
the servers certificate. */
Daniel Stenberg
committed
if(!SSL_CTX_load_verify_locations(connssl->ctx,
Daniel Stenberg
committed
data->set.str[STRING_SSL_CAFILE],
data->set.str[STRING_SSL_CAPATH])) {
Daniel Stenberg
committed
if(data->set.ssl.verifypeer) {
/* Fail if we insist on successfully verifying the server. */
failf(data,"error setting certificate verify locations:\n"
" CAfile: %s\n CApath: %s\n",
Daniel Stenberg
committed
data->set.str[STRING_SSL_CAFILE]?
data->set.str[STRING_SSL_CAFILE]: "none",
data->set.str[STRING_SSL_CAPATH]?
data->set.str[STRING_SSL_CAPATH] : "none");
return CURLE_SSL_CACERT_BADFILE;
}
else {
/* Just continue with a warning if no strict certificate verification
is required. */
Daniel Stenberg
committed
infof(data, "error setting certificate verify locations,"
" continuing anyway:\n");
}
}
else {
/* Everything is fine. */
Daniel Stenberg
committed
infof(data, "successfully set certificate verify locations:\n");
Daniel Stenberg
committed
}
Daniel Stenberg
committed
infof(data,
" CAfile: %s\n"
" CApath: %s\n",
Daniel Stenberg
committed
data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]:
"none",
data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]:
"none");
Daniel Stenberg
committed
if(data->set.str[STRING_SSL_CRLFILE]) {
Daniel Stenberg
committed
/* tell SSL where to find CRL file that is used to check certificate
* revocation */
lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx),
X509_LOOKUP_file());
if(!lookup ||
(!X509_load_crl_file(lookup,data->set.str[STRING_SSL_CRLFILE],
X509_FILETYPE_PEM)) ) {
failf(data,"error loading CRL file: %s\n",
data->set.str[STRING_SSL_CRLFILE]);
Daniel Stenberg
committed
return CURLE_SSL_CRL_BADFILE;
}
else {
/* Everything is fine. */
infof(data, "successfully load CRL file:\n");
X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx),
Daniel Stenberg
committed
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
Daniel Stenberg
committed
}
infof(data,
" CRLfile: %s\n", data->set.str[STRING_SSL_CRLFILE] ?
Daniel Stenberg
committed
data->set.str[STRING_SSL_CRLFILE]: "none");
Daniel Stenberg
committed
}
/* 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. */
Daniel Stenberg
committed
SSL_CTX_set_verify(connssl->ctx,
data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE,
cert_verify_callback);
Daniel Stenberg
committed
/* give application a chance to interfere with SSL set up. */
if(data->set.ssl.fsslctx) {
Daniel Stenberg
committed
retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx,
data->set.ssl.fsslctxp);
if(retcode) {
failf(data,"error signaled by ssl ctx callback");
return retcode;
}
}
/* Lets make an SSL structure */
Daniel Stenberg
committed
if(connssl->handle)
Daniel Stenberg
committed
SSL_free(connssl->handle);
Daniel Stenberg
committed
connssl->handle = SSL_new(connssl->ctx);
Daniel Stenberg
committed
if(!connssl->handle) {
failf(data, "SSL: couldn't create a context (handle)!");
return CURLE_OUT_OF_MEMORY;
}
Daniel Stenberg
committed
SSL_set_connect_state(connssl->handle);
Daniel Stenberg
committed
connssl->server_cert = 0x0;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) &&
#ifdef ENABLE_IPV6
(0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) &&
sni &&
!SSL_set_tlsext_host_name(connssl->handle, conn->host.name))
infof(data, "WARNING: failed to configure server name indication (SNI) "
"TLS extension\n");
#endif
Daniel Stenberg
committed
/* Check if there's a cached ID we can/should use here! */
if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
/* we got a session id, use it! */
Daniel Stenberg
committed
if(!SSL_set_session(connssl->handle, ssl_sessionid)) {
Daniel Stenberg
committed
failf(data, "SSL: SSL_set_session failed: %s",
ERR_error_string(ERR_get_error(),NULL));
return CURLE_SSL_CONNECT_ERROR;
}
Daniel Stenberg
committed
/* Informational message */
infof (data, "SSL re-using session ID\n");
}
/* pass the raw socket into the SSL layers */
if(!SSL_set_fd(connssl->handle, (int)sockfd)) {
failf(data, "SSL: SSL_set_fd failed: %s",
ERR_error_string(ERR_get_error(),NULL));
return CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
Daniel Stenberg
committed
static CURLcode
ossl_connect_step2(struct connectdata *conn, int sockindex)
Daniel Stenberg
committed
{
struct SessionHandle *data = conn->data;
int err;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
Daniel Stenberg
committed
DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
Daniel Stenberg
committed
|| ssl_connect_2_reading == connssl->connecting_state
|| ssl_connect_2_writing == connssl->connecting_state);
Daniel Stenberg
committed
ERR_clear_error();
Daniel Stenberg
committed
err = SSL_connect(connssl->handle);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* 1 is fine
0 is "not successful but was shut down controlled"
<0 is "handshake was not successful, because a fatal error occurred" */
if(1 != err) {
int detail = SSL_get_error(connssl->handle, err);
Daniel Stenberg
committed
Daniel Stenberg
committed
if(SSL_ERROR_WANT_READ == detail) {
connssl->connecting_state = ssl_connect_2_reading;
return CURLE_OK;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
else if(SSL_ERROR_WANT_WRITE == detail) {
connssl->connecting_state = ssl_connect_2_writing;
return CURLE_OK;
}
else {
/* untreated error */
unsigned long errdetail;
char error_buffer[256]; /* OpenSSL documents that this must be at least
256 bytes long. */
Daniel Stenberg
committed
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
CURLcode rc;
const char *cert_problem = NULL;
connssl->connecting_state = ssl_connect_2; /* the connection failed,
we're not waiting for
anything else. */
errdetail = ERR_get_error(); /* Gets the earliest error code from the
thread's error queue and removes the
entry. */
switch(errdetail) {
case 0x1407E086:
/* 1407E086:
SSL routines:
SSL2_SET_CERTIFICATE:
certificate verify failed */
/* fall-through */
case 0x14090086:
/* 14090086:
SSL routines:
SSL3_GET_SERVER_CERTIFICATE:
certificate verify failed */
cert_problem = "SSL certificate problem, verify that the CA cert is"
" OK. Details:\n";
rc = CURLE_SSL_CACERT;
break;
default:
rc = CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
break;
}
Daniel Stenberg
committed
/* detail is already set to the SSL error above */
/* If we e.g. use SSLv2 request-method and the server doesn't like us
* (RST connection etc.), OpenSSL gives no explanation whatsoever and
* the SO_ERROR is also lost.
*/
Daniel Stenberg
committed
if(CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) {
Daniel Stenberg
committed
conn->host.name, conn->port);
return rc;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/* Could be a CERT problem */
SSL_strerror(errdetail, error_buffer, sizeof(error_buffer));
failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer);
return rc;
}
}
else {
/* we have been connected fine, we're not waiting for anything else. */
connssl->connecting_state = ssl_connect_3;
/* Informational message */
infof (data, "SSL connection using %s\n",
SSL_get_cipher(connssl->handle));
return CURLE_OK;
}
}
Daniel Stenberg
committed
static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len)
{
int i, ilen;
if((ilen = (int)len) < 0)
return 1; /* buffer too big */
i = i2t_ASN1_OBJECT(buf, ilen, a);
if(i >= ilen)
return 1; /* buffer too small */
Daniel Stenberg
committed
return 0;
}
static CURLcode push_certinfo_len(struct SessionHandle *data,
int certnum,
const char *label,
const char *value,
size_t valuelen)
{
struct curl_certinfo *ci = &data->info.certs;
Daniel Stenberg
committed
struct curl_slist *nl;
CURLcode res = CURLE_OK;
size_t labellen = strlen(label);
size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */
output = malloc(outlen);
if(!output)
Daniel Stenberg
committed
return CURLE_OUT_OF_MEMORY;
/* sprintf the label and colon */
Daniel Stenberg
committed
/* memcpy the value (it might not be zero terminated) */
memcpy(&output[labellen+1], value, valuelen);
Daniel Stenberg
committed
/* zero terminate the output */
Daniel Stenberg
committed
/* TODO: we should rather introduce an internal API that can do the
equivalent of curl_slist_append but doesn't strdup() the given data as
like in this place the extra malloc/free is totally pointless */
nl = curl_slist_append(ci->certinfo[certnum], output);
Daniel Stenberg
committed
if(!nl) {
curl_slist_free_all(ci->certinfo[certnum]);
Daniel Stenberg
committed
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
res = CURLE_OUT_OF_MEMORY;
}
else
ci->certinfo[certnum] = nl;
return res;
}
/* this is a convenience function for push_certinfo_len that takes a zero
terminated value */
static CURLcode push_certinfo(struct SessionHandle *data,
int certnum,
const char *label,
const char *value)
{
size_t valuelen = strlen(value);
return push_certinfo_len(data, certnum, label, value, valuelen);
}
static void pubkey_show(struct SessionHandle *data,
int num,
const char *type,
const char *name,
unsigned char *raw,
int len)
{
Daniel Stenberg
committed
int i;
char namebuf[32];
char *buffer;
buffer = malloc(left);
if(buffer) {
char *ptr=buffer;
snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
for(i=0; i< len; i++) {
snprintf(ptr, left, "%02x:", raw[i]);
ptr += 3;
left -= 3;
}
infof(data, " %s: %s\n", namebuf, buffer);
push_certinfo(data, num, namebuf, buffer);
free(buffer);
Daniel Stenberg
committed
}
}
#define print_pubkey_BN(_type, _name, _num) \
do { \
if(pubkey->pkey._type->_name != NULL) { \
int len = BN_num_bytes(pubkey->pkey._type->_name); \
if(len < CERTBUFFERSIZE) { \
BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)bufp); \
pubkey_show(data, _num, #_type, #_name, (unsigned char*)bufp, len); \
Daniel Stenberg
committed
} \
} \
Daniel Stenberg
committed
static int X509V3_ext(struct SessionHandle *data,
int certnum,
STACK_OF(X509_EXTENSION) *exts)
{
int i;
size_t j;
Daniel Stenberg
committed
if(sk_X509_EXTENSION_num(exts) <= 0)
/* no extensions, bail out */
return 1;
for(i=0; i<sk_X509_EXTENSION_num(exts); i++) {
Daniel Stenberg
committed
ASN1_OBJECT *obj;
X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
BUF_MEM *biomem;
char buf[512];
char *ptr=buf;
char namebuf[128];
Daniel Stenberg
committed
BIO *bio_out = BIO_new(BIO_s_mem());
if(!bio_out)
return 1;
Daniel Stenberg
committed
obj = X509_EXTENSION_get_object(ext);
asn1_object_dump(obj, namebuf, sizeof(namebuf));
infof(data, "%s: %s\n", namebuf,
X509_EXTENSION_get_critical(ext)?"(critical)":"");
if(!X509V3_EXT_print(bio_out, ext, 0, 0))
M_ASN1_OCTET_STRING_print(bio_out, ext->value);
BIO_get_mem_ptr(bio_out, &biomem);
/* biomem->length bytes at biomem->data, this little loop here is only