// Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// SSL3.0 and TLS1.0 Alert protocol source file.
// Describes the implementation of the Alert protocol (send and receive)
// events classes
//
//
/**
@file AlertProtocolEvents.cpp
*/
#include "AlertProtocolEvents.h"
#include "tlsrecorditem.h"
#include "recordprotocolevents.h"
#include "tlshandshake.h"
#include "applicationdata.h"
#include <sslerr.h>
// @todo There must be a means of notifying the TLS Provider when a fatal
// alert happens (as this invalidates the session; i.e., no new connections
// can subsequently be made with that session id). Can ClearSessionCache be
// used but identifying only one session (as opposed to all a client's
// sessions)?
//TLS specific error <-> alert code mapping
struct TTLSErrorAlertCodeMap
{
TInt iTLSErrorCode;
TInt iAlertCode;
};
const TUint KWarningAlertsCount = 2;
enum ETLSAlertCode {
EAlertclose_notify = 0,
EAlertunexpected_message = 10,
EAlertbad_record_mac = 20,
EAlertdecryption_failed = 21,
EAlertrecord_overflow = 22,
EAlertdecompression_failure = 30,
EAlerthandshake_failure = 40,
EAlertbad_certificate = 42,
EAlertunsupported_certificate = 43,
EAlertcertificate_revoked = 44,
EAlertcertificate_expired = 45,
EAlertcertificate_unknown = 46,
EAlertillegal_parameter = 47,
EAlertunknown_ca = 48,
EAlertaccess_denied = 49,
EAlertdecode_error = 50,
EAlertdecrypt_error = 51,
EAlertexport_restriction = 60,
EAlertprotocol_version = 70,
EAlertinsufficient_security = 71,
EAlertinternal_error = 80,
EAlertuser_canceled = 90,
EAlertno_renegotiation = 100
};
const TTLSErrorAlertCodeMap glbTLSErrorAlertCodeMap[] =
{//the first two alerts are warnings the rest is fatal
{KErrSSLAlertCloseNotify, 0}, //close_notify(0),
{KErrSSLAlertNoRenegotiation,100}, //no_renegotiation(100),
{KErrSSLAlertUnexpectedMessage,10}, //unexpected_message(10),
{KErrSSLAlertBadRecordMac,20}, //bad_record_mac(20),
{KErrSSLAlertDecryptionFailed,21}, //decryption_failed(21),
{KErrSSLAlertRecordOverflow,22}, //record_overflow(22),
{KErrSSLAlertDecompressionFailure,30}, //decompression_failure(30),
{KErrSSLAlertHandshakeFailure,40}, //handshake_failure(40),
{KErrSSLAlertBadCertificate,42}, //bad_certificate(42),
{KErrSSLAlertUnsupportedCertificate,43},//unsupported_certificate(43),
{KErrSSLAlertCertificateRevoked,44}, //certificate_revoked(44),
{KErrSSLAlertCertificateExpired,45}, //certificate_expired(45),
{KErrSSLAlertCertificateUnknown,46}, //certificate_unknown(46),
{KErrSSLAlertIllegalParameter,47}, //illegal_parameter(47),
{KErrSSLAlertUnknownCA,48}, //unknown_ca(48),
{KErrSSLAlertAccessDenied,49}, //access_denied(49),
{KErrSSLAlertDecodeError,50}, //decode_error(50),
{KErrSSLAlertDecryptError,51}, //decrypt_error(51),
{KErrSSLAlertExportRestriction,60}, //export_restriction(60),
{KErrSSLAlertProtocolVersion,70}, //protocol_version(70),
{KErrSSLAlertInsufficientSecurity,71}, //insufficient_security(71),
{KErrSSLAlertInternalError,80}, //internal_error(80),
{KErrSSLAlertUserCanceled,90} //user_canceled(90),
};
//the error which apparently don't map to any alert
// KErrSSLAlertAccessDenied:
// KErrSSLAlertDecodeError:
// KErrSSLAlertDecryptError:
CAsynchEvent* CSendAlert::ProcessL( TRequestStatus& aStatus )
{
// @todo The processing code can be put into a separate function (code reuse/reduce bloat).
// @todo Ensure that alerts are set up correctly (i.e., the code that should cause an
// alert to be sent is set up correctly.
TInt error = iStateMachine->LastError();
LOG(Log::Printf(_L("CSendAlert::ProcessL(). Error value = %d"), error ));
iAlertMsg.SetLength( 0 );
switch ( error )
{
case KErrEof:
{ // CTlsConnection::Recv() completion status when there is no more data to
// receive. This is not a SSL/TLS error.
TRequestStatus* p=&aStatus;
User::RequestComplete( p, KErrNone );
return NULL; //stop the state machine
}
case KErrSSLAlertCloseNotify:
{
//Upon sending the close_notify from server report KErrEof to the application
//to be intact with existing behaviour.
iStateMachine->SetLastError( KErrEof );
iAlertMsg.Append( EAlertWarning );
iAlertMsg.Append( EAlertclose_notify );
iRecordComposer.SetNext( this );
break;
}
case KErrDisconnected:
{
/* Fix for the TLS Client hang issue DEF130128.
* Server might have disconnected the TLS connection.
* Terminate the state machine */
TRequestStatus* p=&aStatus;
User::RequestComplete(p, KErrDisconnected);
return NULL; //stop the state machine
}
case KErrArgument:
{
iStateMachine->SetLastError( KErrSSLAlertIllegalParameter );
iAlertMsg.Append( EAlertFatal );
iAlertMsg.Append( EAlertillegal_parameter );
iRecordComposer.SetNext( NULL );
break;
}
case KErrCancel:
{// A user_canceled alert should be followed by a close_notify alert. So this
// event will be the next one to be processed.
iRecordComposer.SetNext( NULL );
iAlertMsg.Append( EAlertWarning );
iAlertMsg.Append( EAlertclose_notify );
iRecordComposer.SetNext( NULL );
break;
}
default:
{ //find the matching alert code to send
TUint nIndex = 0;
while ( nIndex < sizeof( glbTLSErrorAlertCodeMap )/sizeof( glbTLSErrorAlertCodeMap[0] ) &&
glbTLSErrorAlertCodeMap[nIndex].iTLSErrorCode != error )
{
nIndex++;
}
// Set the message content and its record type.
iAlertMsg.Append( nIndex >= KWarningAlertsCount ? EAlertFatal : EAlertWarning );
iAlertMsg.Append( nIndex < sizeof( glbTLSErrorAlertCodeMap )/sizeof( glbTLSErrorAlertCodeMap[0] ) ?
glbTLSErrorAlertCodeMap[nIndex].iAlertCode : EAlertunexpected_message );
if ( iAlertMsg[0] == EAlertFatal )
{
iRecordComposer.SetNext( NULL );
}
break;
}
} //switch
iRecordComposer.SetUserData( &iAlertMsg );
iRecordComposer.SetRecordType( ETlsAlertContentType );
iRecordComposer.ResetCurrentPos();
return iRecordComposer.ProcessL( aStatus );
}
CAsynchEvent* CRecvAlert::ProcessL( TRequestStatus& aStatus )
{
// Get the Alert message contents from the Record Parser's iPtrHBuf (this descriptor is
// always set to point to the decrypted (when necessary) data.
TPtr8 alertMsg( NULL, 0 );
alertMsg.Set( iRecordParser.PtrHBuf() );
User::LeaveIfError( alertMsg.Length() != KAlertMsgLength ? KErrSSLAlertUnexpectedMessage : KErrNone );
TUint8 alertLevel = alertMsg[0];
TUint8 alertDesc = alertMsg[1];
LOG(Log::Printf(_L("CRecvAlert::ProcessL(). Alert level = %d"), alertLevel ));
LOG(Log::Printf(_L("CRecvAlert::ProcessL(). Alert description = %d"), alertDesc ));
TRequestStatus* p=&aStatus;
User::RequestComplete( p, KErrNone );
if ( alertLevel == EAlertWarning )
{// In all circumstances, when a warning alert is received, we carry on as normal.
// There is no need to set the next event as this will be unchanged from normal
// operation. For a Close_notify alert, we must send one in response.
// So the next event will be CSendAlert sending a close-notify alert.
if ( alertDesc == EAlertclose_notify )
{
iStateMachine->SetLastError( KErrSSLAlertCloseNotify );
return &iSendAlert;
}
else if ( alertDesc != EAlertno_renegotiation )
{
return &iRecordParser;
}
else if ( iRecordParser.ReadActive() )
{//we must be in data mode already to receive this alert
//if alertDesc == EAlertno_renegotiation for the moment it means that re-negotiation completed successfully
return NULL;
}
alertDesc = EAlertillegal_parameter;
}
//Complete the request immediately and close the connection.
//and set the statemachine error code to return to the client
TUint nIndex = 0;
while ( nIndex < sizeof( glbTLSErrorAlertCodeMap )/sizeof( glbTLSErrorAlertCodeMap[0] ) &&
glbTLSErrorAlertCodeMap[nIndex].iAlertCode != alertDesc )
{
nIndex++;
}
iStateMachine->SetLastError( nIndex < sizeof( glbTLSErrorAlertCodeMap )/sizeof( glbTLSErrorAlertCodeMap[0] ) ?
glbTLSErrorAlertCodeMap[nIndex].iTLSErrorCode : KErrSSLAlertIllegalParameter );
return NULL;
}
TBool CRecvAlert::AcceptRecord( TInt aRecordType ) const
/**
* This method accepts an Alert event. Alerts are always accepted.
*/
{
LOG(Log::Printf(_L("CRecvAlert::AcceptRecord()\n"));)
return aRecordType == ETlsAlertContentType;
}