/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


/*  *********************************************************************
    File: sslAlertMessage.cpp - SSL3 Alert protocol
    ****************************************************************** */

#include "ssl.h"
#include "sslAlertMessage.h"
#include "sslMemory.h"
#include "sslContext.h"
#include "sslSession.h"
#include "sslDebug.h"
#include "sslUtils.h"

#include <assert.h>

#ifdef	NDEBUG
#define SSLLogAlertMsg(msg,sent)
#else
static void SSLLogAlertMsg(AlertDescription msg, bool sent);
#endif

static OSStatus SSLEncodeAlert(
	SSLRecord &rec, 
	AlertLevel level, 
	AlertDescription desc, 
	SSLContext *ctx);

/*
 * If a peer sends us any kind of a bad cert alert, we may need to adjust
 * ctx->clientCertState accordingly.
 */
static void 
SSLDetectCertRejected(
	SSLContext 			*ctx,
	AlertDescription	desc)
{
	if(ctx->protocolSide == SSL_ServerSide) {
		return;
	}
	if(ctx->clientCertState != kSSLClientCertSent) {
		return;
	}
	switch(desc) {
		case SSL_AlertBadCert:
		case SSL_AlertUnsupportedCert:
		case SSL_AlertCertRevoked:
		case SSL_AlertCertExpired:
		case SSL_AlertCertUnknown:
		case SSL_AlertUnknownCA:
			ctx->clientCertState = kSSLClientCertRejected;
			break;
		default:
			break;
	}
}

OSStatus
SSLProcessAlert(SSLRecord rec, SSLContext *ctx)
{   OSStatus            err = noErr;
    AlertLevel          level;
    AlertDescription    desc;
    uint8               *charPtr;
    uint32              remaining;
    
    if (rec.contents.length % 2 != 0)
    {   
		err = SSLFatalSessionAlert(SSL_AlertIllegalParam, ctx);
        if (!err) {
            err = errSSLProtocol;
		}
        return err;
    }
    
    charPtr = rec.contents.data;
    remaining = rec.contents.length;
	bool fatal = false;
	
    while (remaining > 0)
    {   level = (AlertLevel)*charPtr++;
        desc = (AlertDescription)*charPtr++;
		sslHdskMsgDebug("alert msg recieved level %d   desc %d",
			(int)level, (int)desc);
        remaining -= 2;
        SSLLogAlertMsg(desc, false);
		
        if (level == SSL_AlertLevelFatal) {
			/* explicit fatal errror */
			fatal = true;
            sslHdskMsgDebug("***Fatal alert %d received\n", desc);
        }
        SSLDetectCertRejected(ctx, desc);
		
        switch (desc) {
			/* A number of these are fatal by implication */
            case SSL_AlertUnexpectedMsg:
				err = errSSLPeerUnexpectedMsg;
				fatal = true;
				break;
            case SSL_AlertBadRecordMac:
 				err = errSSLPeerBadRecordMac;
				fatal = true;
				break;
			case SSL_AlertDecryptionFail:
 				err = errSSLPeerDecryptionFail;
				fatal = true;
				break;
            case SSL_AlertRecordOverflow:
 				err = errSSLPeerRecordOverflow;
				fatal = true;
				break;
            case SSL_AlertDecompressFail:
 				err = errSSLPeerDecompressFail;
				fatal = true;
				break;
            case SSL_AlertHandshakeFail:
 				err = errSSLPeerHandshakeFail;
				fatal = true;
				break;
            case SSL_AlertIllegalParam:
 				err = errSSLIllegalParam;
				fatal = true;
				break;
            case SSL_AlertBadCert:
				err = errSSLPeerBadCert;
				break;
            case SSL_AlertUnsupportedCert:
				err = errSSLPeerUnsupportedCert;
				break;
            case SSL_AlertCertRevoked:
				err = errSSLPeerCertRevoked;
				break;
            case SSL_AlertCertExpired:
				err = errSSLPeerCertExpired;
				break;
            case SSL_AlertCertUnknown:
                err = errSSLPeerCertUnknown;
                break;
            case SSL_AlertUnknownCA:
                err = errSSLPeerUnknownCA;
                break;
            case SSL_AlertAccessDenied:
                err = errSSLPeerAccessDenied;
                break;
            case SSL_AlertDecodeError:
                err = errSSLPeerDecodeError;
                break;
            case SSL_AlertDecryptError:
                err = errSSLPeerDecryptError;
                break;
            case SSL_AlertExportRestriction:
                err = errSSLPeerExportRestriction;
                break;
            case SSL_AlertProtocolVersion:
                err = errSSLPeerProtocolVersion;
                break;
            case SSL_AlertInsufficientSecurity:
                err = errSSLPeerInsufficientSecurity;
                break;
            case SSL_AlertInternalError:
                err = errSSLPeerInternalError;
                break;
            case SSL_AlertUserCancelled:
                err = errSSLPeerUserCancelled;
                break;
            case SSL_AlertNoRenegotiation:
                err = errSSLPeerNoRenegotiation;
                break;
			/* unusual cases.... */
            case SSL_AlertCloseNotify:
				/* the clean "we're done" case */
                SSLClose(ctx);
                err = noErr;
                break;
            case SSL_AlertNoCert:
                if((ctx->state == SSL_HdskStateClientCert) &&
				   (ctx->protocolSide == SSL_ServerSide) &&
				   (ctx->clientAuth != kAlwaysAuthenticate)) {
					/*
					 * Tolerate this unless we're configured to  
					 * *require* a client cert. If a client cert is
					 * required, we'll catch the error at the next 
					 * handshake msg we receive - which will probably
					 * be a client key exchange msg, which is illegal
					 * when we're in state SSL_HdskStateClientCert.
					 * If the client cert is optional, advance to 
					 * state ClientKeyExchange by pretending we
					 * just got a client cert msg. 
					 */
                    if ((err = SSLAdvanceHandshake(SSL_HdskCert, 
							ctx)) != 0) {
                        return err;
					}
				}
                break;
            default:
                /* Unknown alert, ignore if not fatal */
				if(level == SSL_AlertLevelFatal) {
					err = errSSLFatalAlert;
				}
				else {
					err = noErr;
				}
                break;
        }
		if(fatal) {
			/* don't bother processing any more */
			break;
		}
    }
    if(fatal) {
       	SSLDeleteSessionData(ctx);
	}
    return err;
}

