Index: curl-7.18.1/lib/gtls.c =================================================================== --- curl-7.18.1.orig/lib/gtls.c 2008-06-02 09:45:07.598156647 +0200 +++ curl-7.18.1/lib/gtls.c 2008-06-02 09:45:59.554156684 +0200 @@ -143,6 +143,31 @@ 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 +246,8 @@ 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 +401,9 @@ 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 +427,9 @@ /* 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 +448,21 @@ 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 */ Index: curl-7.18.1/include/curl/curl.h =================================================================== --- curl-7.18.1.orig/include/curl/curl.h 2008-06-02 09:45:07.582176619 +0200 +++ curl-7.18.1/include/curl/curl.h 2008-06-02 09:45:59.554156684 +0200 @@ -442,6 +442,7 @@ connection */ CURLE_SSL_CRL_BADFILE, /* 81 - could not load CRL file, missing or wrong format */ + CURLE_SSL_ISSUER_ERROR, /* 82 - Issuer check failed */ CURL_LAST /* never use! */ } CURLcode; @@ -1192,6 +1193,9 @@ /* CRL file */ CINIT(CRLFILE, OBJECTPOINT, 169), + /* Issuer certificate */ + CINIT(ISSUERCERT, OBJECTPOINT, 170), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; Index: curl-7.18.1/lib/url.c =================================================================== --- curl-7.18.1.orig/lib/url.c 2008-06-02 09:45:07.594156699 +0200 +++ curl-7.18.1/lib/url.c 2008-06-02 09:45:59.558156493 +0200 @@ -1811,6 +1811,14 @@ 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 @@ -3952,6 +3960,7 @@ 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]; Index: curl-7.18.1/lib/urldata.h =================================================================== --- curl-7.18.1.orig/lib/urldata.h 2008-06-02 09:45:07.598156647 +0200 +++ curl-7.18.1/lib/urldata.h 2008-06-02 09:45:59.558156493 +0200 @@ -211,6 +211,7 @@ 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 */ @@ -1314,6 +1315,7 @@ STRING_USERPWD, /* , 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 */ Index: curl-7.18.1/lib/ssluse.c =================================================================== --- curl-7.18.1.orig/lib/ssluse.c 2008-06-02 09:45:07.582176619 +0200 +++ curl-7.18.1/lib/ssluse.c 2008-06-02 09:45:59.558156493 +0200 @@ -1643,6 +1643,9 @@ 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) @@ -1692,6 +1695,41 @@ /* 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) { @@ -1704,12 +1742,12 @@ 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); Index: curl-7.18.1/lib/nss.c =================================================================== --- curl-7.18.1.orig/lib/nss.c 2008-06-02 09:45:07.598156647 +0200 +++ curl-7.18.1/lib/nss.c 2008-06-02 09:45:59.562164543 +0200 @@ -727,6 +727,40 @@ /** * + * 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, @@ -1065,7 +1099,7 @@ connssl->client_nickname = strdup(nickname); if(SSL_GetClientAuthDataHook(model, (SSLGetClientAuthData) SelectClientCert, - (void *)connssl->client_nickname) != + (void *)connssl) != SECSuccess) { curlerr = CURLE_SSL_CERTPROBLEM; goto error; @@ -1078,6 +1112,7 @@ 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); @@ -1103,6 +1138,31 @@ 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); + } + 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: Index: curl-7.18.1/lib/strerror.c =================================================================== --- curl-7.18.1.orig/lib/strerror.c 2008-06-02 09:45:07.598156647 +0200 +++ curl-7.18.1/lib/strerror.c 2008-06-02 09:46:53.890156509 +0200 @@ -225,6 +225,9 @@ 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";