diff options
-rw-r--r-- | lib/axtls.c | 376 | ||||
-rw-r--r-- | lib/axtls.h | 10 | ||||
-rwxr-xr-x | tests/runtests.pl | 12 |
3 files changed, 374 insertions, 24 deletions
diff --git a/lib/axtls.c b/lib/axtls.c index 9a253bc1e..ff4ff2b15 100644 --- a/lib/axtls.c +++ b/lib/axtls.c @@ -50,17 +50,93 @@ /* The last #include file should be: */ #include "memdebug.h" +/* SSL_read is opied from axTLS compat layer */ +static int SSL_read(SSL *ssl, void *buf, int num) +{ + uint8_t *read_buf; + int ret; + + while((ret = ssl_read(ssl, &read_buf)) == SSL_OK); + + if(ret > SSL_OK){ + memcpy(buf, read_buf, ret > num ? num : ret); + } + + return ret; +} + /* Global axTLS init, called from Curl_ssl_init() */ int Curl_axtls_init(void) { +/* axTLS has no global init. Everything is done through SSL and SSL_CTX + * structs stored in connectdata structure. Perhaps can move to axtls.h. + */ return 1; } int Curl_axtls_cleanup(void) { + /* axTLS has no global cleanup. Perhaps can move this to axtls.h. */ return 1; } +static CURLcode map_error_to_curl(int axtls_err) +{ + switch (axtls_err) + { + case SSL_ERROR_NOT_SUPPORTED: + case SSL_ERROR_INVALID_VERSION: + case -70: /* protocol version alert from server */ + return CURLE_UNSUPPORTED_PROTOCOL; + break; + case SSL_ERROR_NO_CIPHER: + return CURLE_SSL_CIPHER; + break; + case SSL_ERROR_BAD_CERTIFICATE: /* this may be bad server cert too */ + case SSL_ERROR_NO_CERT_DEFINED: + case -42: /* bad certificate alert from server */ + case -43: /* unsupported cert alert from server */ + case -44: /* cert revoked alert from server */ + case -45: /* cert expired alert from server */ + case -46: /* cert unknown alert from server */ + return CURLE_SSL_CERTPROBLEM; + break; + case SSL_X509_ERROR(X509_NOT_OK): + case SSL_X509_ERROR(X509_VFY_ERROR_NO_TRUSTED_CERT): + case SSL_X509_ERROR(X509_VFY_ERROR_BAD_SIGNATURE): + case SSL_X509_ERROR(X509_VFY_ERROR_NOT_YET_VALID): + case SSL_X509_ERROR(X509_VFY_ERROR_EXPIRED): + case SSL_X509_ERROR(X509_VFY_ERROR_SELF_SIGNED): + case SSL_X509_ERROR(X509_VFY_ERROR_INVALID_CHAIN): + case SSL_X509_ERROR(X509_VFY_ERROR_UNSUPPORTED_DIGEST): + case SSL_X509_ERROR(X509_INVALID_PRIV_KEY): + return CURLE_PEER_FAILED_VERIFICATION; + break; + case -48: /* unknown ca alert from server */ + return CURLE_SSL_CACERT; + break; + case -49: /* access denied alert from server */ + return CURLE_REMOTE_ACCESS_DENIED; + break; + case SSL_ERROR_CONN_LOST: + case SSL_ERROR_SOCK_SETUP_FAILURE: + case SSL_ERROR_INVALID_HANDSHAKE: + case SSL_ERROR_INVALID_PROT_MSG: + case SSL_ERROR_INVALID_HMAC: + case SSL_ERROR_INVALID_SESSION: + case SSL_ERROR_INVALID_KEY: /* it's too bad this doesn't map better */ + case SSL_ERROR_FINISHED_INVALID: + case SSL_ERROR_NO_CLIENT_RENOG: + default: + return CURLE_SSL_CONNECT_ERROR; + break; + } + return CURLE_SSL_CONNECT_ERROR; /* catch-all for non-easily-mapped errors */ +} + +static Curl_recv axtls_recv; +static Curl_send axtls_send; + /* * This function is called after the TCP connect has completed. Setup the TLS * layer and do all necessary magic. @@ -70,25 +146,232 @@ Curl_axtls_connect(struct connectdata *conn, int sockindex) { + struct SessionHandle *data = conn->data; + SSL_CTX *ssl_ctx; + SSL *ssl; + int cert_types[] = {SSL_OBJ_X509_CERT, SSL_OBJ_PKCS12, 0}; + int key_types[] = {SSL_OBJ_RSA_KEY, SSL_OBJ_PKCS8, SSL_OBJ_PKCS12, 0}; + int i, ssl_fcn_return; + const uint8_t *ssl_sessionid; + size_t ssl_idsize; + const char *x509; + + /* Assuming users will not compile in custom key/cert to axTLS */ + uint32_t client_option = SSL_NO_DEFAULT_KEY; + + if(conn->ssl[sockindex].state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + /* axTLS only supports TLSv1 */ + if(data->set.ssl.version != CURL_SSLVERSION_TLSv1) { + failf(data, "axTLS only supports TLSv1"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef AXTLSDEBUG + client_option |= SSL_DISPLAY_STATES | SSL_DISPLAY_RSA | SSL_DISPLAY_CERTS; +#endif /* AXTLSDEBUG */ + + /* Allocate an SSL_CTX struct */ + ssl_ctx = ssl_ctx_new(client_option, SSL_DEFAULT_CLNT_SESS); + if(ssl_ctx == NULL) { + failf(data, "unable to create client SSL context"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* Load the trusted CA cert bundle file */ + if(data->set.ssl.CAfile) { + if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, data->set.ssl.CAfile, NULL) \ + != SSL_OK){ + infof(data, "error reading ca cert file %s \n", + data->set.ssl.CAfile); + if(data->set.ssl.verifypeer){ + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found certificates in %s\n", data->set.ssl.CAfile); + } + + /* gtls.c tasks we're skipping for now: + * 1) certificate revocation list checking + * 2) dns name assignment to host + * 3) set protocol priority. axTLS is TLSv1 only, so can probably ignore + * 4) set certificate priority. axTLS ignores type and sends certs in + * order added. can probably ignore this. + */ + + /* Load client certificate */ + if(data->set.str[STRING_CERT]){ + i=0; + /* Instead of trying to analyze cert type here, let axTLS try them all. */ + while(cert_types[i] != 0){ + ssl_fcn_return = ssl_obj_load(ssl_ctx, cert_types[i], \ + data->set.str[STRING_CERT], NULL); + if(ssl_fcn_return == SSL_OK){ + infof(data, "successfully read cert file %s \n", \ + data->set.str[STRING_CERT]); + break; + } + i++; + } + /* Tried all cert types, none worked. */ + if(cert_types[i] == 0){ + failf(data, "%s is not x509 or pkcs12 format", \ + data->set.str[STRING_CERT]); + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Load client key. + If a pkcs12 file successfully loaded a cert, then there's nothing to do + because the key has already been loaded. */ + if(data->set.str[STRING_KEY] && cert_types[i] != SSL_OBJ_PKCS12){ + i=0; + /* Instead of trying to analyze key type here, let axTLS try them all. */ + while(key_types[i] != 0){ + ssl_fcn_return = ssl_obj_load(ssl_ctx, key_types[i], \ + data->set.str[STRING_KEY], NULL); + if(ssl_fcn_return == SSL_OK){ + infof(data, "successfully read key file %s \n", \ + data->set.str[STRING_KEY]); + break; + } + i++; + } + /* Tried all key types, none worked. */ + if(key_types[i] == 0){ + failf(data, "Failure: %s is not a supported key file", \ + data->set.str[STRING_KEY]); + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* gtls.c does more here that is being left out for now + * 1) set session credentials. can probably ignore since axtls puts this + * info in the ssl_ctx struct + * 2) setting up callbacks. these seem gnutls specific + */ + + /* In axTLS, handshaking happens inside ssl_client_new. */ + if(!Curl_ssl_getsessionid(conn, (void **) &ssl_sessionid, &ssl_idsize)){ + /* we got a session id, use it! */ + infof (data, "SSL re-using session ID\n"); + ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], \ + ssl_sessionid, ssl_idsize); + } + else + ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0); + + /* Check to make sure handshake was ok. */ + ssl_fcn_return = ssl_handshake_status(ssl); + if(ssl_fcn_return != SSL_OK){ + Curl_axtls_close(conn, sockindex); + ssl_display_error(ssl_fcn_return); /* goes to stdout. */ + return map_error_to_curl(ssl_fcn_return); + } + infof (data, "handshake completed successfully\n"); + + /* Here, gtls.c gets the peer certificates and fails out depending on + * settings in "data." axTLS api doesn't have get cert chain fcn, so omit? + */ + + /* Verify server's certificate */ + if(data->set.ssl.verifypeer){ + if(ssl_verify_cert(ssl) != SSL_OK){ + Curl_axtls_close(conn, sockindex); + failf(data, "server cert verify failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else + infof(data, "\t server certificate verification SKIPPED\n"); + + /* Here, gtls.c does issuer verfication. axTLS has no straightforward + * equivalent, so omitting for now.*/ + + /* See if common name was set in server certificate */ + x509 = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME); + if(x509 == NULL) + infof(data, "error fetching CN from cert\n"); + + /* Here, gtls.c does the following + * 1) x509 hostname checking per RFC2818. axTLS doesn't support this, but + * it seems useful. Omitting for now. + * 2) checks cert validity based on time. axTLS does this in ssl_verify_cert + * 3) displays a bunch of cert information. axTLS doesn't support most of + * this, but a couple fields are available. + */ + + /* General housekeeping */ + conn->ssl[sockindex].state = ssl_connection_complete; + conn->ssl[sockindex].ssl = ssl; + conn->ssl[sockindex].ssl_ctx = ssl_ctx; + conn->recv[sockindex] = axtls_recv; + conn->send[sockindex] = axtls_send; + + /* Put our freshly minted SSL session in cache */ + ssl_idsize = ssl_get_session_id_size(ssl); + ssl_sessionid = ssl_get_session_id(ssl); + if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize) \ + != CURLE_OK) + infof (data, "failed to add session to cache\n"); + return CURLE_OK; } /* return number of sent (non-SSL) bytes */ -ssize_t Curl_axtls_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len) +static ssize_t axtls_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *err) { - return 0; + /* ssl_write() returns 'int' while write() and send() returns + 'size_t' */ + char error_buffer[120]; /* Comply with OpenSSL, which documents that this + must be at least 120 bytes long. */ + int rc = ssl_write(conn->ssl[sockindex].ssl, mem, (int)len); + + if(rc < 0 ) { + *err = map_error_to_curl(rc); + rc = -1; /* generic error code for send failure */ + } + + *err = CURLE_OK; + return rc; } void Curl_axtls_close_all(struct SessionHandle *data) { + (void)data; } void Curl_axtls_close(struct connectdata *conn, int sockindex) { + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + if(connssl->ssl) { + /* line from ssluse.c: (void)SSL_shutdown(connssl->ssl); + axTLS compat layer does nothing for SSL_shutdown */ + + /* The following line is from ssluse.c. There seems to be no axTLS + equivalent. ssl_free and ssl_ctx_free close things. + SSL_set_connect_state(connssl->handle); */ + + ssl_free (connssl->ssl); + connssl->ssl = NULL; + } + if(connssl->ssl_ctx) { + ssl_ctx_free (connssl->ssl_ctx); + connssl->ssl_ctx = NULL; + } } /* @@ -97,7 +380,54 @@ void Curl_axtls_close(struct connectdata *conn, int sockindex) */ int Curl_axtls_shutdown(struct connectdata *conn, int sockindex) { - return 0; + /* Outline taken from ssluse.c since functions are in axTLS compat layer. + axTLS's error set is much smaller, so a lot of error-handling was removed. + */ + int retval = 0; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + char buf[120]; /* We will use this for the OpenSSL error buffer, so it has + to be at least 120 bytes long. */ + ssize_t nread; + + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + /* axTLS compat layer does nothing for SSL_shutdown, so we do nothing too + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + (void)SSL_shutdown(connssl->ssl); + */ + + if(connssl->ssl) { + int what = Curl_socket_ready(conn->sock[sockindex], + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + nread = (ssize_t)SSL_read(conn->ssl[sockindex].ssl, buf, + sizeof(buf)); + + if (nread < SSL_OK){ + failf(data, "close notify alert not received during shutdown"); + retval = -1; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + } + + ssl_free (connssl->ssl); + connssl->ssl = NULL; + } + return retval; } /* @@ -105,13 +435,24 @@ int Curl_axtls_shutdown(struct connectdata *conn, int sockindex) * Otherwise we return the amount of data read. Other errors should return -1 * and set 'wouldblock' to FALSE. */ -ssize_t Curl_axtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - bool *wouldblock) +static ssize_t axtls_recv(struct connectdata *conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *err) { - return 0; + ssize_t ret = (ssize_t)SSL_read(conn->ssl[num].ssl, buf, + (int)buffersize); + + /* axTLS isn't terribly generous about error reporting */ + if(ret < 0) { + failf(conn->data, "axTLS recv error (%d)", (int)ret); + *err = map_error_to_curl(ret); + return -1; + } + + *err = CURLE_OK; + return ret; } /* @@ -124,16 +465,23 @@ ssize_t Curl_axtls_recv(struct connectdata *conn, /* connection data */ */ int Curl_axtls_check_cxn(struct connectdata *conn) { - return 0; + /* ssluse.c line: rc = SSL_peek(conn->ssl[FIRSTSOCKET].ssl, (void*)&buf, 1); + axTLS compat layer always returns the last argument, so connection is + always alive? */ + + return 1; /* connection still in place */ } void Curl_axtls_session_free(void *ptr) { + /* free the ID */ + /* both ssluse.c and gtls.c do something here, but axTLS's OpenSSL + compatibility layer does nothing, so we do nothing too. */ } size_t Curl_axtls_version(char *buffer, size_t size) { - return snprintf(buffer, size, "axTLS/1.2.7"); + return snprintf(buffer, size, "axTLS/%s", ssl_version()); } #endif /* USE_AXTLS */ diff --git a/lib/axtls.h b/lib/axtls.h index ba62eecbf..f1e94b65c 100644 --- a/lib/axtls.h +++ b/lib/axtls.h @@ -38,14 +38,6 @@ void Curl_axtls_close_all(struct SessionHandle *data); /* close a SSL connection */ void Curl_axtls_close(struct connectdata *conn, int sockindex); -/* return number of sent (non-SSL) bytes */ -ssize_t Curl_axtls_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len); -ssize_t Curl_axtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - bool *wouldblock); void Curl_axtls_session_free(void *ptr); size_t Curl_axtls_version(char *buffer, size_t size); int Curl_axtls_shutdown(struct connectdata *conn, int sockindex); @@ -62,8 +54,6 @@ int Curl_axtls_check_cxn(struct connectdata *conn); #define curlssl_set_engine(x,y) (x=x, y=y, CURLE_FAILED_INIT) #define curlssl_set_engine_default(x) (x=x, CURLE_FAILED_INIT) #define curlssl_engines_list(x) (x=x, (struct curl_slist *)NULL) -#define curlssl_send Curl_axtls_send -#define curlssl_recv Curl_axtls_recv #define curlssl_version Curl_axtls_version #define curlssl_check_cxn(x) Curl_axtls_check_cxn(x) #define curlssl_data_pending(x,y) (x=x, y=y, 0) diff --git a/tests/runtests.pl b/tests/runtests.pl index 2eb65fd9e..743fb1f62 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -207,6 +207,7 @@ my $has_gnutls; # built with GnuTLS my $has_nss; # built with NSS my $has_yassl; # built with yassl my $has_polarssl;# built with polarssl +my $has_axtls; # built with axTLS my $has_shared; # built shared @@ -696,6 +697,7 @@ sub verifyhttp { $flags .= "--silent "; $flags .= "--verbose "; $flags .= "--globoff "; + $flags .= "-1 " if($has_axtls == 1); $flags .= "--insecure " if($proto eq 'https'); $flags .= "\"$proto://$ip:$port/${bonus}verifiedserver\""; @@ -1950,6 +1952,10 @@ sub checksystem { $has_openssl=1; $ssllib="polarssl"; } + elsif ($libcurl =~ /axtls/i) { + $has_axtls=1; + $ssllib="axTLS"; + } } elsif($_ =~ /^Protocols: (.*)/i) { # these are the protocols compiled in to this libcurl @@ -2297,6 +2303,11 @@ sub singletest { next; } } + elsif($f eq "axTLS") { + if($has_axtls) { + next; + } + } elsif($f eq "netrc_debug") { if($debug_build) { next; @@ -2548,6 +2559,7 @@ sub singletest { if($curl_debug) { unlink($memdump); } + $cmd = "-1 ".$cmd if(exists $feature{"SSL"} && $has_axtls == 1); # create a (possibly-empty) file before starting the test my @inputfile=getpart("client", "file"); |