OSStatus
SSLSendAlert(AlertLevel level, AlertDescription desc, SSLContext *ctx)
{   SSLRecord       rec;
    OSStatus        err;
    
	switch(ctx->negProtocolVersion) {
		case SSL_Version_Undetermined:
			/* Too early in negotiation to send an alert */
			return noErr;
		case SSL_Version_2_0:
			/* shouldn't be here */
			assert(0);
			return errSSLInternal;
		default:
			break;
	}
	if(ctx->sentFatalAlert) {
		/* no more alerts allowed */
		return noErr;
	}
    if ((err = SSLEncodeAlert(rec, level, desc, ctx)) != 0)
        return err;
	assert(ctx->sslTslCalls != NULL);
	SSLLogAlertMsg(desc, true);
    if ((err = ctx->sslTslCalls->writeRecord(rec, ctx)) != 0)
        return err;
    if ((err = SSLFreeBuffer(rec.contents, ctx)) != 0)
        return err;
    if(desc == SSL_AlertCloseNotify) {
		/* no more alerts allowed */
		ctx->sentFatalAlert = true;
	}
    return noErr;
}

static OSStatus
SSLEncodeAlert(SSLRecord &rec, AlertLevel level, AlertDescription desc, SSLContext *ctx)
{   OSStatus          err;
    
	rec.protocolVersion = ctx->negProtocolVersion;
	rec.contentType = SSL_RecordTypeAlert;
    rec.contents.length = 2;
    if ((err = SSLAllocBuffer(rec.contents, 2, ctx)) != 0)
        return err;
    rec.contents.data[0] = level;
    rec.contents.data[1] = desc;
    
    return noErr;
}

