diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gtls.c | 53 | ||||
-rw-r--r-- | lib/nss.c | 69 | ||||
-rw-r--r-- | lib/ssluse.c | 42 | ||||
-rw-r--r-- | lib/strerror.c | 3 | ||||
-rw-r--r-- | lib/url.c | 9 | ||||
-rw-r--r-- | lib/urldata.h | 2 |
6 files changed, 170 insertions, 8 deletions
diff --git a/lib/gtls.c b/lib/gtls.c index e9e410243..6e762f161 100644 --- a/lib/gtls.c +++ b/lib/gtls.c @@ -143,6 +143,32 @@ static void showtime(struct SessionHandle *data, infof(data, "%s", data->state.buffer); } +static gnutls_datum load_file (const char *file) +{ + FILE *f; + gnutls_datum loaded_file = { NULL, 0 }; + long filelen; + void *ptr; + + if (!(f = fopen(file, "r")) + || fseek(f, 0, SEEK_END) != 0 + || (filelen = ftell(f)) < 0 + || fseek(f, 0, SEEK_SET) != 0 + || !(ptr = malloc((size_t)filelen)) + || fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { + return loaded_file; + } + + loaded_file.data = ptr; + loaded_file.size = (unsigned int)filelen; + return loaded_file; +} + +static void unload_file(gnutls_datum data) { + free(data.data); +} + + /* this function does a BLOCKING SSL/TLS (re-)handshake */ static CURLcode handshake(struct connectdata *conn, gnutls_session session, @@ -221,7 +247,8 @@ Curl_gtls_connect(struct connectdata *conn, unsigned int cert_list_size; const gnutls_datum *chainp; unsigned int verify_status; - gnutls_x509_crt x509_cert; + gnutls_x509_crt x509_cert,x509_issuer; + gnutls_datum issuerp; char certbuf[256]; /* big enough? */ size_t size; unsigned int algo; @@ -375,7 +402,9 @@ Curl_gtls_connect(struct connectdata *conn, chainp = gnutls_certificate_get_peers(session, &cert_list_size); if(!chainp) { - if(data->set.ssl.verifypeer) { + if(data->set.ssl.verifypeer || + data->set.ssl.verifyhost || + data->set.ssl.issuercert) { failf(data, "failed to get server cert"); return CURLE_PEER_FAILED_VERIFICATION; } @@ -399,8 +428,9 @@ Curl_gtls_connect(struct connectdata *conn, /* verify_status is a bitmask of gnutls_certificate_status bits */ if(verify_status & GNUTLS_CERT_INVALID) { if(data->set.ssl.verifypeer) { - failf(data, "server certificate verification failed. CAfile: %s", - data->set.ssl.CAfile?data->set.ssl.CAfile:"none"); + failf(data, "server certificate verification failed. CAfile: %s " + "CRLfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none", + data->set.ssl.CRLfile?data->set.ssl.CRLfile:"none"); return CURLE_SSL_CACERT; } else @@ -419,6 +449,21 @@ Curl_gtls_connect(struct connectdata *conn, gnutls_x509_crt_t format */ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); + if (data->set.ssl.issuercert) { + gnutls_x509_crt_init(&x509_issuer); + issuerp = load_file(data->set.ssl.issuercert); + gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); + rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer); + unload_file(issuerp); + if (rc <= 0) { + failf(data, "server certificate issuer check failed (IssuerCert: %s)", + data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); + return CURLE_SSL_ISSUER_ERROR; + } + infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n", + data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); + } + size=sizeof(certbuf); rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, 0, /* the first and only one */ @@ -723,6 +723,43 @@ static void display_conn_info(struct connectdata *conn, PRFileDesc *sock) /** * + * Check that the Peer certificate's issuer certificate matches the one found + * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the + * issuer check, so we provide comments that mimic the OpenSSL + * X509_check_issued function (in x509v3/v3_purp.c) + */ +static SECStatus check_issuer_cert(struct connectdata *conn, PRFileDesc *sock, char* issuer_nickname) +{ + CERTCertificate *cert,*cert_issuer,*issuer; + SECStatus res=SECSuccess; + void *proto_win = NULL; + + /* + PRArenaPool *tmpArena = NULL; + CERTAuthKeyID *authorityKeyID = NULL; + SECITEM *caname = NULL; + */ + + cert = SSL_PeerCertificate(sock); + cert_issuer = CERT_FindCertIssuer(cert,PR_Now(),certUsageObjectSigner); + + proto_win = SSL_RevealPinArg(sock); + issuer = NULL; + issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); + + if ((!cert_issuer) || (!issuer)) + res = SECFailure; + else if (CERT_CompareCerts(cert_issuer,issuer)==PR_FALSE) + res = SECFailure; + + CERT_DestroyCertificate(cert); + CERT_DestroyCertificate(issuer); + CERT_DestroyCertificate(cert_issuer); + return res; +} + +/** + * * Callback to pick the SSL client certificate. */ static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, @@ -853,7 +890,7 @@ int Curl_nss_close_all(struct SessionHandle *data) return 0; } -CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) +CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) { PRInt32 err; PRFileDesc *model = NULL; @@ -1046,6 +1083,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) } else { strncpy(nickname, data->set.str[STRING_CERT], PATH_MAX); + nickname[PATH_MAX-1]=0; /* make sure this is zero terminated */ } if(nss_Init_Tokens(conn) != SECSuccess) { free(nickname); @@ -1061,7 +1099,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) connssl->client_nickname = strdup(nickname); if(SSL_GetClientAuthDataHook(model, (SSLGetClientAuthData) SelectClientCert, - (void *)connssl->client_nickname) != + (void *)connssl) != SECSuccess) { curlerr = CURLE_SSL_CERTPROBLEM; goto error; @@ -1074,6 +1112,7 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) else connssl->client_nickname = NULL; + /* Import our model socket onto the existing file descriptor */ connssl->handle = PR_ImportTCPSocket(sockfd); connssl->handle = SSL_ImportFD(model, connssl->handle); @@ -1099,6 +1138,32 @@ CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) display_conn_info(conn, connssl->handle); + if (data->set.str[STRING_SSL_ISSUERCERT]) { + char *n; + char *nickname; + nickname = (char *)malloc(PATH_MAX); + if(is_file(data->set.str[STRING_SSL_ISSUERCERT])) { + n = strrchr(data->set.str[STRING_SSL_ISSUERCERT], '/'); + if (n) { + n++; /* skip last slash */ + snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", 1, n); + } + } + else { + strncpy(nickname, data->set.str[STRING_SSL_ISSUERCERT], PATH_MAX); + nickname[PATH_MAX-1]=0; /* make sure this is zero terminated */ + } + if (check_issuer_cert(conn,connssl->handle,nickname)==SECFailure) { + infof(data,"SSL certificate issuer check failed\n"); + free(nickname); + curlerr = CURLE_SSL_ISSUER_ERROR; + goto error; + } + else { + infof("SSL certificate issuer check ok\n"); + } + } + return CURLE_OK; error: diff --git a/lib/ssluse.c b/lib/ssluse.c index f14ad344e..4fff13d78 100644 --- a/lib/ssluse.c +++ b/lib/ssluse.c @@ -1629,6 +1629,9 @@ static CURLcode servercert(struct connectdata *conn, long lerr; ASN1_TIME *certdate; struct SessionHandle *data = conn->data; + X509 *issuer; + FILE *fp; + connssl->server_cert = SSL_get_peer_certificate(connssl->handle); if(!connssl->server_cert) { if(strict) @@ -1678,6 +1681,41 @@ static CURLcode servercert(struct connectdata *conn, /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ + /* e.g. match issuer name with provided issuer certificate */ + if (data->set.str[STRING_SSL_ISSUERCERT]) { + if (! (fp=fopen(data->set.str[STRING_SSL_ISSUERCERT],"r"))) { + if (strict) + failf(data, "SSL: Unable to open issuer cert (%s)\n", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + issuer = PEM_read_X509(fp,NULL,NULL,NULL); + if (!issuer) { + if (strict) + failf(data, "SSL: Unable to read issuer cert (%s)\n", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + X509_free(issuer); + fclose(fp); + return CURLE_SSL_ISSUER_ERROR; + } + fclose(fp); + if (X509_check_issued(issuer,connssl->server_cert) != X509_V_OK) { + if (strict) + failf(data, "SSL: Certificate issuer check failed (%s)\n", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + X509_free(issuer); + connssl->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + infof(data, "\t SSL certificate issuer check ok (%s)\n", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(issuer); + } + lerr = data->set.ssl.certverifyresult= SSL_get_verify_result(connssl->handle); if(data->set.ssl.certverifyresult != X509_V_OK) { @@ -1690,12 +1728,12 @@ static CURLcode servercert(struct connectdata *conn, retcode = CURLE_PEER_FAILED_VERIFICATION; } else - infof(data, "SSL certificate verify result: %s (%ld)," + infof(data, "\t SSL certificate verify result: %s (%ld)," " continuing anyway.\n", X509_verify_cert_error_string(lerr), lerr); } else - infof(data, "SSL certificate verify ok.\n"); + infof(data, "\t SSL certificate verify ok.\n"); } X509_free(connssl->server_cert); diff --git a/lib/strerror.c b/lib/strerror.c index 03b01582c..032e2c747 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -225,6 +225,9 @@ curl_easy_strerror(CURLcode error) case CURLE_SSL_CRL_BADFILE: return "Failed to load CRL file (path? access rights?, format?)"; + case CURLE_SSL_ISSUER_ERROR: + return "Issuer check against peer certificate failed"; + case CURLE_SEND_FAIL_REWIND: return "Send failed since rewinding of the data stream failed"; @@ -1819,6 +1819,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, result = setstropt(&data->set.str[STRING_SSL_CRLFILE], va_arg(param, char *)); break; + case CURLOPT_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + result = setstropt(&data->set.str[STRING_SSL_ISSUERCERT], + va_arg(param, char *)); + break; case CURLOPT_TELNETOPTIONS: /* * Set a linked list of telnet options @@ -3960,6 +3968,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH]; data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE]; data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; diff --git a/lib/urldata.h b/lib/urldata.h index def598b41..2e0f69204 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -213,6 +213,7 @@ struct ssl_config_data { char *CApath; /* certificate dir (doesn't work on windows) */ char *CAfile; /* cerficate to verify peer against */ char *CRLfile; /* CRL to check cerficate revocation */ + char *issuercert; /* optional issuer cerficate filename */ char *random_file; /* path to file containing "random" data */ char *egdsocket; /* path to file containing the EGD daemon socket */ char *cipher_list; /* list of ciphers to use */ @@ -1319,6 +1320,7 @@ enum dupstring { STRING_USERPWD, /* <user:password>, if used */ STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ /* -- end of strings -- */ STRING_LAST /* not used, just an end-of-list marker */ |