static CURLcode nss_load_ca_certificates(struct connectdata *conn, int sockindex) { struct SessionHandle *data = conn->data; const char *cafile = data->set.ssl.CAfile; const char *capath = data->set.ssl.CApath; if(cafile) { CURLcode rv = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE); if(CURLE_OK != rv) return rv; } if(capath) { struct_stat st; if(stat(capath, &st) == -1) return CURLE_SSL_CACERT_BADFILE; if(S_ISDIR(st.st_mode)) { PRDirEntry *entry; PRDir *dir = PR_OpenDir(capath); if(!dir) return CURLE_SSL_CACERT_BADFILE; while((entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN))) { char *fullpath = aprintf("%s/%s", capath, entry->name); if(!fullpath) { PR_CloseDir(dir); return CURLE_OUT_OF_MEMORY; } if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE)) /* This is purposefully tolerant of errors so non-PEM files can * be in the same directory */ infof(data, "failed to load '%s' from CURLOPT_CAPATH\n", fullpath); free(fullpath); } PR_CloseDir(dir); } else infof(data, "warning: CURLOPT_CAPATH not a directory (%s)\n", capath); } infof(data, " CAfile: %s\n CApath: %s\n", cafile ? cafile : "none", capath ? capath : "none"); return CURLE_OK; }
static int cert_stuff(struct connectdata *conn, int sockindex, char *cert_file, char *key_file) { struct SessionHandle *data = conn->data; int rv = 0; if(cert_file) { rv = nss_load_cert(&conn->ssl[sockindex], 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(conn, sockindex, key_file); else /* In case the cert file also has the key */ rv = nss_load_key(conn, sockindex, 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 CURLcode cert_stuff(struct connectdata *conn, int sockindex, char *cert_file, char *key_file) { struct SessionHandle *data = conn->data; CURLcode rv; if(cert_file) { rv = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE); if(CURLE_OK != rv) { const PRErrorCode err = PR_GetError(); if(!display_error(conn, err, cert_file)) { const char *err_name = nss_error_to_name(err); failf(data, "unable to load client cert: %d (%s)", err, err_name); } return rv; } } if(key_file || (is_file(cert_file))) { if(key_file) rv = nss_load_key(conn, sockindex, key_file); else /* In case the cert file also has the key */ rv = nss_load_key(conn, sockindex, cert_file); if(CURLE_OK != rv) { const PRErrorCode err = PR_GetError(); if(!display_error(conn, err, key_file)) { const char *err_name = nss_error_to_name(err); failf(data, "unable to load client key: %d (%s)", err, err_name); } return rv; } } return CURLE_OK; }
CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) { PRInt32 err; PRFileDesc *model = NULL; PRBool ssl2, ssl3, tlsv1; struct SessionHandle *data = conn->data; curl_socket_t sockfd = conn->sock[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SECStatus rv; char *certDir = NULL; int curlerr; const int *cipher_to_enable; curlerr = CURLE_SSL_CONNECT_ERROR; if (connssl->state == ssl_connection_complete) return CURLE_OK; connssl->data = data; #ifdef HAVE_PK11_CREATEGENERICOBJECT connssl->cacert[0] = NULL; connssl->cacert[1] = NULL; connssl->key = NULL; #endif /* FIXME. NSS doesn't support multiple databases open at the same time. */ PR_Lock(nss_initlock); if(!initialized) { struct_stat st; /* First we check if $SSL_DIR points to a valid dir */ certDir = getenv("SSL_DIR"); if(certDir) { if((stat(certDir, &st) != 0) || (!S_ISDIR(st.st_mode))) { certDir = NULL; } } /* Now we check if the default location is a valid dir */ if(!certDir) { if((stat(SSL_DIR, &st) == 0) && (S_ISDIR(st.st_mode))) { certDir = (char *)SSL_DIR; } } if (!NSS_IsInitialized()) { initialized = 1; infof(conn->data, "Initializing NSS with certpath: %s\n", certDir ? certDir : "none"); if(!certDir) { rv = NSS_NoDB_Init(NULL); } else { char *certpath = PR_smprintf("%s%s", NSS_VersionCheck("3.12.0") ? "sql:" : "", certDir); rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY); PR_smprintf_free(certpath); } if(rv != SECSuccess) { infof(conn->data, "Unable to initialize NSS database\n"); curlerr = CURLE_SSL_CACERT_BADFILE; initialized = 0; PR_Unlock(nss_initlock); goto error; } } if(num_enabled_ciphers() == 0) NSS_SetDomesticPolicy(); #ifdef HAVE_PK11_CREATEGENERICOBJECT if(!mod) { char *configstring = aprintf("library=%s name=PEM", pem_library); if(!configstring) { PR_Unlock(nss_initlock); goto error; } mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); free(configstring); 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); } } #endif PK11_SetPasswordFunc(nss_get_password); } PR_Unlock(nss_initlock); model = PR_NewTCPSocket(); if(!model) goto error; model = SSL_ImportFD(NULL, model); if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) goto error; ssl2 = ssl3 = tlsv1 = PR_FALSE; switch (data->set.ssl.version) { default: case CURL_SSLVERSION_DEFAULT: ssl3 = tlsv1 = PR_TRUE; break; case CURL_SSLVERSION_TLSv1: tlsv1 = PR_TRUE; break; case CURL_SSLVERSION_SSLv2: ssl2 = PR_TRUE; break; case CURL_SSLVERSION_SSLv3: ssl3 = PR_TRUE; break; } if(SSL_OptionSet(model, SSL_ENABLE_SSL2, ssl2) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_ENABLE_SSL3, ssl3) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_ENABLE_TLS, tlsv1) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, ssl2) != SECSuccess) goto error; /* enable all ciphers from enable_ciphers_by_default */ cipher_to_enable = enable_ciphers_by_default; while (SSL_NULL_WITH_NULL_NULL != *cipher_to_enable) { if (SSL_CipherPrefSet(model, *cipher_to_enable, PR_TRUE) != SECSuccess) { curlerr = CURLE_SSL_CIPHER; goto error; } cipher_to_enable++; } if(data->set.ssl.cipher_list) { if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) { curlerr = CURLE_SSL_CIPHER; goto error; } } if(data->set.ssl.verifyhost == 1) infof(data, "warning: ignoring unsupported value (1) of ssl.verifyhost\n"); data->set.ssl.certverifyresult=0; /* not checked yet */ if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, conn) != SECSuccess) { goto error; } if(SSL_HandshakeCallback(model, (SSLHandshakeCallback) HandshakeCallback, NULL) != SECSuccess) goto error; if(!data->set.ssl.verifypeer) /* skip the verifying of the peer */ ; else if(data->set.ssl.CAfile) { int rc = nss_load_cert(&conn->ssl[sockindex], data->set.ssl.CAfile, PR_TRUE); if(!rc) { curlerr = CURLE_SSL_CACERT_BADFILE; goto error; } } 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)) { int rc; dir = PR_OpenDir(data->set.ssl.CApath); 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); rc = nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE); /* FIXME: check this return value! */ } /* 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.ssl.CRLfile) { int rc = nss_load_crl(data->set.ssl.CRLfile, PR_FALSE); if (!rc) { curlerr = CURLE_SSL_CRL_BADFILE; goto error; } infof(data, " CRLfile: %s\n", data->set.ssl.CRLfile ? data->set.ssl.CRLfile : "none"); } if(data->set.str[STRING_CERT]) { bool nickname_alloc = FALSE; char *nickname = fmt_nickname(data->set.str[STRING_CERT], &nickname_alloc); if(!nickname) return CURLE_OUT_OF_MEMORY; if(!cert_stuff(conn, sockindex, data->set.str[STRING_CERT], data->set.str[STRING_KEY])) { /* failf() is already done in cert_stuff() */ if(nickname_alloc) free(nickname); return CURLE_SSL_CERTPROBLEM; } /* this "takes over" the pointer to the allocated name or makes a dup of it */ connssl->client_nickname = nickname_alloc?nickname:strdup(nickname); if(!connssl->client_nickname) return CURLE_OUT_OF_MEMORY; } else connssl->client_nickname = NULL; if(SSL_GetClientAuthDataHook(model, SelectClientCert, (void *)connssl) != SECSuccess) { curlerr = CURLE_SSL_CERTPROBLEM; goto error; } /* Import our model socket onto the existing file descriptor */ connssl->handle = PR_ImportTCPSocket(sockfd); connssl->handle = SSL_ImportFD(model, connssl->handle); if(!connssl->handle) goto error; PR_Close(model); /* We don't need this any more */ /* This is the password associated with the cert that we're using */ if (data->set.str[STRING_KEY_PASSWD]) { SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]); } /* Force handshake on next I/O */ SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE); SSL_SetURL(connssl->handle, conn->host.name); /* Force the handshake now */ if(SSL_ForceHandshakeWithTimeout(connssl->handle, PR_SecondsToInterval(HANDSHAKE_TIMEOUT)) != SECSuccess) { if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) curlerr = CURLE_PEER_FAILED_VERIFICATION; else if(conn->data->set.ssl.certverifyresult!=0) curlerr = CURLE_SSL_CACERT; goto error; } connssl->state = ssl_connection_complete; display_conn_info(conn, connssl->handle); if (data->set.str[STRING_SSL_ISSUERCERT]) { SECStatus ret; bool nickname_alloc = FALSE; char *nickname = fmt_nickname(data->set.str[STRING_SSL_ISSUERCERT], &nickname_alloc); if(!nickname) return CURLE_OUT_OF_MEMORY; ret = check_issuer_cert(connssl->handle, nickname); if(nickname_alloc) free(nickname); if(SECFailure == ret) { infof(data,"SSL certificate issuer check failed\n"); curlerr = CURLE_SSL_ISSUER_ERROR; goto error; } else { infof(data, "SSL certificate issuer check ok\n"); } } return CURLE_OK; error: err = PR_GetError(); infof(data, "NSS error %d\n", err); if(model) PR_Close(model); return curlerr; }
CURLcode Curl_nss_connect(struct connectdata * conn, int sockindex) { PRInt32 err; PRFileDesc *model = NULL; PRBool ssl2, ssl3, tlsv1; struct SessionHandle *data = conn->data; curl_socket_t sockfd = conn->sock[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SECStatus rv; #ifdef HAVE_PK11_CREATEGENERICOBJECT char *configstring = NULL; #endif char *certDir = NULL; int curlerr; curlerr = CURLE_SSL_CONNECT_ERROR; /* FIXME. NSS doesn't support multiple databases open at the same time. */ if(!initialized) { initialized = 1; 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 = (char *)SSL_DIR; } } if(!certDir) { rv = NSS_NoDB_Init(NULL); } else { rv = NSS_Initialize(certDir, NULL, NULL, "secmod.db", NSS_INIT_READONLY); } if(rv != SECSuccess) { infof(conn->data, "Unable to initialize NSS database\n"); curlerr = CURLE_SSL_CACERT_BADFILE; goto error; } NSS_SetDomesticPolicy(); #ifdef HAVE_PK11_CREATEGENERICOBJECT configstring = (char *)malloc(PATH_MAX); PR_snprintf(configstring, PATH_MAX, "library=%s name=PEM", pem_library); mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); free(configstring); 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); } #endif } model = PR_NewTCPSocket(); if(!model) goto error; model = SSL_ImportFD(NULL, model); if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) goto error; ssl2 = ssl3 = tlsv1 = PR_FALSE; switch (data->set.ssl.version) { default: case CURL_SSLVERSION_DEFAULT: ssl2 = ssl3 = tlsv1 = PR_TRUE; break; case CURL_SSLVERSION_TLSv1: tlsv1 = PR_TRUE; break; case CURL_SSLVERSION_SSLv2: ssl2 = PR_TRUE; break; case CURL_SSLVERSION_SSLv3: ssl3 = PR_TRUE; break; } if(SSL_OptionSet(model, SSL_ENABLE_SSL2, ssl2) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_ENABLE_SSL3, ssl3) != SECSuccess) goto error; if(SSL_OptionSet(model, SSL_ENABLE_TLS, tlsv1) != SECSuccess) goto error; if(data->set.ssl.cipher_list) { if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) { curlerr = CURLE_SSL_CIPHER; goto error; } } data->set.ssl.certverifyresult=0; /* not checked yet */ if(SSL_BadCertHook(model, (SSLBadCertHandler) BadCertHandler, conn) != SECSuccess) { goto error; } if(SSL_HandshakeCallback(model, (SSLHandshakeCallback) HandshakeCallback, NULL) != SECSuccess) goto error; if(!data->set.ssl.verifypeer) /* skip the verifying of the peer */ ; else if (data->set.ssl.CAfile) { int rc = nss_load_cert(data->set.ssl.CAfile, PR_TRUE); if (!rc) { curlerr = CURLE_SSL_CACERT_BADFILE; goto error; } } 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)) { int rc; dir = PR_OpenDir(data->set.ssl.CApath); 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); rc = nss_load_cert(fullpath, PR_TRUE); /* FIXME: check this return value! */ } /* 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.str[STRING_CERT]) { char *n; char *nickname; nickname = (char *)malloc(PATH_MAX); if(is_file(data->set.str[STRING_CERT])) { n = strrchr(data->set.str[STRING_CERT], '/'); if (n) { n++; /* skip last slash */ snprintf(nickname, PATH_MAX, "PEM Token #%ld:%s", 1, n); } } else { strncpy(nickname, data->set.str[STRING_CERT], PATH_MAX); } if(nss_Init_Tokens(conn) != SECSuccess) { free(nickname); goto error; } if (!cert_stuff(conn, data->set.str[STRING_CERT], data->set.str[STRING_KEY])) { /* failf() is already done in cert_stuff() */ free(nickname); return CURLE_SSL_CERTPROBLEM; } connssl->client_nickname = strdup(nickname); if(SSL_GetClientAuthDataHook(model, (SSLGetClientAuthData) SelectClientCert, (void *)connssl->client_nickname) != SECSuccess) { curlerr = CURLE_SSL_CERTPROBLEM; goto error; } free(nickname); PK11_SetPasswordFunc(nss_no_password); } 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); 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); SSL_SetURL(connssl->handle, conn->host.name); /* Force the handshake now */ if (SSL_ForceHandshakeWithTimeout(connssl->handle, PR_SecondsToInterval(HANDSHAKE_TIMEOUT)) != SECSuccess) { if (conn->data->set.ssl.certverifyresult!=0) curlerr = CURLE_SSL_CACERT; goto error; } display_conn_info(conn, connssl->handle); return CURLE_OK; error: err = PR_GetError(); infof(data, "NSS error %d\n", err); if(model) PR_Close(model); return curlerr; }