OSStatus
SSLFatalSessionAlert(AlertDescription desc, SSLContext *ctx)
{   OSStatus          err1, err2;
    
    if(desc != SSL_AlertCloseNotify) {
    	sslHdskMsgDebug("SSLFatalSessionAlert: desc %d\n", desc);
    }
    SSLChangeHdskState(ctx, SSL_HdskStateErrorClose);
    
	if(ctx->negProtocolVersion < TLS_Version_1_0) {
		/* translate to SSL3 if necessary */
		switch(desc) {
			case SSL_AlertDecryptionFail:
			case SSL_AlertRecordOverflow:
			case SSL_AlertAccessDenied:
			case SSL_AlertDecodeError:
			case SSL_AlertDecryptError:
			case SSL_AlertExportRestriction:
			case SSL_AlertProtocolVersion:
			case SSL_AlertInsufficientSecurity:
			case SSL_AlertUserCancelled:
			case SSL_AlertNoRenegotiation:
				desc = SSL_AlertHandshakeFail;
				break;
			case SSL_AlertUnknownCA:
				desc = SSL_AlertUnsupportedCert;
				break;
			case SSL_AlertInternalError:
				desc = SSL_AlertCloseNotify;
				break;
			default:
				/* send as is */
				break;
		}
	}
    /* Make session unresumable; I'm not stopping if I get an error,
        because I'd like to attempt to send the alert anyway */
    err1 = SSLDeleteSessionData(ctx);
    
    /* Second, send the alert */
    err2 = SSLSendAlert(SSL_AlertLevelFatal, desc, ctx);
    
	ctx->sentFatalAlert = true;
	
    /* If they both returned errors, arbitrarily return the first */
    return err1 != 0 ? err1 : err2;
}

#ifndef	NDEBUG

/* log alert messages */

static char *alertMsgToStr(AlertDescription msg)
{
	static char badStr[100];
	
	switch(msg) {
		case SSL_AlertCloseNotify:
			return "SSL_AlertCloseNotify";	
		case SSL_AlertUnexpectedMsg:
			return "SSL_AlertUnexpectedMsg";	
		case SSL_AlertBadRecordMac:
			return "SSL_AlertBadRecordMac";	
		case SSL_AlertDecryptionFail:
			return "SSL_AlertDecryptionFail";	
		case SSL_AlertRecordOverflow:
			return "SSL_AlertRecordOverflow";	
		case SSL_AlertDecompressFail:
			return "SSL_AlertDecompressFail";	
		case SSL_AlertHandshakeFail:
			return "SSL_AlertHandshakeFail";	
		case SSL_AlertNoCert:
			return "SSL_AlertNoCert";	
		case SSL_AlertBadCert:
			return "SSL_AlertBadCert";	
		case SSL_AlertUnsupportedCert:
			return "SSL_AlertUnsupportedCert";	
		case SSL_AlertCertRevoked:
			return "SSL_AlertCertRevoked";	
			
		case SSL_AlertCertExpired:
			return "SSL_AlertCertExpired";	
		case SSL_AlertCertUnknown:
			return "SSL_AlertCertUnknown";	
		case SSL_AlertIllegalParam:
			return "SSL_AlertIllegalParam";	
		case SSL_AlertUnknownCA:
			return "SSL_AlertUnknownCA";	
		case SSL_AlertAccessDenied:
			return "SSL_AlertAccessDenied";	
		case SSL_AlertDecodeError:
			return "SSL_AlertDecodeError";	
		case SSL_AlertDecryptError:
			return "SSL_AlertDecryptError";	

		case SSL_AlertExportRestriction:
			return "SSL_AlertExportRestriction";	
		case SSL_AlertProtocolVersion:
			return "SSL_AlertProtocolVersion";	
		case SSL_AlertInsufficientSecurity:
			return "SSL_AlertInsufficientSecurity";	
		case SSL_AlertInternalError:
			return "SSL_AlertInternalError";	
		case SSL_AlertUserCancelled:
			return "SSL_AlertUserCancelled";	
		case SSL_AlertNoRenegotiation:
			return "SSL_AlertNoRenegotiation";	

		default:
			sprintf(badStr, "Unknown state (%d(d)", msg);
			return badStr;
	}
}

static void SSLLogAlertMsg(AlertDescription msg, bool sent)
{
	sslHdskMsgDebug("---%s alert msg %s", 
		alertMsgToStr(msg), (sent ? "sent" : "recv"));
}

#endif	/* NDEBUG */
