/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /** BEGIN COPYRIGHT BLOCK * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * Copyright (C) 2007 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #include "nspr.h" #include "sslproto.h" #include "prerror.h" #include "ssl.h" #include "nss.h" #include "pk11func.h" #include "cert.h" #include "certt.h" #include "sslerr.h" #include "secerr.h" #include "httpClient/httpc/engine.h" #include "httpClient/httpc/http.h" #include "httpClient/httpc/PSPRUtil.h" #include "httpClient/httpc/Defines.h" //-- #include "httpClient/httpc/DebugLogger.h" #include "engine/RA.h" #include "main/Memory.h" char* certName = NULL; char* password = NULL; int ciphers[32]; int cipherCount = 0; int _doVerifyServerCert = 1; //-- static const char *DEBUG_MODULE = "httpclient"; //-- static const char *DEBUG_CLASS_NAME = "HttpEngine"; PRIntervalTime Engine::globaltimeout = PR_TicksPerSecond()*30; static char * ownPasswd( PK11SlotInfo *slot, PRBool retry, void *arg) { if (!retry) { if( password != NULL ) { return PL_strdup(password); } else { return PL_strdup( "httptest" ); } } else { return NULL; } } /** * Function: SECStatus myBadCertHandler() *
* Purpose: This callback is called when the incoming certificate is not * valid. We define a certain set of parameters that still cause the * certificate to be "valid" for this session, and return SECSuccess to cause * the server to continue processing the request when any of these conditions * are met. Otherwise, SECFailure is return and the server rejects the * request. */ SECStatus myBadCertHandler( void *arg, PRFileDesc *socket ) { SECStatus secStatus = SECFailure; PRErrorCode err; /* log invalid cert here */ if ( !arg ) { return secStatus; } *(PRErrorCode *)arg = err = PORT_GetError(); /* If any of the cases in the switch are met, then we will proceed */ /* with the processing of the request anyway. Otherwise, the default */ /* case will be reached and we will reject the request. */ switch (err) { case SEC_ERROR_INVALID_AVA: case SEC_ERROR_INVALID_TIME: case SEC_ERROR_BAD_SIGNATURE: case SEC_ERROR_EXPIRED_CERTIFICATE: case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_CERT_VALID: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CRL_EXPIRED: case SEC_ERROR_CRL_BAD_SIGNATURE: case SEC_ERROR_EXTENSION_VALUE_INVALID: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_CERT_USAGES_INVALID: case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: case SEC_ERROR_EXTENSION_NOT_FOUND: // Added by Rob 5/21/2002 secStatus = SECSuccess; break; default: secStatus = SECFailure; break; } return secStatus; } PRBool __EXPORT InitSecurity(char* certDir, char* certname, char* certpassword, char *prefix,int verify ) { if (certpassword) { password = PL_strdup(certpassword); } else { password = PL_strdup( "httptest" ); } if (certname) { certName = PL_strdup(certname); } SECStatus stat; PR_Init( PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 ); if (!NSS_IsInitialized()) { stat = NSS_Initialize( certDir, prefix, prefix,"secmod.db", NSS_INIT_READONLY); } else { stat = SECSuccess; RA::Debug( LL_PER_PDU, "initSecurity: ", "NSS Already initialized" ); } if (SECSuccess != stat) { // int err = PR_GetError(); return PR_FAILURE; } PK11_SetPasswordFunc(ownPasswd); stat = NSS_SetDomesticPolicy(); SSL_CipherPrefSetDefault( SSL_RSA_WITH_NULL_MD5, PR_TRUE ); _doVerifyServerCert = verify; return PR_TRUE; } int ssl2Suites[] = { SSL_EN_RC4_128_WITH_MD5, /* A */ SSL_EN_RC4_128_EXPORT40_WITH_MD5, /* B */ SSL_EN_RC2_128_CBC_WITH_MD5, /* C */ SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, /* D */ SSL_EN_DES_64_CBC_WITH_MD5, /* E */ SSL_EN_DES_192_EDE3_CBC_WITH_MD5, /* F */ 0 }; int ssl3Suites[] = { SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* a */ SSL_FORTEZZA_DMS_WITH_RC4_128_SHA, /* b */ SSL_RSA_WITH_RC4_128_MD5, /* c */ SSL_RSA_WITH_3DES_EDE_CBC_SHA, /* d */ SSL_RSA_WITH_DES_CBC_SHA, /* e */ SSL_RSA_EXPORT_WITH_RC4_40_MD5, /* f */ SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* g */ SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* h */ SSL_RSA_WITH_NULL_MD5, /* i */ SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, /* j */ SSL_RSA_FIPS_WITH_DES_CBC_SHA, /* k */ TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, /* l */ TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, /* m */ 0 }; int tlsSuites[] = { TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 0 }; void disableAllCiphersOnSocket(PRFileDesc* sock) { int i; int numsuites = SSL_NumImplementedCiphers; /* disable all the cipher suites for that socket */ for (i = 0; i 0; ) { /* do nothing */; } ciphers[cipherCount++] = cipher; } return PR_TRUE; } SECStatus certcallback ( void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer) { return SECSuccess; // always succeed } /** * Function: SECStatus myAuthCertificate() *
* Purpose: This function is our custom certificate authentication handler. *
* Note: This implementation is essentially the same as the default * SSL_AuthCertificate(). */ extern "C" { static SECStatus myAuthCertificate( void *arg, PRFileDesc *socket, PRBool checksig, PRBool isServer ) { SECCertUsage certUsage; CERTCertificate * cert; void * pinArg; char * hostName = NULL; SECStatus secStatus = SECSuccess; //-- static const char *DEBUG_METHOD_NAME = "myAuthCertificate"; //-- DebugLogger *logger = DebugLogger::GetDebugLogger( "httpclient"); if ( !arg || !socket ) { return SECFailure; } /* Define how the cert is being used based upon the isServer flag. */ certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; cert = SSL_PeerCertificate( socket ); pinArg = SSL_RevealPinArg( socket ); // Skip the server cert verification fconditionally, because our test // servers do not have a valid root CA cert. if ( _doVerifyServerCert ) { PRLock *verify_lock = RA::GetVerifyLock(); if (verify_lock == NULL) { return SECFailure; } PR_Lock(verify_lock); /* This function is not thread-safe. So we need to use a global lock */ secStatus = CERT_VerifyCertNow( (CERTCertDBHandle *)arg, cert, checksig, certUsage, pinArg); PR_Unlock(verify_lock); if( SECSuccess != secStatus ) { //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, if (cert == NULL) { RA::Debug( LL_PER_PDU, "myAuthCertificate: ", "Server Certificate Not Found" ); } else { if (cert->subjectName == NULL) { RA::Debug( LL_PER_PDU, "myAuthCertificate: ", "Untrusted server certificate" ); } else { RA::Debug( LL_PER_PDU, "myAuthCertificate: ", "Untrusted server certificate error=%d subject='%s'", PORT_GetError(), cert->subjectName ); } } } } /* If this is a server, we're finished. */ if (isServer || secStatus != SECSuccess) { return secStatus; } /* Certificate is OK. Since this is the client side of an SSL * connection, we need to verify that the name field in the cert * matches the desired hostname. This is our defense against * man-in-the-middle attacks. */ /* SSL_RevealURL returns a hostName, not an URL. */ hostName = SSL_RevealURL( socket ); if (hostName && hostName[0]) { secStatus = CERT_VerifyCertName( cert, hostName ); if( SECSuccess != secStatus ) { //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "myAuthCertificate: ", "Server name does not match that in certificate" ); } } else { secStatus = SECFailure; //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "myAuthCertificate: ", "server name has been specified" ); } if( hostName != NULL ) { PR_Free( hostName ); hostName = NULL; } return secStatus; } /* Function: SECStatus ownGetClientAuthData() * * Purpose: This callback is used by SSL to pull client certificate * information upon server request. */ static SECStatus ownGetClientAuthData(void *arg, PRFileDesc *socket, CERTDistNames *caNames, CERTCertificate **pRetCert,/*return */ SECKEYPrivateKey **pRetKey) { CERTCertificate * cert = NULL; SECKEYPrivateKey * privKey = NULL; void * proto_win = NULL; SECStatus rv = SECFailure; char * localNickName = (char *)arg; proto_win = SSL_RevealPinArg(socket); if (localNickName) { RA::Debug( LL_PER_PDU, "ownGetClientAuthData: ", "ownGetClientAuthData looking for nickname=%s", localNickName ); cert = PK11_FindCertFromNickname(localNickName, proto_win); if (cert) { RA::Debug( LL_PER_PDU, "ownGetClientAuthData: ", "ownGetClientAuthData found cert" ); privKey = PK11_FindKeyByAnyCert(cert, proto_win); if (privKey) { RA::Debug( LL_PER_PDU, "ownGetClientAuthData: ", "ownGetClientAuthData found priv key for cert" ); rv = SECSuccess; } else { if( cert != NULL ) { CERT_DestroyCertificate( cert ); cert = NULL; } } } else { RA::Debug( LL_PER_PDU, "ownGetClientAuthData: ", "ownGetClientAuthData did NOT find cert" ); } if (rv == SECSuccess) { *pRetCert = cert; *pRetKey = privKey; } // if( localNickName != NULL ) { // free( localNickName ); // localNickName = NULL; // } return rv; } else { RA::Debug( LL_PER_PDU, "ownGetClientAuthData: ", "ownGetClientAuthData does not have nickname" ); } char* chosenNickName = certName ? (char *)PL_strdup(certName) : NULL; if (chosenNickName) { cert = PK11_FindCertFromNickname(chosenNickName, proto_win); if (cert) { privKey = PK11_FindKeyByAnyCert(cert, proto_win); if (privKey) { rv = SECSuccess; } else { if( cert != NULL ) { CERT_DestroyCertificate( cert ); cert = NULL; } } } } else { /* no nickname given, automatically find the right cert */ CERTCertNicknames * names; int i; names = CERT_GetCertNicknames( CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, proto_win); if (names != NULL) { for( i=0; i < names->numnicknames; i++ ) { cert = PK11_FindCertFromNickname(names->nicknames[i], proto_win); if (!cert) { continue; } /* Only check unexpired certs */ if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) != secCertTimeValid) { if( cert != NULL ) { CERT_DestroyCertificate( cert ); cert = NULL; } continue; } rv = NSS_CmpCertChainWCANames(cert, caNames); if (rv == SECSuccess) { privKey = PK11_FindKeyByAnyCert(cert, proto_win); if (privKey) { // got the key break; } // cert database password was probably wrong rv = SECFailure; break; }; } /* for loop */ CERT_FreeNicknames(names); } // names } // no nickname chosen if (rv == SECSuccess) { *pRetCert = cert; *pRetKey = privKey; } if( chosenNickName != NULL ) { free( chosenNickName ); chosenNickName = NULL; } return rv; } } // extern "C" void nodelay(PRFileDesc* fd) { PRSocketOptionData opt; PRStatus rv; opt.option = PR_SockOpt_NoDelay; opt.value.no_delay = PR_FALSE; rv = PR_GetSocketOption(fd, &opt); if (rv == PR_FAILURE) { return; } opt.option = PR_SockOpt_NoDelay; opt.value.no_delay = PR_TRUE; rv = PR_SetSocketOption(fd, &opt); if (rv == PR_FAILURE) { return; } return; } void __EXPORT setDefaultAllTLSCiphers() { int i =0; char alg[256]; while (tlsSuites[i]) { PR_snprintf((char *)alg, 256, "%x", tlsSuites[i]); RA::Debug( LL_PER_PDU, "setDefaultAllTLSCiphers", alg); SSL_CipherPrefSetDefault(tlsSuites[i++], PR_TRUE); } RA::Debug( LL_PER_PDU, "setDefaultAllTLSCiphers", "number of ciphers set:%d", i); } /** * Returns a file descriptor for I/O if the HTTP connection is successful * @param addr PRnetAddr structure which points to the server to connect to * @param SSLOn boo;elan to state if this is an SSL client */ PRFileDesc * Engine::_doConnect(PRNetAddr *addr, PRBool SSLOn, const PRInt32* cipherSuite, PRInt32 count, const char *nickName, PRBool handshake, /*const SecurityProtocols& secprots,*/ const char *serverName, PRIntervalTime timeout) { //-- static const char *DEBUG_METHOD_NAME = "doConnect"; //-- DebugLogger *logger = DebugLogger::GetDebugLogger( "httpclient"); PRFileDesc *tcpsock = NULL; PRFileDesc *sock = NULL; setDefaultAllTLSCiphers(); tcpsock = PR_OpenTCPSocket(addr->raw.family); if (nickName != NULL) RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "_doConnect has nickname=%s", nickName ); else RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "_doConnect has nickname=NULL" ); if (!tcpsock) { //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, //XXXX log NSPR error code RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "PR_OpenTCPSocket returned NULL" ); return NULL; } nodelay(tcpsock); if (PR_TRUE == SSLOn) { RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "SSL is ON" ); sock=SSL_ImportFD(NULL, tcpsock); if (!sock) { //xxx log if( tcpsock != NULL ) { PR_Close( tcpsock ); tcpsock = NULL; } return NULL; } int error = 0; PRBool rv = SSL_OptionSet(sock, SSL_SECURITY, 1); if ( SECSuccess == rv ) { rv = SSL_OptionSet(sock, SSL_HANDSHAKE_AS_CLIENT, 1); } if ( SECSuccess == rv ) { rv = SSL_OptionSet(sock, SSL_ENABLE_SSL3, PR_TRUE); } if ( SECSuccess == rv ) { rv = SSL_OptionSet(sock, SSL_ENABLE_TLS, PR_TRUE); } if ( SECSuccess != rv ) { error = PORT_GetError(); if( sock != NULL ) { PR_Close( sock ); sock = NULL; } //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "SSL_OptionSet error: %d", error ); return NULL; } rv = SSL_GetClientAuthDataHook( sock, ownGetClientAuthData, (void*)nickName); if ( SECSuccess != rv ) { error = PORT_GetError(); if( sock != NULL ) { PR_Close( sock ); sock = NULL; } //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "SSL_GetClientAuthDataHook error: %d", error ); return NULL; } rv = SSL_AuthCertificateHook(sock, (SSLAuthCertificate)myAuthCertificate, (void *)CERT_GetDefaultCertDB()); if (rv != SECSuccess ) { if( sock != NULL ) { PR_Close( sock ); sock = NULL; } return NULL; } PRErrorCode errCode = 0; rv = SSL_BadCertHook( sock, (SSLBadCertHandler)myBadCertHandler, &errCode ); rv = SSL_SetURL( sock, serverName ); if (rv != SECSuccess ) { error = PORT_GetError(); if( sock != NULL ) { PR_Close( sock ); sock = NULL; } //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "SSL_SetURL error: %d", error ); return NULL; } RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "end SSL is ON" ); //EnableAllTLSCiphers( sock); //EnableAllSSL3Ciphers( sock); } else { RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "SSL is OFF" ); sock = tcpsock; } RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "about to call PR_Connect, timeout =%d", timeout ); if ( PR_Connect(sock, addr, timeout) == PR_FAILURE ) { //-- logger->Log( LOGLEVEL_SEVERE, DEBUG_CLASS_NAME, //-- DEBUG_METHOD_NAME, RA::Debug( LL_PER_PDU, "Engine::_doConnect: ", "PR_Connect error: %d Msg=%s", PR_GetError(), "XXX" ); if( sock != NULL ) { PR_Close( sock ); sock = NULL; } return NULL; } return (sock); } /** * Called from higher level to connect, sends a request * and gets a response as an HttpResponse object * * @param request Contains the entire request url + headers etc * @param server Has the host, port, protocol info * @param timeout Time in seconds to wait for a response * @return The response body and headers */ PSHttpResponse * HttpEngine::makeRequest( PSHttpRequest &request, const PSHttpServer& server, int timeout, PRBool expectChunked ) { PRNetAddr addr; PRFileDesc *sock = NULL; PSHttpResponse *resp = NULL; PRBool response_code = 0; server.getAddr(&addr); char *nickName = request.getCertNickName(); char *serverName = (char *)server.getAddr(); sock = _doConnect( &addr, request.isSSL(), 0, 0,nickName, 0, serverName ); if ( sock != NULL) { PRBool status = request.send( sock ); if ( status ) { resp = new PSHttpResponse( sock, &request, timeout, expectChunked ); response_code = resp->processResponse(); RA::Debug( LL_PER_PDU, "HttpEngine::makeRequest: ", "makeRequest response %d", response_code ); if(!response_code) { RA::Debug( LL_PER_PDU, "HttpEngine::makeRequest: ", "Deleting response because of FALSE return, returning NULL." ); if( resp != NULL ) { delete resp; resp = NULL; } if( sock != NULL ) { PR_Close( sock ); sock = NULL; } return NULL; } } if( sock != NULL ) { PR_Close( sock ); sock = NULL; } } return resp; }