diff -up curl-7.16.4/configure.nsspem curl-7.16.4/configure --- curl-7.16.4/configure.nsspem 2007-06-25 11:18:41.000000000 +0200 +++ curl-7.16.4/configure 2007-09-06 08:35:31.000000000 +0200 @@ -26760,7 +26760,7 @@ echo "$as_me: WARNING: Use --with-ssl, - fi -if test X"$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then +if test X"$USE_NSS$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then { echo "$as_me:$LINENO: checking CA cert bundle install path" >&5 echo $ECHO_N "checking CA cert bundle install path... $ECHO_C" >&6; } diff -up curl-7.16.4/configure.ac.nsspem curl-7.16.4/configure.ac --- curl-7.16.4/configure.ac.nsspem 2007-06-12 23:39:21.000000000 +0200 +++ curl-7.16.4/configure.ac 2007-09-06 08:35:31.000000000 +0200 @@ -1468,7 +1468,7 @@ dnl ************************************ dnl Check for the CA bundle dnl ********************************************************************** -if test X"$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then +if test X"$USE_NSS$USE_GNUTLS$OPENSSL_ENABLED" != "X"; then AC_MSG_CHECKING([CA cert bundle install path]) diff -up curl-7.16.4/lib/nss.c.nsspem curl-7.16.4/lib/nss.c --- curl-7.16.4/lib/nss.c.nsspem 2007-05-25 23:56:27.000000000 +0200 +++ curl-7.16.4/lib/nss.c 2007-09-06 09:26:39.000000000 +0200 @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -69,10 +70,19 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #endif +#define SSL_DIR "/etc/pki/nssdb" + +/* enough to fit the string "PEM Token #[0|1]" */ +#define SLOTSIZE 13 + PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd); static int initialized = 0; -static int noverify = 0; +static int verify_done = 0; +char * select_nickname = NULL; + +/* Global so our callbacks can update as appropriate */ +static int curlerr; #define HANDSHAKE_TIMEOUT 30 @@ -87,15 +97,25 @@ typedef struct { PRInt32 version; /* protocol version valid for this cipher */ } cipher_s; -/* the table itself is defined in nss_engine_init.c */ #ifdef NSS_ENABLE_ECC #define ciphernum 48 #else #define ciphernum 23 #endif +#define PK11_SETATTRS(x,id,v,l) \ + do { \ + (x)->type = (id); \ + (x)->pValue=(v); \ + (x)->ulValueLen = (l); \ + } while (0) + +#define CERT_NewTempCertificate __CERT_NewTempCertificate + enum sslversion { SSL2 = 1, SSL3 = 2, TLS = 4 }; +pphrase_arg_t *parg = NULL; + cipher_s cipherlist[ciphernum] = { /* SSL2 cipher suites */ {"rc4", SSL_EN_RC4_128_WITH_MD5, SSL2}, @@ -154,6 +174,9 @@ cipher_s cipherlist[ciphernum] = { #endif }; +const char* pem_library = "libnsspem.so"; +static SECMODModule* mod = NULL; + static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model, char *cipher_list) { @@ -197,9 +220,7 @@ static SECStatus set_ciphers(struct Sess } if(found == PR_FALSE) { - char buf[1024]; - snprintf(buf, 1024, "Unknown cipher in list: %s", cipher); - failf(data, buf); + failf(data, "Unknown cipher in list: %s", cipher); return SECFailure; } @@ -220,27 +241,240 @@ static SECStatus set_ciphers(struct Sess return SECSuccess; } +/* + * Determine whether the nickname passed in is a filename that needs to + * be loaded as a PEM or a regular NSS nickname. + * + * returns 1 for a file + * returns 0 for not a file (NSS nickname) + */ +static int is_file(const char *filename) { + struct stat st; + + if (filename == NULL) + return 0; + + if (stat(filename, &st) == 0) { + if (S_ISREG(st.st_mode)) + return 1; + } + return 0; +} + +static int +nss_load_cert(struct SessionHandle *data, const char *filename, PRBool cacert) +{ + CERTCertificate *cert; + void *proto_win = NULL; + CK_SLOT_ID slotID; + PK11SlotInfo * slot = NULL; + PK11GenericObject *rv; + CK_ATTRIBUTE *attrs; + CK_ATTRIBUTE theTemplate[20]; + CK_BBOOL cktrue = CK_TRUE; + CK_BBOOL ckfalse = CK_FALSE; + CK_OBJECT_CLASS objClass = CKO_CERTIFICATE; + char *nickname = NULL; + char *slotname = NULL; + char *n; + + /* If there is no slash in the filename it is assumed to be a regular + * NSS nickname. + */ + if (is_file(filename)) { + n = strrchr(filename, '/'); + if (n) + n++; + } else { + /* A nickname from the NSS internal database */ + nickname = strdup(filename); + goto done; + } + + attrs = theTemplate; + + /* All CA and trust objects go into slot 0. Other slots are used + * for storing certificates. With each new user certificate we increment + * the slot count. We only support 1 user certificate right now. + */ + if (cacert) { + slotID = 0; + } else { + slotID = 1; + } + + slotname = (char *)malloc(SLOTSIZE); + nickname = (char *)malloc(PATH_MAX); + snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID); + snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", slotID, n); + + slot = PK11_FindSlotByName(slotname); + + if (!slot) { + free(slotname); + free(nickname); + return 0; + } + + PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++; + PK11_SETATTRS(attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++; + PK11_SETATTRS(attrs, CKA_LABEL, (unsigned char *)filename, strlen(filename)+1); attrs++; + if (cacert) { + PK11_SETATTRS(attrs, CKA_TRUST, &cktrue, sizeof(CK_BBOOL) ); attrs++; + } else { + PK11_SETATTRS(attrs, CKA_TRUST, &ckfalse, sizeof(CK_BBOOL) ); attrs++; + } + + /* This load the certificate in our PEM module into the appropriate + * slot. + */ + rv = PK11_CreateGenericObject(slot, theTemplate, 4, PR_FALSE /* isPerm */); + + PK11_FreeSlot(slot); + + free(slotname); + if (rv == NULL) { + free(nickname); + return 0; + } + +done: + /* Double-check that the certificate or nickname requested exists in + * either the token or the NSS certificate database. + */ + if (!cacert) { + cert = PK11_FindCertFromNickname((char *)nickname, proto_win); + + /* An invalid nickname was passed in */ + if (cert == NULL) { + PR_SetError(SEC_ERROR_UNKNOWN_CERT, 0); + return 0; + } + + CERT_DestroyCertificate(cert); + } + free(nickname); + + return 1; +} + +static int nss_load_key(char *key_file) +{ + PK11SlotInfo * slot = NULL; + PK11GenericObject *rv; + CK_ATTRIBUTE *attrs; + CK_ATTRIBUTE theTemplate[20]; + CK_BBOOL cktrue = CK_TRUE; + CK_OBJECT_CLASS objClass = CKO_PRIVATE_KEY; + CK_SLOT_ID slotID; + char *slotname = NULL; + + attrs = theTemplate; + + /* FIXME: grok the various file types */ + + /* FIXME: shouldn't be hardcoded */ + slotID = 1; + + slotname = (char *)malloc(SLOTSIZE); + snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID); + + slot = PK11_FindSlotByName(slotname); + free(slotname); + + if (!slot) { + return 0; + } + + PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass) ); attrs++; + PK11_SETATTRS(attrs, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL) ); attrs++; + PK11_SETATTRS(attrs, CKA_LABEL, (unsigned char *)key_file, strlen(key_file)+1); attrs++; + + /* When adding an encrypted key the PKCS#11 will be set as removed */ + rv = PK11_CreateGenericObject(slot, theTemplate, 3, PR_FALSE /* isPerm */); + if (rv == NULL) { + PR_SetError(SEC_ERROR_BAD_KEY, 0); + return 0; + } + + /* This will force the token to be seen as re-inserted */ + PK11_IsPresent(slot); + + parg->retryCount = 0; + /* parg is initialized in nss_Init_Tokens() */ + if (PK11_Authenticate(slot, PR_TRUE, parg) != SECSuccess) { + return 0; + } + + return 1; +} + +static int display_error(struct connectdata *conn, PRInt32 err, const char *filename) { + switch(err) { + case SEC_ERROR_BAD_PASSWORD: + failf(conn->data, "Unable to load client key: Incorrect password\n"); + return 1; + case SEC_ERROR_UNKNOWN_CERT: + failf(conn->data, "Unable to load certificate %s\n", filename); + return 1; + } + return 0; /* The caller will print a generic error */ +} + +static int cert_stuff(struct connectdata *conn, char *cert_file, char *key_file) +{ + struct SessionHandle *data = conn->data; + int rv = 0; + + if (cert_file) + rv = nss_load_cert(data, cert_file, PR_FALSE); + if (!rv) { + if (!display_error(conn, PR_GetError(), cert_file)) + failf(data, "Unable to load client cert %d.", PR_GetError()); + return 0; + } + if (key_file || (is_file(cert_file) )) { + if (key_file) + rv = nss_load_key(key_file); + else + rv = nss_load_key(cert_file); + if (!rv) { + if (!display_error(conn, PR_GetError(), key_file)) + failf(data, "Unable to load client key %d.", PR_GetError()); + + return 0; + } + } + return 1; +} + static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg) { pphrase_arg_t *parg = (pphrase_arg_t *) arg; - (void)slot; /* unused */ - (void)retry; /* unused */ + parg->retryCount++; + if (parg->retryCount > 2) + return NULL; if(parg->data->set.key_passwd) return (char *)PORT_Strdup((char *)parg->data->set.key_passwd); else return NULL; } -static SECStatus nss_Init_Tokens(struct connectdata * conn) +static char * nss_no_password(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + return NULL; +} + +static SECStatus nss_Init_Tokens(struct SessionHandle *data) { PK11SlotList *slotList; PK11SlotListElement *listEntry; SECStatus ret, status = SECSuccess; - pphrase_arg_t *parg; - parg = (pphrase_arg_t *) malloc(sizeof(*parg)); + if (!parg) + parg = (pphrase_arg_t *) malloc(sizeof(*parg)); parg->retryCount = 0; - parg->data = conn->data; + parg->data = data; PK11_SetPasswordFunc(nss_get_password); @@ -253,10 +487,10 @@ static SECStatus nss_Init_Tokens(struct if(PK11_NeedLogin(slot) && PK11_NeedUserInit(slot)) { if(slot == PK11_GetInternalKeySlot()) { - failf(conn->data, "The NSS database has not been initialized.\n"); + failf(data, "The NSS database has not been initialized.\n"); } else { - failf(conn->data, "The token %s has not been initialized.", + failf(data, "The token %s has not been initialized.", PK11_GetTokenName(slot)); } PK11_FreeSlot(slot); @@ -265,6 +499,8 @@ static SECStatus nss_Init_Tokens(struct ret = PK11_Authenticate(slot, PR_TRUE, parg); if(SECSuccess != ret) { + if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) + infof(data, "The password for token '%s' is incorrect\n", PK11_GetTokenName(slot)); status = SECFailure; break; } @@ -272,15 +508,59 @@ static SECStatus nss_Init_Tokens(struct PK11_FreeSlot(slot); } - free(parg); return status; } static SECStatus BadCertHandler(void *arg, PRFileDesc * socket) { SECStatus success = SECSuccess; - (void)arg; - (void)socket; + struct connectdata *conn = (struct connectdata *)arg; + PRErrorCode err = PR_GetError(); + CERTCertificate *cert = NULL; + char *subject, *issuer; + + if (verify_done) + return success; + + verify_done = 1; + cert = SSL_PeerCertificate(socket); + subject = CERT_NameToAscii(&cert->subject); + issuer = CERT_NameToAscii(&cert->issuer); + CERT_DestroyCertificate(cert); + + switch(err) { + case SEC_ERROR_CA_CERT_INVALID: + infof(conn->data, "Issuer certificate is invalid: '%s'\n", issuer); + if (conn->data->set.ssl.verifypeer) + success = SECFailure; + break; + case SEC_ERROR_UNTRUSTED_ISSUER: + if (conn->data->set.ssl.verifypeer) + success = SECFailure; + infof(conn->data, "Certificate is signed by an untrusted issuer: '%s'\n", issuer); + break; + case SSL_ERROR_BAD_CERT_DOMAIN: + if (conn->data->set.ssl.verifypeer) + success = SECFailure; + infof(conn->data, "common name: %s (does not match '%s')\n", + subject, conn->host.dispname); + break; + case SEC_ERROR_EXPIRED_CERTIFICATE: + if (conn->data->set.ssl.verifypeer) + success = SECFailure; + infof(conn->data, "Remote Certificate has expired.\n"); + break; + default: + if (conn->data->set.ssl.verifypeer) + success = SECFailure; + infof(conn->data, "Bad certificate received. Subject = '%s', Issuer = '%s'\n", subject, issuer); + break; + } + if (success == SECSuccess) + infof(conn->data, "SSL certificate verify ok.\n"); + PR_Free(subject); + PR_Free(issuer); + curlerr = CURLE_SSL_CACERT; return success; } @@ -295,6 +575,52 @@ static SECStatus HandshakeCallback(PRFil return SECSuccess; } +static void display_conn_info(struct connectdata *conn, PRFileDesc * socket) +{ + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + CERTCertificate *cert; + char *subject, *issuer, *common_name; + PRExplodedTime printableTime; + char timeString[256]; + PRTime notBefore, notAfter; + + if (SSL_GetChannelInfo(socket, &channel, sizeof channel) == + SECSuccess && channel.length == sizeof channel && + channel.cipherSuite) { + if (SSL_GetCipherSuiteInfo(channel.cipherSuite, + &suite, sizeof suite) == SECSuccess) { + infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName); + } + } + + infof(conn->data, "Server certificate:\n"); + + cert = SSL_PeerCertificate(socket); + subject = CERT_NameToAscii(&cert->subject); + issuer = CERT_NameToAscii(&cert->issuer); + common_name = CERT_GetCommonName(&cert->subject); + infof(conn->data, "\tsubject: %s\n", subject); + + CERT_GetCertTimes(cert, ¬Before, ¬After); + PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); + PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); + infof(conn->data, "\tstart date: %s\n", timeString); + PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); + PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); + infof(conn->data, "\texpire date: %s\n", timeString); + infof(conn->data, "\tcommon name: %s\n", common_name); + infof(conn->data, "\tissuer: %s\n", issuer); + + PR_Free(subject); + PR_Free(issuer); + PR_Free(common_name); + + CERT_DestroyCertificate(cert); + + return; +} + /** * * Callback to pick the SSL client certificate. @@ -309,24 +635,42 @@ static SECStatus SelectClientCert(void * char *nickname = (char *)arg; void *proto_win = NULL; SECStatus secStatus = SECFailure; + PK11SlotInfo *slot; (void)caNames; proto_win = SSL_RevealPinArg(socket); + if (!nickname) + return secStatus; + cert = PK11_FindCertFromNickname(nickname, proto_win); if(cert) { - privKey = PK11_FindKeyByAnyCert(cert, proto_win); - if(privKey) { - secStatus = SECSuccess; - } - else { - CERT_DestroyCertificate(cert); + + if (!strncmp(nickname, "PEM Token", 9)) { + CK_SLOT_ID slotID = 1; /* hardcoded for now */ + char * slotname = (char *)malloc(SLOTSIZE); + snprintf(slotname, SLOTSIZE, "PEM Token #%ld", slotID); + slot = PK11_FindSlotByName(slotname); + privKey = PK11_FindPrivateKeyFromCert(slot, cert, NULL); + PK11_FreeSlot(slot); + free(slotname); + if(privKey) { + secStatus = SECSuccess; + } + } else { + privKey = PK11_FindKeyByAnyCert(cert, proto_win); + if(privKey) { + secStatus = SECSuccess; + } } } if(secStatus == SECSuccess) { *pRetCert = cert; *pRetKey = privKey; + } else { + if (cert) + CERT_DestroyCertificate(cert); } return secStatus; @@ -397,6 +741,8 @@ void Curl_nss_close(struct connectdata * } connssl->use = FALSE; /* get back to ordinary socket usage */ } + if (select_nickname) + free(select_nickname); } /* @@ -418,31 +764,52 @@ CURLcode Curl_nss_connect(struct connect curl_socket_t sockfd = conn->sock[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SECStatus rv; - int curlerr = CURLE_SSL_CONNECT_ERROR; + char *configstring = NULL; + char *certDir = NULL; + + curlerr = CURLE_SSL_CONNECT_ERROR; + certDir = getenv("SSL_DIR"); /* Look in $SSL_DIR */ + + if (!certDir) { + struct stat st; + + if (stat(SSL_DIR, &st) == 0) + if (S_ISDIR(st.st_mode)) { + certDir = "/etc/pki/nssdb"; + } + } /* FIXME. NSS doesn't support multiple databases open at the same time. */ if(!initialized) { - if(!data->set.ssl.CAfile) { - if(data->set.ssl.verifypeer) { - failf(data, "No NSS cacert database specified."); - return CURLE_SSL_CACERT_BADFILE; - } - else { - rv = NSS_NoDB_Init(NULL); - noverify = 1; - } + if(!certDir) { + rv = NSS_NoDB_Init(NULL); } else { - rv = NSS_Initialize(data->set.ssl.CAfile, NULL, NULL, "secmod.db", + rv = NSS_Initialize(certDir, NULL, NULL, "secmod.db", NSS_INIT_READONLY); } if(rv != SECSuccess) { curlerr = CURLE_SSL_CACERT_BADFILE; goto error; } - } + configstring = (char *)malloc(4096); + + NSS_SetDomesticPolicy(); - NSS_SetDomesticPolicy(); + PR_snprintf(configstring, 4096, "library=%s name=PEM", pem_library); +// PR_snprintf(configstring, 4096, "library=%s name=PEM parameters=\"NSS=\"trustorder=75\"\"", pem_library); +// PR_snprintf(configstring, 4096, "library=/usr/lib/libnsspem.so name=PEM parameters=\"/etc/pki/tls/certs/ca-bundle.crt\" NSS=\"trustorder=75\""); + + mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); + if (!mod || !mod->loaded) { + if (mod) { + SECMOD_DestroyModule(mod); + mod = NULL; + } + infof(data, "WARNING: failed to load NSS PEM library %s. Using OpenSSL PEM certificates will not work.\n", pem_library); + } + free(configstring); + } model = PR_NewTCPSocket(); if(!model) @@ -482,26 +849,92 @@ CURLcode Curl_nss_connect(struct connect goto error; if(data->set.ssl.cipher_list) { - if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) + if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) { + curlerr = CURLE_SSL_CIPHER; goto error; + } } - if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, NULL) - != SECSuccess) + if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, conn) + != SECSuccess) { goto error; + } if(SSL_HandshakeCallback(model, (SSLHandshakeCallback) HandshakeCallback, NULL) != SECSuccess) goto error; + if (mod && data->set.ssl.CAfile) { + rv = nss_load_cert(data, data->set.ssl.CAfile, PR_TRUE); + } else if (data->set.ssl.CApath) { + struct stat st; + PRDir *dir; + PRDirEntry *entry; + + if (stat(data->set.ssl.CApath, &st) == -1) + curlerr = CURLE_SSL_CACERT_BADFILE; + goto error; + + if (S_ISDIR(st.st_mode)) { + dir = PR_OpenDir(data->set.ssl.CApath); + int rv; + do { + entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN); + + if (entry) { + char fullpath[PATH_MAX]; + + snprintf(fullpath, sizeof(fullpath), "%s/%s", data->set.ssl.CApath, entry->name); + rv = nss_load_cert(data, fullpath, PR_TRUE); + } + /* This is purposefully tolerant of errors so non-PEM files + * can be in the same directory */ + } while (entry != NULL); + PR_CloseDir(dir); + } + } + infof(data, + " CAfile: %s\n" + " CApath: %s\n", + data->set.ssl.CAfile ? data->set.ssl.CAfile : "none", + data->set.ssl.CApath ? data->set.ssl.CApath : "none"); + if(data->set.cert) { + char * n; + char * nickname; + + nickname = (char *)malloc(PATH_MAX); + if (is_file(data->set.cert)) { + n = strrchr(data->set.cert, '/'); + if (n) { + n++; /* skip last slash */ + snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", 1, n); + } + } else { + strncpy(nickname, data->set.cert, PATH_MAX); + } + if(nss_Init_Tokens(conn->data) != SECSuccess) { + free(nickname); + goto error; + } + if (!cert_stuff(conn, data->set.cert, data->set.key)) { + /* failf() is already done in cert_stuff() */ + free(nickname); + return CURLE_SSL_CERTPROBLEM; + } + + select_nickname = strdup(nickname); if(SSL_GetClientAuthDataHook(model, (SSLGetClientAuthData) SelectClientCert, - (void *)data->set.cert) != SECSuccess) { + (void *)select_nickname) != SECSuccess) { curlerr = CURLE_SSL_CERTPROBLEM; goto error; } - if(nss_Init_Tokens(conn) != SECSuccess) - goto error; + + free(nickname); + free(parg); + + /* No longer return the password, lets us free parg */ + PK11_SetPasswordFunc(nss_no_password); } /* Import our model socket onto the existing file descriptor */ @@ -509,6 +942,7 @@ CURLcode Curl_nss_connect(struct connect connssl->handle = SSL_ImportFD(model, connssl->handle); if(!connssl->handle) goto error; + PR_Close(model); /* We don't need this any more */ /* Force handshake on next I/O */ SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE); @@ -518,14 +952,17 @@ CURLcode Curl_nss_connect(struct connect /* Force the handshake now */ if (SSL_ForceHandshakeWithTimeout(connssl->handle, PR_SecondsToInterval(HANDSHAKE_TIMEOUT)) - != SECSuccess) + != SECSuccess) { goto error; + } + + display_conn_info(conn, connssl->handle); return CURLE_OK; error: err = PR_GetError(); - failf(data, "NSS error %d", err); + infof(data, "NSS error %d\n", err); if(model) PR_Close(model); return curlerr;