2441 lines
75 KiB
C
2441 lines
75 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* The contents of this file are subject to the Mozilla Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1994-2000 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* terms of the GNU General Public License Version 2 or later (the
|
|
* "GPL"), in which case the provisions of the GPL are applicable
|
|
* instead of those above. If you wish to allow use of your
|
|
* version of this file only under the terms of the GPL and not to
|
|
* allow others to use your version of this file under the MPL,
|
|
* indicate your decision by deleting the provisions above and
|
|
* replace them with the notice and other provisions required by
|
|
* the GPL. If you do not delete the provisions above, a recipient
|
|
* may use your version of this file under either the MPL or the
|
|
* GPL.
|
|
*/
|
|
#include "sslconn.h"
|
|
#include "ctrlconn.h"
|
|
#include "sslerror.h"
|
|
#include "serv.h"
|
|
#include "servimpl.h"
|
|
#include "sslskst.h"
|
|
#include "ssmerrs.h"
|
|
#include "ssldlgs.h"
|
|
#include "collectn.h"
|
|
#include "prefs.h"
|
|
|
|
/* NSS headers */
|
|
#include "seccomon.h"
|
|
#include "keyt.h"
|
|
#include "cert.h"
|
|
#include "secasn1.h"
|
|
#include "genname.h"
|
|
|
|
/* Shorthand macros for inherited classes */
|
|
#define SSMRESOURCE(sslconn) (&(sslconn)->super.super.super)
|
|
#define SSMCONNECTION(sslconn) (&(sslconn)->super.super)
|
|
#define SSMDATACONNECTION(sslconn) (&(sslconn)->super)
|
|
#define SSMCONTROLCONNECTION(sslconn) ((SSMControlConnection*)(sslconn->super.super.m_parent))
|
|
|
|
/* SSM_UserCertChoice: enum for cert choice info */
|
|
typedef enum {ASK, AUTO} SSM_UserCertChoice;
|
|
|
|
/* persistent cert choice object we will use here */
|
|
static SSM_UserCertChoice certChoice;
|
|
|
|
/* strings for marking invalid user cert nicknames */
|
|
#define NICKNAME_EXPIRED_STRING " (expired)"
|
|
#define NICKNAME_NOT_YET_VALID_STRING " (not yet valid)"
|
|
|
|
/* private function prototypes */
|
|
void SSMSSLDataConnection_InitializeUIInfo(SSMSSLUIInfo* info);
|
|
void SSMSSLDataConnection_DestroyUIInfo(SSMSSLUIInfo* info);
|
|
SSMStatus SSMSSLDataConnection_UpdateSecurityStatus(SSMSSLDataConnection* conn);
|
|
SECStatus SSM_SetupSSLSocket(PRFileDesc* socket, PRFileDesc** sslsocket,
|
|
CERTCertDBHandle* handle, char* hostname,
|
|
void* wincx);
|
|
SSMStatus SSM_GetSSLSocket(SSMSSLDataConnection* conn);
|
|
void SSM_SSLDataServiceThread(void* arg);
|
|
SSMStatus SSM_SetUserCertChoice(SSMSSLDataConnection* conn);
|
|
SECStatus SSM_ConvertCANamesToStrings(PRArenaPool* arena, char** caNameStrings,
|
|
CERTDistNames* caNames);
|
|
SSMStatus ssm_client_auth_prepare_nicknames(SSMSSLDataConnection* conn,
|
|
CERTCertNicknames* nicknames);
|
|
PRBool SSM_SSLErrorNeedsDialog(int error);
|
|
SECStatus SSM_SSLMakeBadServerCertDialog(int error,
|
|
CERTCertificate* cert,
|
|
SSMSSLDataConnection* conn);
|
|
SECStatus SSM_SSLVerifyServerCert(CERTCertDBHandle* handle,
|
|
CERTCertificate* cert, PRBool checkSig,
|
|
SSMSSLDataConnection* conn);
|
|
PRStatus SSMSSLDataConnection_TLSStepUp(SSMSSLDataConnection* conn);
|
|
/* callback functions */
|
|
SECStatus SSM_SSLAuthCertificate(void* arg, PRFileDesc* socket,
|
|
PRBool checkSig, PRBool isServer);
|
|
SECStatus SSM_SSLBadCertHandler(void* arg, PRFileDesc* socket);
|
|
SECStatus SSM_SSLGetClientAuthData(void* arg,
|
|
PRFileDesc* socket,
|
|
struct CERTDistNamesStr* caNames,
|
|
struct CERTCertificateStr** pRetCert,
|
|
struct SECKEYPrivateKeyStr** pRetKey);
|
|
void SSM_SSLHandshakeCallback(PRFileDesc* socket, void* clientData);
|
|
|
|
|
|
|
|
/* implementations */
|
|
/* class functions */
|
|
SSMStatus SSMSSLDataConnection_Create(void* arg,
|
|
SSMControlConnection* ctrlconn,
|
|
SSMResource** res)
|
|
{
|
|
SSMStatus rv = PR_SUCCESS;
|
|
SSMSSLDataConnection* conn;
|
|
|
|
/* check arguments */
|
|
if (arg == NULL || res == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
*res = NULL; /* in case we fail */
|
|
|
|
conn = (SSMSSLDataConnection*)PR_CALLOC(sizeof(SSMSSLDataConnection));
|
|
if (!conn) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSMSSLDataConnection_Init(conn, (SSMInfoSSL*)arg,
|
|
SSM_RESTYPE_SSL_DATA_CONNECTION);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
SSMSSLDataConnection_Invariant(conn);
|
|
|
|
*res = &conn->super.super.super;
|
|
return PR_SUCCESS;
|
|
|
|
loser:
|
|
if (rv == PR_SUCCESS) {
|
|
rv = PR_FAILURE;
|
|
}
|
|
|
|
if (conn) {
|
|
SSM_ShutdownResource(SSMRESOURCE(conn), rv);
|
|
SSM_FreeResource(SSMRESOURCE(conn));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_Shutdown(SSMResource* arg, SSMStatus status)
|
|
{
|
|
SSMStatus rv;
|
|
SSMSSLDataConnection* conn = (SSMSSLDataConnection*)arg;
|
|
PRBool firstTime = PR_TRUE;
|
|
/* ### sjlee: firstTime doesn't seem to be set anywhere */
|
|
|
|
/* check argument */
|
|
if (arg == NULL) {
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
/*SSMSSLDataConnection_Invariant(conn); -- this could have been called from loser */
|
|
|
|
SSM_LockResource(arg);
|
|
|
|
/* shut down base class */
|
|
rv = SSMDataConnection_Shutdown(arg, status);
|
|
|
|
if (firstTime) {
|
|
SSM_DEBUG("First time shutting down SSL connection.\n");
|
|
}
|
|
|
|
/* If service threads are done, close the SSL socket */
|
|
if (SSMRESOURCE(conn)->m_threadCount == 0) {
|
|
if (conn->socketSSL) {
|
|
SSM_DEBUG("Closing SSL socket with linger.\n");
|
|
rv = PR_Close(conn->socketSSL);
|
|
conn->socketSSL = NULL;
|
|
SSM_DEBUG("Closed SSL socket (rv == %d).\n", rv);
|
|
}
|
|
}
|
|
SSM_UnlockResource(arg);
|
|
|
|
if (!firstTime) {
|
|
rv = (SSMStatus)SSM_ERR_ALREADY_SHUT_DOWN;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_Init(SSMSSLDataConnection* conn,
|
|
SSMInfoSSL* info, SSMResourceType type)
|
|
{
|
|
SSMStatus rv = PR_SUCCESS;
|
|
SSMControlConnection* parent;
|
|
|
|
/* check arguments */
|
|
if (conn == NULL || info == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* fill in the information we got from the request packet */
|
|
parent = info->parent;
|
|
rv = SSMDataConnection_Init(SSMDATACONNECTION(conn), parent, type);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
conn->isTLS = info->isTLS;
|
|
conn->port = info->port;
|
|
conn->hostIP = info->hostIP;
|
|
conn->hostName = info->hostName;
|
|
/* Initialize UI info */
|
|
SSMSSLDataConnection_InitializeUIInfo(&conn->m_UIInfo);
|
|
|
|
if (conn->isTLS) {
|
|
conn->isSecure = PR_FALSE;
|
|
|
|
/* Create the step-up FD so that the control connection
|
|
can wake us up */
|
|
conn->stepUpFD = PR_NewPollableEvent();
|
|
if (conn->stepUpFD == NULL)
|
|
goto loser;
|
|
}
|
|
else {
|
|
conn->isSecure = PR_TRUE;
|
|
conn->forceHandshake = info->forceHandshake;
|
|
|
|
/* Save the client context */
|
|
SSMRESOURCE(conn)->m_clientContext = info->clientContext;
|
|
}
|
|
|
|
/* Spawn the data service thread */
|
|
SSM_DEBUG("Creating SSL data service thread. Getting ref on SSL "
|
|
"connection.\n");
|
|
|
|
/*
|
|
* This reference is for the socket status. The client needs to tell
|
|
* us when to get rid of it.
|
|
*/
|
|
SSM_GetResourceReference(SSMRESOURCE(conn));
|
|
|
|
SSMDATACONNECTION(conn)->m_dataServiceThread =
|
|
SSM_CreateThread(SSMRESOURCE(conn), SSM_SSLDataServiceThread);
|
|
if (SSMDATACONNECTION(conn)->m_dataServiceThread == NULL) {
|
|
goto loser;
|
|
}
|
|
conn->m_statusFetched = PR_FALSE;
|
|
return PR_SUCCESS;
|
|
|
|
loser:
|
|
if (rv == PR_SUCCESS) {
|
|
rv = PR_FAILURE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_Destroy(SSMResource* res, PRBool doFree)
|
|
{
|
|
SSMSSLDataConnection* conn = (SSMSSLDataConnection*)res;
|
|
|
|
if (res == NULL) {
|
|
return PR_FAILURE;
|
|
}
|
|
/* We should be shut down. */
|
|
PR_ASSERT(res->m_threadCount == 0);
|
|
|
|
/* Destroy our fields. */
|
|
PR_FREEIF(conn->hostIP);
|
|
PR_FREEIF(conn->hostName);
|
|
|
|
/* Destroy UI info. */
|
|
SSMSSLDataConnection_DestroyUIInfo(&conn->m_UIInfo);
|
|
|
|
/* Destroy step-up FD if we still have one. */
|
|
if (conn->stepUpFD)
|
|
PR_DestroyPollableEvent(conn->stepUpFD);
|
|
|
|
/* Destroy superclass fields. */
|
|
SSMDataConnection_Destroy(SSMRESOURCE(conn), PR_FALSE);
|
|
|
|
/* Destroy the socket status if it's there. */
|
|
if (conn->m_sockStat) {
|
|
SSM_FreeResource(&conn->m_sockStat->super);
|
|
}
|
|
|
|
/* Free the connection object if asked. */
|
|
if (doFree) {
|
|
PR_DELETE(conn);
|
|
}
|
|
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
void SSMSSLDataConnection_Invariant(SSMSSLDataConnection* conn)
|
|
{
|
|
SSMDataConnection_Invariant(SSMDATACONNECTION(conn));
|
|
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
PR_ASSERT(SSM_IsAKindOf(SSMRESOURCE(conn),
|
|
SSM_RESTYPE_SSL_DATA_CONNECTION));
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_GetAttrIDs(SSMResource* res,
|
|
SSMAttributeID** ids, PRIntn* count)
|
|
{
|
|
SSMStatus rv;
|
|
|
|
if (res == NULL || ids == NULL || count == NULL) {
|
|
goto loser;
|
|
}
|
|
rv = SSMDataConnection_GetAttrIDs(res, ids, count);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
*ids = (SSMAttributeID *) PR_REALLOC(*ids, (*count + 2)*sizeof(SSMAttributeID));
|
|
if (!*ids) {
|
|
goto loser;
|
|
}
|
|
|
|
(*ids)[*count++] = SSM_FID_SSLDATA_SOCKET_STATUS;
|
|
(*ids)[*count++] = SSM_FID_SSLDATA_DISCARD_SOCKET_STATUS;
|
|
goto done;
|
|
loser:
|
|
if (rv == PR_SUCCESS) {
|
|
rv = PR_FAILURE;
|
|
}
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_SetAttr(SSMResource * res,
|
|
SSMResourceAttrType attrType,
|
|
SSMAttributeValue *value)
|
|
{
|
|
SSMStatus rv = SSM_FAILURE;
|
|
SSMSSLDataConnection *conn = (SSMSSLDataConnection*)res;
|
|
|
|
if (!SSM_IsAKindOf(res, SSM_RESTYPE_SSL_DATA_CONNECTION)) {
|
|
return SSM_FAILURE;
|
|
}
|
|
switch (attrType) {
|
|
case SSM_FID_SSLDATA_DISCARD_SOCKET_STATUS:
|
|
/*
|
|
* Just release the reference we were holding onto.
|
|
*/
|
|
if (!conn->m_statusFetched) {
|
|
SSM_FreeResource(res);
|
|
conn->m_statusFetched = PR_TRUE;
|
|
}
|
|
rv = SSM_SUCCESS;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_GetAttr(SSMResource *res, SSMAttributeID attrID,
|
|
SSMResourceAttrType attrType,
|
|
SSMAttributeValue *value)
|
|
{
|
|
SSMSSLDataConnection* conn = (SSMSSLDataConnection*)res;
|
|
SSMStatus rv = PR_SUCCESS;
|
|
|
|
if (res == NULL || value == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* see what it is */
|
|
switch(attrID) {
|
|
case SSM_FID_SSLDATA_SOCKET_STATUS:
|
|
/* if the socket status does not exist at this time (which
|
|
* is very unlikely usually), we will wait until the handshake
|
|
* callback creates it
|
|
*/
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
while (conn->m_sockStat == NULL ||
|
|
conn->m_sockStat->m_cipherName == NULL) {
|
|
SSM_DEBUG("Oops, the security status has not been updated. Waiting...\n");
|
|
SSM_WaitResource(SSMRESOURCE(conn), PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
if (conn->m_sockStat == NULL) {
|
|
SSM_DEBUG("No socket status on dead socket.\n");
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
goto loser;
|
|
}
|
|
/* We have a socket status object, return its resource ID. */
|
|
value->u.rid = conn->m_sockStat->super.m_id;
|
|
value->type = SSM_RID_ATTRIBUTE;
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
|
|
/*
|
|
if (conn->m_sockStat) {
|
|
}
|
|
else {
|
|
rv = SSM_ERR_ATTRIBUTE_MISSING;
|
|
goto loser;
|
|
}
|
|
*/
|
|
break;
|
|
case SSM_FID_SSLDATA_ERROR_VALUE:
|
|
value->type = SSM_NUMERIC_ATTRIBUTE;
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
value->u.numeric = conn->m_error;
|
|
SSM_DEBUG("Reported error: %ld\n", conn->m_error);
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
break;
|
|
default:
|
|
rv = SSMConnection_GetAttr(res, attrID, attrType, value);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
goto done;
|
|
loser:
|
|
value->type = SSM_NO_ATTRIBUTE;
|
|
if (rv == PR_SUCCESS) {
|
|
rv = PR_FAILURE;
|
|
}
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: SSMStatus SSMSSLDataConnection_PickleSecurityStatus()
|
|
* Purpose: fills in information on security status (pickled socket status
|
|
* and the security level) on PickleSecurityStatus request
|
|
*
|
|
* Arguments and return values:
|
|
* - conn: SSL connection object
|
|
* - len: length of the pickled data
|
|
* - blob: pickled data
|
|
* - securityLevel: security level
|
|
* - returns: PR_SUCCESS if successful; error code otherwise
|
|
*
|
|
* Note: Note that this is not really a pickle class function. This is
|
|
* specially designed to handle security status requests efficiently.
|
|
*/
|
|
SSMStatus SSMSSLDataConnection_PickleSecurityStatus(SSMSSLDataConnection* conn,
|
|
PRIntn* len, void** blob,
|
|
PRIntn* securityLevel)
|
|
{
|
|
SSMStatus rv = PR_SUCCESS;
|
|
|
|
PR_ASSERT(conn != NULL);
|
|
|
|
/* in case of failure */
|
|
*len = 0;
|
|
*blob = NULL;
|
|
*securityLevel = 0;
|
|
|
|
/* the connection may not be secure yet in case of proxy connections:
|
|
* we should just return and indicate no security if that's the case
|
|
*/
|
|
if (!conn->isSecure) {
|
|
return rv;
|
|
}
|
|
|
|
/* if the socket status does not exist at this time (which is usually
|
|
* very unlikely), we will wait until the handshake callback creates it
|
|
*/
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
if (conn->m_sockStat == NULL ||
|
|
conn->m_sockStat->m_cipherName == NULL) {
|
|
SSM_DEBUG("Oops, the security status has not been updated. "
|
|
"Waiting...\n");
|
|
SSM_WaitResource(SSMRESOURCE(conn), PR_TicksPerSecond());
|
|
}
|
|
if (conn->m_sockStat == NULL) {
|
|
SSM_DEBUG("No socket status on dead socket.\n");
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
rv = PR_FAILURE;
|
|
goto loser;
|
|
}
|
|
/* it's probably OK to unlock it here */
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
|
|
/* we have a socket status object. */
|
|
rv = SSMSSLSocketStatus_Pickle(&conn->m_sockStat->super, len, blob);
|
|
if (rv != PR_SUCCESS) {
|
|
SSM_DEBUG("Failed to pickle the socket status.\n");
|
|
goto loser;
|
|
}
|
|
|
|
/* get the security level */
|
|
*securityLevel = conn->m_sockStat->m_level;
|
|
|
|
/*
|
|
* The client retrieved the socket status, so we can release our
|
|
* refernce now.
|
|
*/
|
|
if (!conn->m_statusFetched) {
|
|
SSM_FreeResource(SSMRESOURCE(conn));
|
|
conn->m_statusFetched = PR_TRUE;
|
|
}
|
|
loser:
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: SSMStatus SSMSSLDataConnection_FormSubmitHandler()
|
|
* Purpose: handles any UI form submission that has an SSMSSLDataConnection
|
|
* object as target
|
|
* Expected request:
|
|
* - command: "formsubmit_handler"
|
|
* - baseRef: "formsubmit_dosubmit_js"
|
|
* - target: an SSMSSLDataConnection object
|
|
* - formName: "cert_selection" | "bad_server_cert"
|
|
* Arguments and return values
|
|
* - res: SSMSSLDataConnection object
|
|
* - req: the HTTPRequest object to be processed
|
|
* - returns: SSM_SUCCESS if successful; error otherwise
|
|
*
|
|
* Note: add more handling helper functions if there are other form
|
|
* submission scenarios
|
|
*/
|
|
SSMStatus SSMSSLDataConnection_FormSubmitHandler(SSMResource* res,
|
|
HTTPRequest* req)
|
|
{
|
|
SSMStatus rv;
|
|
char* tmpStr = NULL;
|
|
SSMSSLDataConnection* conn;
|
|
|
|
|
|
conn = (SSMSSLDataConnection*)res;
|
|
|
|
/* make sure you got the right baseRef */
|
|
rv = SSM_HTTPParamValue(req, "baseRef", &tmpStr);
|
|
if (rv != SSM_SUCCESS ||
|
|
PL_strcmp(tmpStr, "windowclose_doclose_js") != 0) {
|
|
goto loser;
|
|
}
|
|
|
|
/* differentiate among different form submission events by looking up
|
|
* formName, and dispatch the request to the appropriate handler
|
|
*/
|
|
/* cleaning up (unlocking monitors, etc.) is handled by individual
|
|
* handlers, so just go to done
|
|
*/
|
|
if (res->m_formName == NULL) {
|
|
goto loser;
|
|
}
|
|
if (PL_strcmp(res->m_formName, "cert_selection") == 0) {
|
|
rv = SSM_ClientAuthCertSelectionButtonHandler(req);
|
|
goto done;
|
|
}
|
|
else if (PL_strcmp(res->m_formName, "bad_server_cert") == 0) {
|
|
/* server auth failure other than unknown issuer case */
|
|
rv = SSM_ServerAuthFailureButtonHandler(req);
|
|
goto done;
|
|
}
|
|
else {
|
|
/* unknown form name; abort */
|
|
SSM_DEBUG("don't know how to handle this form.\n");
|
|
goto loser;
|
|
}
|
|
loser:
|
|
if (rv == SSM_SUCCESS) {
|
|
rv = SSM_FAILURE;
|
|
}
|
|
SSM_LockResource(req->target);
|
|
conn->m_UIInfo.chosen = -1;
|
|
conn->m_UIInfo.trustBadServerCert = BSCA_NO;
|
|
conn->m_UIInfo.UICompleted = PR_TRUE;
|
|
SSM_NotifyResource(req->target);
|
|
SSM_UnlockResource(req->target);
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
|
|
void SSMSSLDataConnection_InitializeUIInfo(SSMSSLUIInfo* info)
|
|
{
|
|
info->UICompleted = PR_FALSE;
|
|
info->numFilteredCerts = 0;
|
|
info->certNicknames = NULL;
|
|
info->chosen = -1; /* negative value denotes no cert was chosen */
|
|
info->trustBadServerCert = BSCA_NO;
|
|
}
|
|
|
|
void SSMSSLDataConnection_DestroyUIInfo(SSMSSLUIInfo* info)
|
|
{
|
|
int i;
|
|
|
|
if (info->numFilteredCerts != 0) {
|
|
for (i = 0; i < info->numFilteredCerts; i++) {
|
|
PR_FREEIF(info->certNicknames[i]);
|
|
}
|
|
PR_FREEIF(info->certNicknames);
|
|
}
|
|
}
|
|
|
|
SSMStatus SSMSSLDataConnection_UpdateSecurityStatus(SSMSSLDataConnection* conn)
|
|
{
|
|
SSMStatus rv = PR_SUCCESS;
|
|
|
|
PR_ASSERT(conn != NULL);
|
|
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
if (conn->m_sockStat == NULL) {
|
|
/* socket status has not been created. The security status will
|
|
* be properly populated by creating the socket status.
|
|
*/
|
|
SSMResourceID statRID;
|
|
|
|
SSM_CreateResource(SSM_RESTYPE_SSL_SOCKET_STATUS, conn->socketSSL,
|
|
SSMRESOURCE(conn)->m_connection, &statRID,
|
|
(SSMResource**)&conn->m_sockStat);
|
|
conn->m_sockStat->m_error = conn->m_sslServerError;
|
|
/*
|
|
* Just in case someone's waiting on the resource.
|
|
*/
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
SSM_NotifyResource(SSMRESOURCE(conn));
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
}
|
|
else {
|
|
/* just update the values */
|
|
rv = SSMSSLSocketStatus_UpdateSecurityStatus(conn->m_sockStat,
|
|
conn->socketSSL);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
}
|
|
loser:
|
|
/* in case we fail to update the security status, we still notify
|
|
* because we don't want to quit just because this failed...
|
|
*/
|
|
SSM_NotifyResource(SSMRESOURCE(conn));
|
|
/* notify SSMSSLDataConnection_GetAttr() for the socket RID */
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/* updates the error code in case of an exception: returns the current
|
|
* error code (0 if no error)
|
|
*/
|
|
static
|
|
SSMStatus SSMSSLDataConnection_UpdateErrorCode(SSMSSLDataConnection* conn)
|
|
{
|
|
PR_ASSERT(conn != NULL);
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
if (conn->m_error == 0) {
|
|
/* update the error code only if it is not already set:
|
|
* otherwise preserve the initial error code
|
|
*/
|
|
PRInt32 rv;
|
|
|
|
rv = PR_GetError();
|
|
if (rv != SEC_ERROR_LIBRARY_FAILURE) {
|
|
/* XXX sometimes NSS reports this error even when connection
|
|
* terminates normally: will escape this error here so that
|
|
* this is not reported erroneously
|
|
*/
|
|
conn->m_error = rv;
|
|
}
|
|
}
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
|
|
return conn->m_error;
|
|
}
|
|
|
|
/* functions for setting up and configuring SSL connection */
|
|
SECStatus SSM_SetupSSLSocket(PRFileDesc* socket, PRFileDesc** sslsocket,
|
|
CERTCertDBHandle* handle, char* hostname,
|
|
void* wincx)
|
|
{
|
|
SECStatus rv = SECFailure;
|
|
|
|
/* check arguments */
|
|
if ((socket == NULL) || (sslsocket == NULL) || (handle == NULL) ||
|
|
(hostname == NULL) || (wincx == NULL)) {
|
|
goto loser;
|
|
}
|
|
*sslsocket = NULL;
|
|
|
|
/* import the socket into SSL layer */
|
|
*sslsocket = SSL_ImportFD(NULL, socket);
|
|
if (*sslsocket == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* set some SSL settings for the socket */
|
|
rv = SSL_Enable(*sslsocket, SSL_SECURITY, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_Enable(*sslsocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_Enable(*sslsocket, SSL_ENABLE_FDX, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* set callbacks */
|
|
/* client authentication */
|
|
rv = SSL_GetClientAuthDataHook(*sslsocket,
|
|
(SSLGetClientAuthData)SSM_SSLGetClientAuthData,
|
|
NULL) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* server cert authentication */
|
|
rv = SSL_AuthCertificateHook(*sslsocket,
|
|
(SSLAuthCertificate)SSM_SSLAuthCertificate,
|
|
(void*)handle) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_BadCertHook(*sslsocket, (SSLBadCertHandler)SSM_SSLBadCertHandler,
|
|
wincx) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_HandshakeCallback(*sslsocket,
|
|
(SSLHandshakeCallback)SSM_SSLHandshakeCallback,
|
|
wincx) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
SSM_DEBUG("setting PKCS11 pin arg.\n");
|
|
rv = SSL_SetPKCS11PinArg(*sslsocket, wincx) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* prepare against man-in-the-middle attacks */
|
|
rv = SSL_SetURL(*sslsocket, hostname) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
return rv;
|
|
loser:
|
|
if (*sslsocket == NULL) {
|
|
/* aborted before SSL_ImportFD() */
|
|
if (socket != NULL) {
|
|
PR_Close(socket);
|
|
}
|
|
}
|
|
else {
|
|
PR_Close(*sslsocket);
|
|
*sslsocket = NULL;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
SSMStatus SSM_GetSSLSocket(SSMSSLDataConnection* conn)
|
|
{
|
|
SECStatus secstatus = SECSuccess;
|
|
PRStatus prstatus = PR_SUCCESS;
|
|
PRFileDesc* socket = NULL;
|
|
PRSocketOptionData sockdata;
|
|
SSMControlConnection* ctrlconn;
|
|
PRNetAddr addr;
|
|
PRHostEnt hostentry;
|
|
char buffer[256];
|
|
|
|
SSM_DEBUG("SSM_GetSSLSocket entered.\n");
|
|
|
|
/* check argument first */
|
|
if (conn == NULL) {
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
/* Enter SSL lock. We will release once connection is set up.
|
|
* If GetSecurityStatus request comes in before we are done, it'll spin
|
|
* on the SSL lock. (This lock is not used anyplace else.)
|
|
*/
|
|
|
|
/*PR_EnterMonitor(conn->sslLock);*/
|
|
|
|
/* set up SSL secure socket */
|
|
SSM_DEBUG("setting up secure socket.\n");
|
|
socket = PR_NewTCPSocket();
|
|
if (socket == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* make the socket blocking - default on some platforms is non-blocking */
|
|
sockdata.option = PR_SockOpt_Nonblocking;
|
|
sockdata.value.non_blocking = PR_FALSE;
|
|
if (PR_SetSocketOption(socket, &sockdata) != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
|
|
if (!conn->isTLS) {
|
|
/* set up SSL secure socket */
|
|
SSM_DEBUG("setting up secure socket.\n");
|
|
|
|
ctrlconn = SSMCONTROLCONNECTION(conn);
|
|
secstatus = SSM_SetupSSLSocket(socket, &conn->socketSSL,
|
|
ctrlconn->m_certdb, conn->hostName,
|
|
(void*)conn);
|
|
if (secstatus != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
}
|
|
else {
|
|
/* do not need to create a secure socket here */
|
|
conn->socketSSL = socket;
|
|
}
|
|
|
|
/* prepare and setup network connection */
|
|
SSM_DEBUG("preparing and setting up network connection.\n");
|
|
if (PR_StringToNetAddr(conn->hostIP, &addr) != PR_SUCCESS) {
|
|
PRIntn hostIndex;
|
|
|
|
prstatus = PR_GetHostByName(conn->hostName, buffer, 256, &hostentry);
|
|
if (prstatus != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
|
|
SSM_DEBUG("SSL server port: %d / host: %s \n", conn->port,
|
|
conn->hostName);
|
|
do {
|
|
hostIndex = PR_EnumerateHostEnt(0, &hostentry,
|
|
(PRUint16) conn->port, &addr);
|
|
if (hostIndex < 0) {
|
|
goto loser;
|
|
}
|
|
} while (PR_Connect(conn->socketSSL, &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS
|
|
&& hostIndex > 0);
|
|
|
|
SSM_DEBUG("Connected to target.\n");
|
|
} else {
|
|
if (PR_InitializeNetAddr(PR_IpAddrNull, (PRUint16)conn->port, &addr) != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
if (PR_Connect(conn->socketSSL, &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
SSM_DEBUG("SSL server port: %d / host: %s\n", conn->port,
|
|
conn->hostName);
|
|
SSM_DEBUG("connected using hostIP: %s\n", conn->hostIP);
|
|
}
|
|
|
|
#ifdef _HACK_
|
|
/* ### mwelch Fix for NSPR20 connect problem under 95/NT. */
|
|
PR_Sleep(PR_INTERVAL_NO_WAIT);
|
|
#endif
|
|
|
|
/* established SSL connection, ready to send data */
|
|
if (!conn->isTLS && conn->forceHandshake) {
|
|
/* if the client wants a forced handshake (e.g. SSL/IMAP),
|
|
* do it here
|
|
*/
|
|
SSM_DEBUG("forcing handshake.\n");
|
|
secstatus = (SSL_ForceHandshake(conn->socketSSL) == 0)? SECSuccess:SECFailure;
|
|
if (secstatus != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (conn->m_sockStat) {
|
|
SSM_FreeResource(&conn->m_sockStat->super);
|
|
}
|
|
SSM_CreateResource(SSM_RESTYPE_SSL_SOCKET_STATUS, conn->socketSSL,
|
|
SSMRESOURCE(conn)->m_connection, &statRID,
|
|
(SSMResource**)&conn->m_sockStat);
|
|
*/
|
|
/* check everything is hunky-dorey */
|
|
/* SSMSSLSocketStatus_Invariant(conn->m_sockStat); */
|
|
/* end of the block */
|
|
|
|
/* release SSL lock. SSL connection is established. */
|
|
/*PR_Notify(conn->sslLock);
|
|
PR_ExitMonitor(conn->sslLock);*/
|
|
|
|
SSM_DEBUG("returning OK.\n");
|
|
|
|
return prstatus;
|
|
|
|
loser:
|
|
(void)SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
SSM_DEBUG("error %d (%s), exiting abnormally.\n", conn->m_error,
|
|
SSL_Strerror(conn->m_error));
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: PRStatus SSMSSLDataConnection_TLSStepUp()
|
|
* Purpose: "step up" the connection and set up an SSL socket for the
|
|
* underlying TCP socket
|
|
* Arguments and return values:
|
|
* - conn: TLS connection object to be manipulated
|
|
* - returns: PR_SUCCESS if successful; PR_FAILURE otherwise
|
|
*
|
|
* Notes: SSL handshakes are not done here; it is performed when the first
|
|
* read/write operation is done on the secure socket
|
|
* This function must be called while the underlying resource is
|
|
* locked
|
|
*/
|
|
static PRStatus SSMSSLDataConnection_TLSStepUp(SSMSSLDataConnection* conn)
|
|
{
|
|
PRFileDesc* socket = conn->socketSSL;
|
|
PRFileDesc* sslsocket = NULL;
|
|
|
|
PR_ASSERT(conn->isTLS == PR_TRUE);
|
|
|
|
if (conn->isSecure == PR_TRUE) {
|
|
/* socket is already secure */
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
SSM_DEBUG("Setting up secure socket for this TLS connection.\n");
|
|
if (SSM_SetupSSLSocket(socket, &sslsocket,
|
|
SSMCONTROLCONNECTION(conn)->m_certdb,
|
|
conn->hostName, (void*)conn) != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
SSM_DEBUG("Resetting handshake.\n");
|
|
if (SSL_ResetHandshake(sslsocket, PR_FALSE) != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* XXX it is important that the SSL handshake does not take place here
|
|
* because that would create a deadlock on this thread
|
|
*/
|
|
SSM_DEBUG("We now have a secure socket.\n");
|
|
/* secure socket is established */
|
|
conn->socketSSL = sslsocket;
|
|
conn->isSecure = PR_TRUE;
|
|
|
|
return PR_SUCCESS;
|
|
loser:
|
|
(void)SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
if (sslsocket == NULL) {
|
|
if (socket != NULL) {
|
|
PR_Close(socket);
|
|
}
|
|
}
|
|
else {
|
|
PR_Close(sslsocket);
|
|
}
|
|
|
|
return PR_FAILURE;
|
|
}
|
|
|
|
/* thread function */
|
|
/* SSL connection is serviced by the data service thread that works on
|
|
* the client data socket and the SSL socket.
|
|
*
|
|
* Our poll descriptor array contains the client data socket and the SSL
|
|
* target socket in this order.
|
|
*/
|
|
#define SSL_PDS 3
|
|
#define READSSL_CHUNKSIZE 16384
|
|
|
|
void SSM_SSLDataServiceThread(void* arg)
|
|
{
|
|
SSMStatus rv = PR_SUCCESS;
|
|
SSMSSLDataConnection* conn = NULL;
|
|
PRPollDesc pds[SSL_PDS];
|
|
PRIntn nSockets = 0;
|
|
PRInt32 nReady;
|
|
int i;
|
|
PRIntn read = 0;
|
|
PRIntn sent = 0;
|
|
char *outbound = NULL;
|
|
char *inbound = NULL;
|
|
PRIntn oBufSize;
|
|
|
|
SSM_RegisterNewThread("ssl data", (SSMResource *) arg);
|
|
conn = (SSMSSLDataConnection*)arg;
|
|
SSM_DEBUG("initializing.\n");
|
|
if (arg == NULL) {
|
|
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
|
|
goto loser;
|
|
}
|
|
|
|
SSMDATACONNECTION(conn)->m_dataServiceThread = PR_GetCurrentThread();
|
|
|
|
/* initialize xfer buffers */
|
|
outbound = (char *) PR_CALLOC(READSSL_CHUNKSIZE+1);
|
|
if (outbound == NULL)
|
|
goto loser;
|
|
|
|
inbound = (char *) PR_CALLOC(READSSL_CHUNKSIZE+1);
|
|
if (inbound == NULL)
|
|
goto loser;
|
|
|
|
/* initialize the poll descriptors */
|
|
for (i = 0; i < SSL_PDS; i++) {
|
|
pds[i].fd = NULL;
|
|
}
|
|
|
|
/* set up sockets */
|
|
/* set up the client data socket */
|
|
rv = SSMDataConnection_SetupClientSocket(SSMDATACONNECTION(conn));
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* save the client data socket in the array */
|
|
pds[0].fd = SSMDATACONNECTION(conn)->m_clientSocket;
|
|
pds[0].in_flags = PR_POLL_READ;
|
|
nSockets++;
|
|
|
|
/* set up the SSL socket */
|
|
rv = SSM_GetSSLSocket(conn);
|
|
if (rv != PR_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* save the SSL socket in the array */
|
|
pds[1].fd = conn->socketSSL;
|
|
pds[1].in_flags = PR_POLL_READ;
|
|
nSockets++;
|
|
|
|
if (conn->isTLS) {
|
|
/* SSL/SMTP connections usually require a large outbound buffer */
|
|
oBufSize = READSSL_CHUNKSIZE;
|
|
|
|
/* Add step-up FD to the list of FDs until we've been asked to
|
|
encrypt */
|
|
pds[2].fd = conn->stepUpFD;
|
|
pds[2].in_flags = PR_POLL_READ;
|
|
nSockets++;
|
|
}
|
|
else {
|
|
oBufSize = LINESIZE;
|
|
}
|
|
|
|
/* spinning mode. Exchange data between the client socket and the SSL
|
|
* socket.
|
|
*/
|
|
while ((nSockets > 0) &&
|
|
(SSM_Count(SSMDATACONNECTION(conn)->m_shutdownQ) == 0)) {
|
|
/* XXX Nova-specific: Nova does not handle active close from the
|
|
* server properly in case of keep-alive connections: what
|
|
* happens is that although we perform a shutdown on the client
|
|
* socket, Nova never sends us a close. If the SSL socket is
|
|
* removed, we might as well close the client socket by hand.
|
|
*/
|
|
/* if the outbound connection is shut down and there is no data
|
|
* pending in the client socket, perhaps the client is acting up
|
|
*/
|
|
if (pds[1].fd == NULL) {
|
|
/* don't block in polling */
|
|
nReady = PR_Poll(pds, SSL_PDS, PR_INTERVAL_NO_WAIT);
|
|
if (nReady <= 0) {
|
|
/* either error or the client socket is blocked. Bail. */
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
if (SSMDATACONNECTION(conn)->m_clientSocket != NULL) {
|
|
PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket,
|
|
PR_SHUTDOWN_SEND);
|
|
}
|
|
nSockets--;
|
|
if (nSockets > 1)
|
|
{
|
|
/*
|
|
We were waiting for notification to step up,
|
|
but that notification won't happen. So,
|
|
remove stepUpFD from the list as well.
|
|
*/
|
|
pds[2].fd = NULL;
|
|
nSockets = 1;
|
|
}
|
|
break; /* go to loser; */
|
|
}
|
|
}
|
|
|
|
SSM_DEBUG("Polling sockets for pending data.\n");
|
|
/* poll sockets for pending data */
|
|
#if 0
|
|
if (conn->isTLS == PR_TRUE) {
|
|
/* we require the lock for this operation and do a non-blocking
|
|
* poll in case of TLS because we want to yield to the step-up
|
|
* operation
|
|
*/
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
nReady = PR_Poll(pds, SSL_PDS, PR_INTERVAL_NO_WAIT);
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
if (nReady == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
#endif
|
|
/* Poll for however many sockets we're interested in. */
|
|
nReady = PR_Poll(pds, nSockets, PR_INTERVAL_NO_TIMEOUT);
|
|
#if 0
|
|
}
|
|
#endif
|
|
if (nReady < 0) {
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
goto loser;
|
|
}
|
|
|
|
/* Wind down from SSL_PDS because the pollable event
|
|
takes priority over the I/O sockets. */
|
|
for (i = (SSL_PDS-1); i >= 0; i--) {
|
|
if (pds[i].fd == NULL) {
|
|
continue;
|
|
}
|
|
|
|
/* check sockets for data */
|
|
if (pds[i].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT)) {
|
|
if (pds[i].fd == SSMDATACONNECTION(conn)->m_clientSocket) {
|
|
/* got data at the client socket */
|
|
SSM_DEBUG("Attempting to read %ld bytes from client "
|
|
"socket.\n", oBufSize);
|
|
read = PR_Recv(SSMDATACONNECTION(conn)->m_clientSocket,
|
|
outbound, oBufSize, 0,
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
|
|
if (read <= 0) {
|
|
if (read < 0) {
|
|
/* Got an error */
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
SSM_DEBUG("Error receiving data over client "
|
|
"socket, status == %ld\n", (long)rv);
|
|
}
|
|
else {
|
|
/* We got an EOF */
|
|
SSM_DEBUG("Got EOF at client socket.\n");
|
|
rv = PR_SUCCESS;
|
|
}
|
|
|
|
if (conn->socketSSL != NULL) {
|
|
SSM_DEBUG("Shutting down the target socket.\n");
|
|
PR_Shutdown(conn->socketSSL, PR_SHUTDOWN_SEND);
|
|
}
|
|
|
|
/* remove the socket from the array */
|
|
pds[i].fd = NULL;
|
|
nSockets--;
|
|
}
|
|
else {
|
|
SSM_DEBUG("data: <%s>\n" "Send the data to the "
|
|
"target.\n", outbound);
|
|
sent = PR_Send(conn->socketSSL, outbound, read, 0,
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
if (sent != read) {
|
|
SSM_DEBUG("Couldn't send all data to the target socket (sent: %d read: %d)\n",
|
|
sent, read);
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
|
|
if (SSMDATACONNECTION(conn)->m_clientSocket !=
|
|
NULL) {
|
|
SSM_DEBUG("Shutting down the client socket.\n");
|
|
PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket,
|
|
PR_SHUTDOWN_SEND);
|
|
}
|
|
|
|
/* remove the socket from the array */
|
|
pds[i].fd = NULL;
|
|
nSockets--;
|
|
}
|
|
}
|
|
}
|
|
else if (pds[i].fd == conn->socketSSL) {
|
|
/* got data at the SSL socket */
|
|
SSM_DEBUG("Reading data from target socket.\n");
|
|
read = PR_Recv(conn->socketSSL, inbound, READSSL_CHUNKSIZE,
|
|
0, PR_INTERVAL_NO_TIMEOUT);
|
|
|
|
if (read <= 0) {
|
|
if (read < 0) {
|
|
/* Got error */
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
SSM_DEBUG("Error receiving data from target "
|
|
"socket, status = %ld\n", rv);
|
|
}
|
|
else {
|
|
/* We got an EOF */
|
|
SSM_DEBUG("Got EOF at target socket.\n");
|
|
rv = PR_SUCCESS;
|
|
}
|
|
|
|
if (SSMDATACONNECTION(conn)->m_clientSocket != NULL) {
|
|
SSM_DEBUG("Shutting down the client socket.\n");
|
|
PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket,
|
|
PR_SHUTDOWN_SEND);
|
|
}
|
|
|
|
/* remove the socket from the array */
|
|
pds[i].fd = NULL;
|
|
nSockets--;
|
|
}
|
|
else {
|
|
/* Got data, write it to the client socket */
|
|
SSM_DEBUG("Writing to client socket.\n");
|
|
#if 0
|
|
SSM_DumpBuffer(inbound, read);
|
|
#endif
|
|
sent = PR_Send(SSMDATACONNECTION(conn)->m_clientSocket,
|
|
inbound, read, 0,
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
if (sent != read) {
|
|
rv = SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
|
|
if (conn->socketSSL != NULL) {
|
|
SSM_DEBUG("Shutting down the target socket.\n");
|
|
PR_Shutdown(conn->socketSSL, PR_SHUTDOWN_SEND);
|
|
}
|
|
|
|
/* remove the socket from the array */
|
|
pds[i].fd = NULL;
|
|
nSockets--;
|
|
}
|
|
SSM_DEBUG("Wrote %ld bytes.\n", sent);
|
|
}
|
|
}
|
|
else if (pds[i].fd == conn->stepUpFD)
|
|
{
|
|
/* We've been told to enable TLS on this connection.
|
|
Clear the event, step up, and reconfigure sockets. */
|
|
|
|
PRFileDesc *stepUpFD = conn->stepUpFD;
|
|
|
|
/* Make sure the control connection has settled */
|
|
SSM_LockResource(SSMRESOURCE(conn));
|
|
if ((pds[i].out_flags & PR_POLL_READ) != 0)
|
|
/* it's readable, so it should return immediately */
|
|
PR_WaitForPollableEvent(stepUpFD);
|
|
|
|
/* Step up to encryption. */
|
|
rv = SSMSSLDataConnection_TLSStepUp(conn);
|
|
conn->m_error = rv; /* tell the control connection */
|
|
|
|
/* Remove the step-up fd */
|
|
conn->stepUpFD = NULL;
|
|
pds[i].fd = NULL;
|
|
nSockets--;
|
|
PR_DestroyPollableEvent(stepUpFD);
|
|
|
|
/* Notify ourselves to wake up the control connection */
|
|
SSM_NotifyResource(SSMRESOURCE(conn));
|
|
SSM_UnlockResource(SSMRESOURCE(conn));
|
|
}
|
|
}
|
|
} /* end of for loop */
|
|
} /* end of while loop */
|
|
loser:
|
|
SSM_DEBUG("** Closing, return value %ld. **\n", rv);
|
|
if (conn != NULL) {
|
|
SSM_ShutdownResource(SSMRESOURCE(conn), rv);
|
|
SSM_FreeResource(SSMRESOURCE(conn));
|
|
}
|
|
if (outbound != NULL) {
|
|
PR_Free(outbound);
|
|
}
|
|
if (inbound != NULL) {
|
|
PR_Free(inbound);
|
|
}
|
|
SSM_DEBUG("SSL data service thread terminated.\n");
|
|
}
|
|
|
|
|
|
/* callback functions and auxilliary functions */
|
|
/*
|
|
* Function: SECStatus SSM_SSLAuthCertificate()
|
|
* Purpose: this callback function is used to authenticate certificates
|
|
* received from the remote end of an SSL connection.
|
|
*
|
|
* Arguments and return values
|
|
* - arg: certDB handle (cannot be NULL)
|
|
* - socket: SSL socket
|
|
* - checkSig: whether to check the signature (usually should be PR_TRUE)
|
|
* - isServer: whether we are an SSL server (expect PR_FALSE since Cartman is
|
|
* an SSL client)
|
|
* - returns: SECSuccess if successful; error code otherwise
|
|
*
|
|
* ### sjlee: this is essentially a wrapper over the default SSL callback
|
|
* function. It provides minimal argument checking to the default
|
|
* callback. When we're confident that the default will do the
|
|
* job, we might want to drop this...
|
|
*/
|
|
SECStatus SSM_SSLAuthCertificate(void* arg, PRFileDesc* socket,
|
|
PRBool checkSig, PRBool isServer)
|
|
{
|
|
SSM_DEBUG("checking server cert.\n");
|
|
|
|
/* check arguments */
|
|
if (arg == NULL || socket == NULL || isServer) {
|
|
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* call the default callback */
|
|
return (SECStatus) SSL_AuthCertificate(arg, socket, checkSig, isServer);
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: SECStatus SSM_SSLBadCertHandler()
|
|
* Purpose: this callback function is used to handle a situation in which
|
|
* server certificate verification has failed.
|
|
*
|
|
* Arguments and return values
|
|
* - arg: the SSL data connection
|
|
* - socket: SSL socket
|
|
* - returns: SECSuccess if the case is handled and it is authorized to
|
|
* continue connection; otherwise SECFailure
|
|
*/
|
|
SECStatus SSM_SSLBadCertHandler(void* arg, PRFileDesc* socket)
|
|
{
|
|
int error;
|
|
SSMSSLDataConnection* conn;
|
|
CERTCertificate* cert = NULL;
|
|
SECStatus rv = SECFailure;
|
|
|
|
if (socket == NULL || arg == NULL) {
|
|
return SECFailure;
|
|
}
|
|
conn = (SSMSSLDataConnection*)arg;
|
|
|
|
if (!(SSMCONTROLCONNECTION(conn)->m_doesUI)) {
|
|
/* UI-less application. We choose to reject the server cert. */
|
|
goto loser;
|
|
}
|
|
|
|
cert = SSL_PeerCertificate(conn->socketSSL);
|
|
if (cert == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
while (rv != SECSuccess) {
|
|
error = PR_GetError(); /* first of all, get error code */
|
|
|
|
SSM_DEBUG("Got a bad server cert: error %d (%s).\n", error,
|
|
SSL_Strerror(error));
|
|
|
|
/* Save error for socket status */
|
|
conn->m_sslServerError = error;
|
|
|
|
if (!SSM_SSLErrorNeedsDialog(error)) {
|
|
SSM_DEBUG("Exiting abnormally...\n");
|
|
break;
|
|
}
|
|
|
|
if (SSM_SSLMakeBadServerCertDialog(error, cert, conn) != SECSuccess) {
|
|
break;
|
|
}
|
|
|
|
/* check the server cert again for more errors */
|
|
rv = SSM_DoubleCheckServerCert(cert, conn);
|
|
}
|
|
|
|
loser:
|
|
if (rv != SECSuccess) {
|
|
(void)SSMSSLDataConnection_UpdateErrorCode(conn);
|
|
}
|
|
|
|
if (cert != NULL) {
|
|
CERT_DestroyCertificate(cert);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* structs and ASN1 templates for the limited scope-of-use extension
|
|
*
|
|
* CertificateScopeEntry ::= SEQUENCE {
|
|
* name GeneralName, -- pattern, as for NameConstraints
|
|
* portNumber INTEGER OPTIONAL }
|
|
*
|
|
* CertificateScopeOfUse ::= SEQUENCE OF CertificateScopeEntry
|
|
*/
|
|
/*
|
|
* CERTCertificateScopeEntry: struct for scope entry that can be consumed by
|
|
* the code
|
|
* certCertificateScopeOfUse: struct that represents the decoded extension data
|
|
*/
|
|
typedef struct {
|
|
SECItem derConstraint;
|
|
SECItem derPort;
|
|
CERTGeneralName* constraint; /* decoded constraint */
|
|
PRIntn port; /* decoded port number */
|
|
} CERTCertificateScopeEntry;
|
|
|
|
typedef struct {
|
|
CERTCertificateScopeEntry** entries;
|
|
} certCertificateScopeOfUse;
|
|
|
|
/* corresponding ASN1 templates */
|
|
static const SEC_ASN1Template cert_CertificateScopeEntryTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE,
|
|
0, NULL, sizeof(CERTCertificateScopeEntry) },
|
|
{ SEC_ASN1_ANY,
|
|
offsetof(CERTCertificateScopeEntry, derConstraint) },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER,
|
|
offsetof(CERTCertificateScopeEntry, derPort) },
|
|
{ 0 }
|
|
};
|
|
|
|
static const SEC_ASN1Template cert_CertificateScopeOfUseTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE_OF, 0, cert_CertificateScopeEntryTemplate }
|
|
};
|
|
|
|
/*
|
|
* decodes the extension data and create CERTCertificateScopeEntry that can
|
|
* be consumed by the code
|
|
*/
|
|
static
|
|
SECStatus cert_DecodeScopeOfUseEntries(PRArenaPool* arena, SECItem* extData,
|
|
CERTCertificateScopeEntry*** entries,
|
|
int* numEntries)
|
|
{
|
|
certCertificateScopeOfUse* scope = NULL;
|
|
SECStatus rv = SECSuccess;
|
|
int i;
|
|
|
|
*entries = NULL; /* in case of failure */
|
|
*numEntries = 0; /* ditto */
|
|
|
|
scope = (certCertificateScopeOfUse*)
|
|
PORT_ArenaZAlloc(arena, sizeof(certCertificateScopeOfUse));
|
|
if (scope == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SEC_ASN1DecodeItem(arena, (void*)scope,
|
|
cert_CertificateScopeOfUseTemplate, extData);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
*entries = scope->entries;
|
|
PR_ASSERT(*entries != NULL);
|
|
|
|
/* first, let's count 'em. */
|
|
for (i = 0; (*entries)[i] != NULL; i++) ;
|
|
*numEntries = i;
|
|
|
|
/* convert certCertificateScopeEntry sequence into what we can readily
|
|
* use
|
|
*/
|
|
for (i = 0; i < *numEntries; i++) {
|
|
(*entries)[i]->constraint =
|
|
cert_DecodeGeneralName(arena, &((*entries)[i]->derConstraint),
|
|
NULL);
|
|
if ((*entries)[i]->derPort.data != NULL) {
|
|
(*entries)[i]->port =
|
|
(int)DER_GetInteger(&((*entries)[i]->derPort));
|
|
}
|
|
else {
|
|
(*entries)[i]->port = 0;
|
|
}
|
|
}
|
|
|
|
goto done;
|
|
loser:
|
|
if (rv == SECSuccess) {
|
|
rv = SECFailure;
|
|
}
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
static SECStatus cert_DecodeCertIPAddress(SECItem* genname,
|
|
PRUint32* constraint, PRUint32* mask)
|
|
{
|
|
/* in case of failure */
|
|
*constraint = 0;
|
|
*mask = 0;
|
|
|
|
PR_ASSERT(genname->data != NULL);
|
|
if (genname->data == NULL) {
|
|
return SECFailure;
|
|
}
|
|
if (genname->len != 8) {
|
|
/* the length must be 4 byte IP address with 4 byte subnet mask */
|
|
return SECFailure;
|
|
}
|
|
|
|
/* get them in the right order */
|
|
*constraint = PR_ntohl((PRUint32)(*genname->data));
|
|
*mask = PR_ntohl((PRUint32)(*(genname->data + 4)));
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
static char* _str_to_lower(char* string)
|
|
{
|
|
#ifdef XP_WIN
|
|
return _strlwr(string);
|
|
#else
|
|
int i;
|
|
for (i = 0; string[i] != '\0'; i++) {
|
|
string[i] = tolower(string[i]);
|
|
}
|
|
return string;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Sees if the client certificate has a restriction in presenting the cert
|
|
* to the host: returns PR_TRUE if there is no restriction or if the hostname
|
|
* (and the port) satisfies the restriction, or PR_FALSE if the hostname (and
|
|
* the port) does not satisfy the restriction
|
|
*/
|
|
static PRBool CERT_MatchesScopeOfUse(CERTCertificate* cert, char* hostname,
|
|
char* hostIP, PRIntn port)
|
|
{
|
|
PRBool rv = PR_TRUE; /* whether the cert can be presented */
|
|
SECStatus srv;
|
|
SECItem extData;
|
|
PRArenaPool* arena = NULL;
|
|
CERTCertificateScopeEntry** entries = NULL;
|
|
/* arrays of decoded scope entries */
|
|
int numEntries = 0;
|
|
int i;
|
|
char* hostLower = NULL;
|
|
PRUint32 hostIPAddr = 0;
|
|
|
|
PR_ASSERT((cert != NULL) && (hostname != NULL) && (hostIP != NULL));
|
|
|
|
/* find cert extension */
|
|
srv = CERT_FindCertExtension(cert, SEC_OID_NS_CERT_EXT_SCOPE_OF_USE,
|
|
&extData);
|
|
if (srv != SECSuccess) {
|
|
/* most of the time, this means the extension was not found: also,
|
|
* since this is not a critical extension (as of now) we may simply
|
|
* return PR_TRUE
|
|
*/
|
|
goto done;
|
|
}
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (arena == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
/* decode the scope of use entries into pairs of GeneralNames and
|
|
* an optional port numbers
|
|
*/
|
|
srv = cert_DecodeScopeOfUseEntries(arena, &extData, &entries, &numEntries);
|
|
if (srv != SECSuccess) {
|
|
/* XXX What should we do when we failed to decode the extension? This
|
|
* may mean either the extension was malformed or some (unlikely)
|
|
* fatal error on our part: my argument is that if the extension
|
|
* was malformed the extension "disqualifies" as a valid
|
|
* constraint and we may present the cert
|
|
*/
|
|
goto done;
|
|
}
|
|
|
|
/* loop over these structures */
|
|
for (i = 0; i < numEntries; i++) {
|
|
/* determine whether the GeneralName is a DNS pattern, an IP address
|
|
* constraint, or else
|
|
*/
|
|
CERTGeneralName* genname = entries[i]->constraint;
|
|
|
|
/* if constraint is NULL, don't bother looking */
|
|
if (genname == NULL) {
|
|
/* this is not a failure: just continue */
|
|
continue;
|
|
}
|
|
|
|
switch (genname->type) {
|
|
case certDNSName: {
|
|
/* we have a DNS name constraint; we should use only the host name
|
|
* information
|
|
*/
|
|
char* pattern = NULL;
|
|
char* substring = NULL;
|
|
|
|
/* null-terminate the string */
|
|
genname->name.other.data[genname->name.other.len] = '\0';
|
|
pattern = _str_to_lower(genname->name.other.data);
|
|
|
|
if (hostLower == NULL) {
|
|
/* so that it's done only if necessary and only once */
|
|
hostLower = _str_to_lower(PL_strdup(hostname));
|
|
}
|
|
|
|
/* the hostname satisfies the constraint */
|
|
if (((substring = strstr(hostLower, pattern)) != NULL) &&
|
|
/* the hostname contains the pattern */
|
|
(strlen(substring) == strlen(pattern)) &&
|
|
/* the hostname ends with the pattern */
|
|
((substring == hostLower) || (*(substring-1) == '.'))) {
|
|
/* the hostname either is identical to the pattern or
|
|
* belongs to a subdomain
|
|
*/
|
|
rv = PR_TRUE;
|
|
}
|
|
else {
|
|
rv = PR_FALSE;
|
|
}
|
|
/* clean up strings if necessary */
|
|
break;
|
|
}
|
|
case certIPAddress: {
|
|
PRUint32 constraint;
|
|
PRUint32 mask;
|
|
PRNetAddr addr;
|
|
|
|
if (hostIPAddr == 0) {
|
|
/* so that it's done only if necessary and only once */
|
|
PR_StringToNetAddr(hostIP, &addr);
|
|
hostIPAddr = addr.inet.ip;
|
|
}
|
|
|
|
if (cert_DecodeCertIPAddress(&(genname->name.other), &constraint,
|
|
&mask) != SECSuccess) {
|
|
continue;
|
|
}
|
|
if ((hostIPAddr & mask) == (constraint & mask)) {
|
|
rv = PR_TRUE;
|
|
}
|
|
else {
|
|
rv = PR_FALSE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
/* ill-formed entry: abort */
|
|
continue; /* go to the next entry */
|
|
}
|
|
|
|
if (!rv) {
|
|
/* we do not need to check the port: go to the next entry */
|
|
continue;
|
|
}
|
|
|
|
/* finally, check the optional port number */
|
|
if ((entries[i]->port != 0) && (port != entries[i]->port)) {
|
|
/* port number does not match */
|
|
rv = PR_FALSE;
|
|
continue;
|
|
}
|
|
|
|
/* we have a match */
|
|
PR_ASSERT(rv);
|
|
break;
|
|
}
|
|
done:
|
|
/* clean up entries */
|
|
if (arena != NULL) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
if (hostLower != NULL) {
|
|
PR_Free(hostLower);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Function: SECStatus SSM_SSLGetClientAuthData()
|
|
* Purpose: this callback function is used to pull client certificate
|
|
* information upon server request
|
|
*
|
|
* Arguments and return values
|
|
* - arg: SSL data connection
|
|
* - socket: SSL socket we're dealing with
|
|
* - caNames: list of CA names
|
|
* - pRetCert: returns a pointer to a pointer to a valid certificate if
|
|
* successful; otherwise NULL
|
|
* - pRetKey: returns a pointer to a pointer to the corresponding key if
|
|
* successful; otherwise NULL
|
|
* - returns: SECSuccess if successful; error code otherwise
|
|
*/
|
|
SECStatus SSM_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
|
|
CERTDistNames* caNames,
|
|
CERTCertificate** pRetCert,
|
|
SECKEYPrivateKey** pRetKey)
|
|
{
|
|
void* wincx = NULL;
|
|
SECStatus rv = SECFailure;
|
|
SSMSSLDataConnection* conn;
|
|
SSMControlConnection* ctrlconn;
|
|
PRArenaPool* arena = NULL;
|
|
char** caNameStrings = NULL;
|
|
CERTCertificate* cert = NULL;
|
|
SECKEYPrivateKey* privKey = NULL;
|
|
CERTCertList* certList = NULL;
|
|
CERTCertListNode* node;
|
|
CERTCertNicknames* nicknames = NULL;
|
|
char* extracted = NULL;
|
|
PRIntn keyError = 0; /* used for private key retrieval error */
|
|
|
|
SSM_DEBUG("Client authentication callback function called.\n");
|
|
|
|
/* do some argument checking */
|
|
if (socket == NULL || caNames == NULL || pRetCert == NULL ||
|
|
pRetKey == NULL) {
|
|
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* get PKCS11 pin argument */
|
|
wincx = SSL_RevealPinArg(socket);
|
|
if (wincx == NULL) {
|
|
return SECFailure;
|
|
}
|
|
|
|
/* get the right connections */
|
|
conn = (SSMSSLDataConnection*)wincx;
|
|
ctrlconn = (SSMControlConnection*)(SSMCONNECTION(conn)->m_parent);
|
|
PR_ASSERT(ctrlconn);
|
|
|
|
/* create caNameStrings */
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (arena == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
caNameStrings = (char**)PORT_ArenaAlloc(arena,
|
|
sizeof(char*)*(caNames->nnames));
|
|
if (caNameStrings == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSM_ConvertCANamesToStrings(arena, caNameStrings, caNames);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* get the preference */
|
|
if (SSM_SetUserCertChoice(conn) != SSM_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* find valid user cert and key pair */
|
|
if (certChoice == AUTO) {
|
|
/* automatically find the right cert */
|
|
|
|
/* find all user certs that are valid and for SSL */
|
|
certList = CERT_FindUserCertsByUsage(ctrlconn->m_certdb,
|
|
certUsageSSLClient, PR_TRUE,
|
|
PR_TRUE, wincx);
|
|
if (certList == NULL) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* filter the list to those issued by CAs supported by the server */
|
|
rv = CERT_FilterCertListByCANames(certList, caNames->nnames,
|
|
caNameStrings, certUsageSSLClient);
|
|
if (rv != SECSuccess) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* make sure the list is not empty */
|
|
node = CERT_LIST_HEAD(certList);
|
|
if (CERT_LIST_END(node, certList)) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* loop through the list until we find a cert with a key */
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
/* if the certificate has restriction and we do not satisfy it
|
|
* we do not use it
|
|
*/
|
|
if (!CERT_MatchesScopeOfUse(node->cert, conn->hostName,
|
|
conn->hostIP, conn->port)) {
|
|
node = CERT_LIST_NEXT(node);
|
|
continue;
|
|
}
|
|
|
|
privKey = PK11_FindKeyByAnyCert(node->cert, wincx);
|
|
if (privKey != NULL) {
|
|
/* this is a good cert to present */
|
|
cert = CERT_DupCertificate(node->cert);
|
|
break;
|
|
}
|
|
keyError = PR_GetError();
|
|
if (keyError == SEC_ERROR_BAD_PASSWORD) {
|
|
/* problem with password: bail */
|
|
goto loser;
|
|
}
|
|
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
if (cert == NULL) {
|
|
goto noCert;
|
|
}
|
|
}
|
|
else {
|
|
/* user selects a cert to present */
|
|
int i;
|
|
|
|
/* find all user certs that are valid and for SSL */
|
|
/* note that we are allowing expired certs in this list */
|
|
certList = CERT_FindUserCertsByUsage(ctrlconn->m_certdb,
|
|
certUsageSSLClient, PR_TRUE,
|
|
PR_FALSE, wincx);
|
|
if (certList == NULL) {
|
|
goto noCert;
|
|
}
|
|
|
|
if (caNames->nnames != 0) {
|
|
/* filter the list to those issued by CAs supported by the
|
|
* server
|
|
*/
|
|
rv = CERT_FilterCertListByCANames(certList, caNames->nnames,
|
|
caNameStrings,
|
|
certUsageSSLClient);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
|
|
/* list is empty - no matching certs */
|
|
goto noCert;
|
|
}
|
|
|
|
/* filter it further for hostname restriction */
|
|
node = CERT_LIST_HEAD(certList);
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
if (!CERT_MatchesScopeOfUse(node->cert, conn->hostName,
|
|
conn->hostIP, conn->port)) {
|
|
CERTCertListNode* removed = node;
|
|
node = CERT_LIST_NEXT(removed);
|
|
CERT_RemoveCertListNode(removed);
|
|
}
|
|
else {
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
}
|
|
if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
|
|
goto noCert;
|
|
}
|
|
|
|
nicknames = CERT_NicknameStringsFromCertList(certList,
|
|
NICKNAME_EXPIRED_STRING,
|
|
NICKNAME_NOT_YET_VALID_STRING);
|
|
if (nicknames == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
SSM_DEBUG("%d valid user certs found.\n", nicknames->numnicknames);
|
|
conn->m_UIInfo.numFilteredCerts = nicknames->numnicknames;
|
|
|
|
if (ssm_client_auth_prepare_nicknames(conn, nicknames) !=
|
|
SSM_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
/* create a cert selection dialog and get back the chosen nickname */
|
|
if (SSM_SSLMakeClientAuthDialog(conn) != SSM_SUCCESS) {
|
|
goto loser;
|
|
}
|
|
|
|
PR_ASSERT(conn->m_UIInfo.chosen >= 0);
|
|
i = conn->m_UIInfo.chosen;
|
|
SSM_DEBUG("Cert %s was selected.\n", conn->m_UIInfo.certNicknames[i]);
|
|
|
|
/* first we need to extract the real nickname in case the cert
|
|
* is an expired or not a valid one yet
|
|
*/
|
|
extracted = CERT_ExtractNicknameString(conn->m_UIInfo.certNicknames[i],
|
|
NICKNAME_EXPIRED_STRING,
|
|
NICKNAME_NOT_YET_VALID_STRING);
|
|
if (extracted == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* find the cert under that nickname */
|
|
node = CERT_LIST_HEAD(certList);
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
if (PL_strcmp(node->cert->nickname, extracted) == 0) {
|
|
cert = CERT_DupCertificate(node->cert);
|
|
break;
|
|
}
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
if (cert == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* go get the private key */
|
|
privKey = PK11_FindKeyByAnyCert(cert, wincx);
|
|
if (privKey == NULL) {
|
|
keyError = PR_GetError();
|
|
if (keyError == SEC_ERROR_BAD_PASSWORD) {
|
|
/* problem with password: bail */
|
|
goto loser;
|
|
}
|
|
else {
|
|
goto noCert;
|
|
}
|
|
}
|
|
}
|
|
/* rv == SECSuccess */
|
|
SSM_DEBUG("Yahoo! Client auth complete.\n");
|
|
|
|
goto done;
|
|
noCert:
|
|
loser:
|
|
if (rv == SECSuccess) {
|
|
rv = SECFailure;
|
|
}
|
|
if (cert != NULL) {
|
|
CERT_DestroyCertificate(cert);
|
|
cert = NULL;
|
|
}
|
|
done:
|
|
if (extracted != NULL) {
|
|
PR_Free(extracted);
|
|
}
|
|
if (nicknames != NULL) {
|
|
CERT_FreeNicknames(nicknames);
|
|
}
|
|
if (certList != NULL) {
|
|
CERT_DestroyCertList(certList);
|
|
}
|
|
if (arena != NULL) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
*pRetCert = cert;
|
|
*pRetKey = privKey;
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Function: SSMStatus SSM_SetUserCertChoice()
|
|
|
|
* Purpose: sets certChoice by reading the preference
|
|
*
|
|
* Arguments and return values
|
|
* - conn: SSMSSLDataConnection
|
|
* - returns: SSM_SUCCESS if successful; SSM_FAILURE otherwise
|
|
*
|
|
* Note: If done properly, this function will read the identifier strings
|
|
* for ASK and AUTO modes, read the selected strings from the
|
|
* preference, compare the strings, and determine in which mode it is
|
|
* in.
|
|
* We currently use ASK mode for UI apps and AUTO mode for UI-less
|
|
* apps without really asking for preferences.
|
|
*/
|
|
SSMStatus SSM_SetUserCertChoice(SSMSSLDataConnection* conn)
|
|
{
|
|
SSMStatus rv;
|
|
SSMControlConnection* parent;
|
|
|
|
parent = SSMCONTROLCONNECTION(conn);
|
|
|
|
if (parent->m_doesUI) {
|
|
char* mode = NULL;
|
|
|
|
rv = PREF_GetStringPref(parent->m_prefs,
|
|
"security.default_personal_cert", &mode);
|
|
if (rv != PR_SUCCESS) {
|
|
return SSM_FAILURE;
|
|
}
|
|
if (PL_strcmp(mode, "Select Automatically") == 0) {
|
|
certChoice = AUTO;
|
|
}
|
|
else if (PL_strcmp(mode, "Ask Every Time") == 0) {
|
|
certChoice = ASK;
|
|
}
|
|
else {
|
|
return SSM_FAILURE;
|
|
}
|
|
}
|
|
else {
|
|
SSM_DEBUG("UI-less app: use auto cert selection.\n");
|
|
certChoice = AUTO;
|
|
}
|
|
|
|
return SSM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: SECStatus SSM_ConvertCANamesToStrings()
|
|
* Purpose: creates CA names strings from (CERTDistNames* caNames)
|
|
*
|
|
* Arguments and return values
|
|
* - arena: arena to allocate strings on
|
|
* - caNameStrings: filled with CA names strings on return
|
|
* - caNames: CERTDistNames to extract strings from
|
|
* - return: SECSuccess if successful; error code otherwise
|
|
*
|
|
* Note: copied in its entirety from Nova code
|
|
*/
|
|
SECStatus SSM_ConvertCANamesToStrings(PRArenaPool* arena, char** caNameStrings,
|
|
CERTDistNames* caNames)
|
|
{
|
|
SECItem* dername;
|
|
SECStatus rv;
|
|
int headerlen;
|
|
uint32 contentlen;
|
|
SECItem newitem;
|
|
int n;
|
|
char* namestring;
|
|
|
|
for (n = 0; n < caNames->nnames; n++) {
|
|
newitem.data = NULL;
|
|
dername = &caNames->names[n];
|
|
|
|
rv = DER_Lengths(dername, &headerlen, &contentlen);
|
|
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
if (headerlen + contentlen != dername->len) {
|
|
/* This must be from an enterprise 2.x server, which sent
|
|
* incorrectly formatted der without the outer wrapper of
|
|
* type and length. Fix it up by adding the top level
|
|
* header.
|
|
*/
|
|
if (dername->len <= 127) {
|
|
newitem.data = (unsigned char *) PR_Malloc(dername->len + 2);
|
|
if (newitem.data == NULL) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char)0x30;
|
|
newitem.data[1] = (unsigned char)dername->len;
|
|
(void)memcpy(&newitem.data[2], dername->data, dername->len);
|
|
}
|
|
else if (dername->len <= 255) {
|
|
newitem.data = (unsigned char *) PR_Malloc(dername->len + 3);
|
|
if (newitem.data == NULL) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char)0x30;
|
|
newitem.data[1] = (unsigned char)0x81;
|
|
newitem.data[2] = (unsigned char)dername->len;
|
|
(void)memcpy(&newitem.data[3], dername->data, dername->len);
|
|
}
|
|
else {
|
|
/* greater than 256, better be less than 64k */
|
|
newitem.data = (unsigned char *) PR_Malloc(dername->len + 4);
|
|
if (newitem.data == NULL) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char)0x30;
|
|
newitem.data[1] = (unsigned char)0x82;
|
|
newitem.data[2] = (unsigned char)((dername->len >> 8) & 0xff);
|
|
newitem.data[3] = (unsigned char)(dername->len & 0xff);
|
|
memcpy(&newitem.data[4], dername->data, dername->len);
|
|
}
|
|
dername = &newitem;
|
|
}
|
|
|
|
namestring = CERT_DerNameToAscii(dername);
|
|
if (namestring == NULL) {
|
|
/* XXX - keep going until we fail to convert the name */
|
|
caNameStrings[n] = "";
|
|
}
|
|
else {
|
|
caNameStrings[n] = PORT_ArenaStrdup(arena, namestring);
|
|
PR_Free(namestring);
|
|
if (caNameStrings[n] == NULL) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
if (newitem.data != NULL) {
|
|
PR_Free(newitem.data);
|
|
}
|
|
}
|
|
|
|
return SECSuccess;
|
|
loser:
|
|
if (newitem.data != NULL) {
|
|
PR_Free(newitem.data);
|
|
}
|
|
return SECFailure;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: SSMStatus ssm_client_auth_prepare_nicknames()
|
|
* Purpose: (private) picks out the filtered nicknames and populates the
|
|
* nicknames field of the SSL connection object with them
|
|
* Arguments and return values:
|
|
* - conn: SSL connection object to be manipulated
|
|
* - nicknames: nickname list
|
|
*
|
|
* Note: the valid nickname list is not empty if this function was called
|
|
* Also the memory for the nickname list is allocated here; the caller
|
|
* is responsible for freeing the nickname list before the client
|
|
* auth callback function returns
|
|
*/
|
|
SSMStatus ssm_client_auth_prepare_nicknames(SSMSSLDataConnection* conn,
|
|
CERTCertNicknames* nicknames)
|
|
{
|
|
int i;
|
|
int number;
|
|
|
|
PR_ASSERT(conn != NULL && conn->m_UIInfo.numFilteredCerts > 0 &&
|
|
nicknames != NULL);
|
|
number = conn->m_UIInfo.numFilteredCerts;
|
|
|
|
/* allocate memory for nickname list */
|
|
conn->m_UIInfo.certNicknames = (char**)PR_Calloc(number, sizeof(char*));
|
|
if (conn->m_UIInfo.certNicknames == NULL) {
|
|
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
|
|
goto loser;
|
|
}
|
|
|
|
/* fill in cert nicknames */
|
|
for (i = 0; i < number; i++) {
|
|
conn->m_UIInfo.certNicknames[i] = PL_strdup(nicknames->nicknames[i]);
|
|
if (conn->m_UIInfo.certNicknames[i] == NULL) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
return SSM_SUCCESS;
|
|
loser:
|
|
return SSM_FAILURE;
|
|
}
|
|
|
|
|
|
void SSM_SSLHandshakeCallback(PRFileDesc* socket, void* clientData)
|
|
{
|
|
SSMSSLDataConnection* conn;
|
|
|
|
PR_ASSERT(socket != NULL);
|
|
PR_ASSERT(clientData != NULL);
|
|
|
|
SSM_DEBUG("Handshake callback called.\n");
|
|
|
|
conn = (SSMSSLDataConnection*)clientData;
|
|
|
|
/* update the security status info */
|
|
(void)SSMSSLDataConnection_UpdateSecurityStatus(conn);
|
|
/* we want to proceed even if the update has failed... */
|
|
|
|
/* check everything is hunky-dorey */
|
|
SSMSSLSocketStatus_Invariant(conn->m_sockStat);
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: PRBool SSM_SSLErrorNeedsDialog()
|
|
* Purpose: sorts out errors that require dialogs
|
|
*
|
|
* Arguments and return values
|
|
* - error: error code
|
|
* - returns: PR_TRUE if dialog needed; PR_FALSE otherwise
|
|
*/
|
|
PRBool SSM_SSLErrorNeedsDialog(int error)
|
|
{
|
|
return ((error == SEC_ERROR_UNKNOWN_ISSUER) ||
|
|
(error == SEC_ERROR_UNTRUSTED_ISSUER) ||
|
|
(error == SSL_ERROR_BAD_CERT_DOMAIN) ||
|
|
(error == SSL_ERROR_POST_WARNING) ||
|
|
(error == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) ||
|
|
(error == SEC_ERROR_CA_CERT_INVALID) ||
|
|
(error == SEC_ERROR_EXPIRED_CERTIFICATE));
|
|
}
|
|
|
|
|
|
|
|
SECStatus SSM_SSLMakeBadServerCertDialog(int error,
|
|
CERTCertificate* cert,
|
|
SSMSSLDataConnection* conn)
|
|
{
|
|
SECStatus rv;
|
|
|
|
if ((error == SEC_ERROR_UNKNOWN_ISSUER) ||
|
|
(error == SEC_ERROR_CA_CERT_INVALID) ||
|
|
(error == SEC_ERROR_UNTRUSTED_ISSUER)) {
|
|
rv = SSM_SSLMakeUnknownIssuerDialog(cert, conn);
|
|
}
|
|
else if (error == SSL_ERROR_BAD_CERT_DOMAIN) {
|
|
rv = SSM_SSLMakeCertBadDomainDialog(cert, conn);
|
|
}
|
|
else if (error == SSL_ERROR_POST_WARNING) {
|
|
/*rv = SEC_MakeCertPostWarnDialog(proto_win, cert);*/
|
|
rv = SECSuccess;
|
|
}
|
|
else if (error == SEC_ERROR_EXPIRED_CERTIFICATE) {
|
|
rv = SSM_SSLMakeCertExpiredDialog(cert, conn);
|
|
}
|
|
else if (error == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) {
|
|
/*rv = SEC_MakeCAExpiredDialog(proto_win, cert);*/
|
|
rv = SECSuccess;
|
|
}
|
|
else {
|
|
rv = SECFailure;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
SECStatus SSM_DoubleCheckServerCert(CERTCertificate* cert,
|
|
SSMSSLDataConnection* conn)
|
|
{
|
|
PRBool checkSig = PR_FALSE;
|
|
/* ### sjlee: it is bit discomforting to set checkSig to false, but
|
|
* the user already decided to be generous about the cert, so it
|
|
* could be OK???
|
|
*/
|
|
SSM_DEBUG("One server cert problem was handled. See if there's more.\n");
|
|
|
|
return SSM_SSLVerifyServerCert(SSMCONTROLCONNECTION(conn)->m_certdb, cert,
|
|
checkSig, conn);
|
|
}
|
|
|
|
|
|
/* This function does the same thing as SSL_AuthCertificate(),
|
|
** but takes different arguments.
|
|
*/
|
|
SECStatus SSM_SSLVerifyServerCert(CERTCertDBHandle* handle,
|
|
CERTCertificate* cert, PRBool checkSig,
|
|
SSMSSLDataConnection* conn)
|
|
{
|
|
SECStatus rv;
|
|
char* hostname = NULL;
|
|
|
|
rv = CERT_VerifyCertNow(handle, cert, checkSig, certUsageSSLServer,
|
|
(void*)conn);
|
|
if (rv != SECSuccess) {
|
|
return rv;
|
|
}
|
|
|
|
/* cert is OK. we will check the name field in the cert against the
|
|
* desired hostname.
|
|
*/
|
|
hostname = SSL_RevealURL(conn->socketSSL);
|
|
if (hostname && hostname[0]) {
|
|
rv = CERT_VerifyCertName(cert, hostname);
|
|
}
|
|
else {
|
|
rv = SECFailure;
|
|
}
|
|
|
|
if (rv != SECSuccess) {
|
|
PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
|
|
}
|
|
if (hostname)
|
|
PR_Free(hostname);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* The following routines are callbacks and assorted functions that are used
|
|
* with SSL/LDAP.
|
|
*/
|
|
/* SSL client-auth callback used for SSL/LDAP */
|
|
SECStatus SSM_LDAPSSLGetClientAuthData(void* arg, PRFileDesc* socket,
|
|
CERTDistNames* caNames,
|
|
CERTCertificate** pRetCert,
|
|
SECKEYPrivateKey** pRetKey)
|
|
{
|
|
SECStatus rv = SECFailure;
|
|
void* wincx = NULL;
|
|
PRArenaPool* arena = NULL;
|
|
char** caNameStrings = NULL;
|
|
CERTCertList* certList = NULL;
|
|
CERTCertListNode* node;
|
|
CERTCertificate* cert = NULL;
|
|
SECKEYPrivateKey* privKey = NULL;
|
|
CERTCertDBHandle* certdb;
|
|
|
|
SSM_DEBUG("Doing client auth for LDAP/SSL.\n");
|
|
|
|
/* check arguments */
|
|
if ((arg == NULL) || (socket == NULL) || (caNames == NULL) ||
|
|
(pRetCert == NULL) || (pRetKey == NULL)) {
|
|
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* get PKCS11 pin argument */
|
|
wincx = SSL_RevealPinArg(socket);
|
|
if (wincx == NULL) {
|
|
return SECFailure;
|
|
}
|
|
|
|
/* create caNameStrings */
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (arena == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
caNameStrings = (char**)PORT_ArenaAlloc(arena,
|
|
sizeof(char*)*(caNames->nnames));
|
|
if (caNameStrings == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* XXX to do: publish the following function in sslconn.h */
|
|
rv = SSM_ConvertCANamesToStrings(arena, caNameStrings, caNames);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* we will just do automatic cert selection for SSL/LDAP */
|
|
/* get the cert DB */
|
|
certdb = (CERTCertDBHandle*)arg;
|
|
|
|
/* find all user certs that are valid and for SSL */
|
|
certList = CERT_FindUserCertsByUsage(certdb, certUsageSSLClient, PR_TRUE,
|
|
PR_TRUE, wincx);
|
|
if (certList == NULL) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* filter the list to those issued by CAs supported by the server */
|
|
rv = CERT_FilterCertListByCANames(certList, caNames->nnames, caNameStrings,
|
|
certUsageSSLClient);
|
|
if (rv != SECSuccess) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* make sure the list is not empty */
|
|
node = CERT_LIST_HEAD(certList);
|
|
if (CERT_LIST_END(node, certList)) {
|
|
goto noCert;
|
|
}
|
|
|
|
/* loop through the list until we find a cert with a key */
|
|
while (!CERT_LIST_END(node, certList)) {
|
|
privKey = PK11_FindKeyByAnyCert(node->cert, wincx);
|
|
if (privKey != NULL) {
|
|
/* this is a good cert to present */
|
|
cert = CERT_DupCertificate(node->cert);
|
|
break;
|
|
}
|
|
node = CERT_LIST_NEXT(node);
|
|
}
|
|
if (cert == NULL) {
|
|
goto noCert;
|
|
}
|
|
|
|
goto done;
|
|
noCert:
|
|
PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
|
|
rv = SECFailure;
|
|
loser:
|
|
if (rv == SECSuccess) {
|
|
rv = SECFailure;
|
|
}
|
|
if (cert != NULL) {
|
|
CERT_DestroyCertificate(cert);
|
|
cert = NULL;
|
|
}
|
|
done:
|
|
if (certList != NULL) {
|
|
CERT_DestroyCertList(certList);
|
|
}
|
|
if (arena != NULL) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
|
|
*pRetCert = cert;
|
|
*pRetKey = privKey;
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* This function must get called when the socket was created but was not
|
|
* SSL-ified or connected
|
|
*/
|
|
SECStatus SSM_LDAPSetupSSL(PRFileDesc* socket, PRFileDesc** sslSocket,
|
|
CERTCertDBHandle* certdb, const char* hostname,
|
|
SSMResource* caller)
|
|
{
|
|
SECStatus rv = SECFailure;
|
|
|
|
/* check arguments */
|
|
if ((socket == NULL) || (sslSocket == NULL) || (certdb == NULL) ||
|
|
(hostname == NULL) || (caller == NULL)) {
|
|
goto loser;
|
|
}
|
|
*sslSocket = NULL;
|
|
|
|
/* import the socket into SSL layer */
|
|
*sslSocket = SSL_ImportFD(NULL, socket);
|
|
if (*sslSocket == NULL) {
|
|
goto loser;
|
|
}
|
|
|
|
/* set some SSL settings for the socket */
|
|
rv = SSL_Enable(*sslSocket, SSL_SECURITY, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_Enable(*sslSocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
/* set callbacks */
|
|
rv = SSL_GetClientAuthDataHook(*sslSocket,
|
|
(SSLGetClientAuthData)SSM_LDAPSSLGetClientAuthData,
|
|
(void*)certdb) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_AuthCertificateHook(*sslSocket,
|
|
(SSLAuthCertificate)SSM_SSLAuthCertificate,
|
|
(void*)certdb) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_SetPKCS11PinArg(*sslSocket, (void*)caller) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = SSL_SetURL(*sslSocket, hostname) == 0 ? SECSuccess : SECFailure;
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
return rv;
|
|
loser:
|
|
if (*sslSocket == NULL) {
|
|
if (socket != NULL) {
|
|
PR_Close(socket);
|
|
}
|
|
}
|
|
else {
|
|
PR_Close(*sslSocket);
|
|
*sslSocket = NULL;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|