ssl2Padding.cpp   [plain text]


/* Copyright (c) 2005-2006 Apple Computer, Inc.
 *
 * ssl2Padding.cpp - test CSSM_PADDING_APPLE_SSLv2.
 */
 

 /*
  * This table illustrates the combinations of:
  *
  * -- SSLv2 (v2) and SSLv3/TLSv1 (v3+) enables (0/1) on the client and server side
  * -- the resulting negotiated protocols (including those forced by a man-in-the-middle 
  *    attacker, denoted by (m))
  * -- the padding generated by the client (client pad)
  * -- the padding style checked by the server (server pad) 
  * -- and the end results
  *
  * client  server 
  * ------  ------ 
  * v2 v3+  v2 v3+  negotiate client pad  server pad   result
  * -- --   -- --   --------- ----------  ----------   ------
  * 0  0    x  x                                       impossible
  * x  x    0  0                                       impossible
  * 0  1    0  1     v3+         PKCS1      PKCS1      normal
  * 0  1    0  1     v2 (m)                            Attack fails, client rejects server hello
  * 0  1    1  0     fail                              incompatible
  * 0  1    1  1     v3+         PKCS1      PKCS1      normal
  * 0  1    1  1     v2 (m)                            Attack fails, client rejects server hello
  * 1  0    0  1     fail                              incompatible
  * 1  0    1  0     v2          PKCS1      PKCS1      normal, both sides are dumb SSL2
  * 1  0    1  0     v3+                               Attack fails, server rejects client hello
  * 1  0    1  1     v2          PKCS1      SSLv2      normal, dumb client
  * 1  0    1  1     v3+ (m)                           Attack fails, client rejects server hello
  * 1  1    0  1     v3+         PKCS1      PKCS1      normal
  * 1  1    0  1     v2 (m)                            Attack fails, server rejects SSL2 handshake
  * 1  1    1  0     v2          SSLv2      PKCS1      normal, dumb server
  * 1  1    1  0     v3+ (m)                           Attack fails, server rejects SSL3 handshakes
  * 1  1    1  1     v3+         PKCS1      PKCS1      normal
  * 1  1    1  1     v2 (m)      SSLv2      SSLv2      Attack fails due to SSLv2 pad detect
  *
  * The client generates SSLv2 padding if it's capable of v3+ but is currently operating
  * in v2 per negotiation. 
  *
  * The server checks for SSLv2 padding if it's capable of v3+ but is currently operating
  * in v2 per negotiation. If SSLv2 padding is seen, fail. 
  */
  
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <Security/cssm.h>
#include "cspwrap.h"
#include "common.h"
#include "cspdlTesting.h"

#define USAGE_NAME			"noUsage"
#define USAGE_NAME_LEN		(strlen(USAGE_NAME))
#define LOOPS_DEF			10

#define KEY_SIZE_DEF		1024
#define KEY_SIZE_SMALL		512

#define PTEXT_LEN			32		/* bytes */

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("   Options:\n");
	printf("   l=loops (default=%d; 0=forever)\n", LOOPS_DEF);
	printf("   k=keySizeInBits; default=%d\n", KEY_SIZE_DEF);
	printf("   D (CSP/DL; default = bare CSP)\n");
	printf("   p (pause on each loop)\n");
	printf("   u (quick; small keys)\n");
	printf("   v(erbose)\n");
	printf("   q(uiet)\n");
	printf("   h(elp)\n");
	exit(1);
}

/* special-purpose generate-context, encrypt, and decrypt routines just for this test */
static int genRsaCryptContext(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY_PTR		key,
	CSSM_PADDING		padding,
	CSSM_BOOL			quiet,
	CSSM_CC_HANDLE		&ccHand)			// RETURNED
{
	CSSM_RETURN crtn;
	CSSM_ACCESS_CREDENTIALS	creds;
	
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
	crtn = CSSM_CSP_CreateAsymmetricContext(cspHand,
		CSSM_ALGID_RSA,
		&creds,			// access
		key,
		padding,
		&ccHand);
	if(crtn) {
		cssmPerror("CSSM_CSP_CreateAsymmetricContext", crtn);
		return testError(quiet);
	}
	return 0;
}

static int doRsaEncrypt(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY_PTR		key,
	CSSM_PADDING		padding,
	CSSM_BOOL			quiet,
	CSSM_DATA			*ptext,
	CSSM_DATA			*ctext)
{
	CSSM_CC_HANDLE	ccHand;
	int				rtn;
	CSSM_RETURN		crtn;
	CSSM_SIZE		bytesMoved;
	CSSM_DATA		remData = {0, NULL};
	
	rtn = genRsaCryptContext(cspHand, key, padding, quiet, ccHand);
	if(rtn) {
		return rtn;
	}
	crtn = CSSM_EncryptData(ccHand,
		ptext,
		1,
		ctext,
		1,
		&bytesMoved,
		&remData);
	CSSM_DeleteContext(ccHand);
	if(crtn == CSSM_OK) {
		/*
		 * Deal with remData - its contents are included in bytesMoved.
		 */
		if(remData.Length != 0) {
			/* malloc and copy a new one */
			uint8 *newCdata = (uint8 *)appMalloc(bytesMoved, NULL);
			memmove(newCdata, ctext->Data, ctext->Length);
			memmove(newCdata+ctext->Length, remData.Data, remData.Length);
			CSSM_FREE(ctext->Data);
			ctext->Data = newCdata;
		}
		ctext->Length = bytesMoved;
		return 0;
	}
	else {
		cssmPerror("CSSM_EncryptData", crtn);
		return testError(quiet);
	}
}

static int doRsaDecrypt(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY_PTR		key,
	CSSM_PADDING		padding,
	CSSM_BOOL			quiet,
	CSSM_RETURN			expectRtn,
	CSSM_DATA			*ctext,
	CSSM_DATA			*rptext)
{
	CSSM_CC_HANDLE	ccHand;
	int				rtn;
	CSSM_RETURN		crtn;
	CSSM_SIZE		bytesMoved;
	CSSM_DATA		remData = {0, NULL};
	
	rtn = genRsaCryptContext(cspHand, key, padding, quiet, ccHand);
	if(rtn) {
		return rtn;
	}
	crtn = CSSM_DecryptData(ccHand,
		ctext,
		1,
		rptext,
		1,
		&bytesMoved,
		&remData);
	CSSM_DeleteContext(ccHand);
	if(crtn != expectRtn) {
		printf("   CSSM_DecryptData: expect %s\n",	cssmErrToStr(expectRtn));
		printf("   CSSM_DecryptData: got    %s\n",  cssmErrToStr(crtn));
		return testError(quiet);
	}
	if(crtn) {
		/* no need to process further */
		return 0;
	}
	if(crtn == CSSM_OK) {
		/*
		 * Deal with remData - its contents are included in bytesMoved.
		 */
		if(remData.Length != 0) {
			/* malloc and copy a new one */
			uint8 *newRpdata = (uint8 *)appMalloc(bytesMoved, NULL);
			memmove(newRpdata, rptext->Data, rptext->Length);
			memmove(newRpdata+rptext->Length, remData.Data, remData.Length);
			CSSM_FREE(rptext->Data);
			rptext->Data = newRpdata;
		}
		rptext->Length = bytesMoved;
		return 0;
	}
	else {
		cssmPerror("CSSM_DecryptData", crtn);
		return testError(quiet);
	}
}

/*
 * encrypt with specified pad
 * decrypt with specified pad, verify expected result (which may be failure)
 */
static int doTest(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY_PTR		pubKey,
	CSSM_KEY_PTR		privKey,
	CSSM_PADDING		encrPad,
	CSSM_PADDING		decrPad,
	CSSM_BOOL			quiet,
	CSSM_RETURN			expectResult)
{
	int				rtn;
	uint8			ptext[PTEXT_LEN];
	CSSM_DATA		ptextData = {PTEXT_LEN, ptext};
	CSSM_DATA		ctext = {0, NULL};
	CSSM_DATA		rptext = {0, NULL};
	
	simpleGenData(&ptextData, PTEXT_LEN, PTEXT_LEN);
	rtn = doRsaEncrypt(cspHand, pubKey, encrPad, quiet, &ptextData, &ctext);
	if(rtn) {
		goto errOut;
	}
	rtn = doRsaDecrypt(cspHand, privKey, decrPad, quiet, expectResult, &ctext, &rptext);
	if(rtn) {
		goto errOut;
	}
	if(expectResult == CSSM_OK) {
		if(memcmp(rptext.Data, ptextData.Data, PTEXT_LEN)) {
			printf("***Data miscomapare after decrypt\n");
			rtn = testError(quiet);
		}
	}
errOut:
	if(ctext.Data) {
		CSSM_FREE(ctext.Data);
	}
	if(rptext.Data) {
		CSSM_FREE(rptext.Data);
	}
	return rtn;
}

int main(int argc, char **argv)
{
	int					arg;
	char				*argp;
	unsigned			loop;
	CSSM_CSP_HANDLE 	cspHand;
	int					rtn = 0;
	
	/*
	 * User-spec'd params
	 */
	unsigned			loops = LOOPS_DEF;
	CSSM_BOOL			verbose = CSSM_FALSE;
	CSSM_BOOL			quiet = CSSM_FALSE;
	uint32				keySizeInBits = KEY_SIZE_DEF;
	CSSM_BOOL			bareCsp = CSSM_TRUE;
	CSSM_BOOL			doPause = CSSM_FALSE;
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
		    case 'l':
				loops = atoi(&argp[2]);
				break;
			case 'k':
				keySizeInBits = atoi(&argv[arg][2]);
				break;
			case 'D':
				bareCsp = CSSM_FALSE;
				break;
			case 'u':
				keySizeInBits = KEY_SIZE_SMALL;
				break;
		    case 'v':
		    	verbose = CSSM_TRUE;
				break;
		    case 'p':
		    	doPause = CSSM_TRUE;
				break;
		    case 'q':
		    	quiet = CSSM_TRUE;
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	
	testStartBanner("ssl2Padding", argc, argv);

	cspHand = cspDlDbStartup(bareCsp, NULL);
	if(cspHand == 0) {
		exit(1);
	}
	CSSM_KEY pubKey;
	CSSM_KEY privKey;
	
	CSSM_RETURN crtn = cspGenKeyPair(cspHand, CSSM_ALGID_RSA,
		USAGE_NAME, USAGE_NAME_LEN,
		keySizeInBits,
		&pubKey, CSSM_TRUE /* ref */, CSSM_KEYUSE_ANY, CSSM_KEYBLOB_RAW_FORMAT_NONE,
		&privKey, CSSM_TRUE /* ref */, CSSM_KEYUSE_ANY, CSSM_KEYBLOB_RAW_FORMAT_NONE,
		CSSM_FALSE);
	if(crtn) {
		printf("***Error generating key pair. Aborting.\n");
		exit(1);
	}
	for(loop=1; ; loop++) {
		if(doPause) {
			fpurge(stdin);
			printf("Top of loop; hit CR to proceed: ");
			getchar();
		}
		
		/* encrypt by client, decrypt by server. */
		
		/*
		 * SSLv3+ negotiated, normal case, or
		 * both sides support SSLv2 only
		 */
		if(!quiet) {
			printf("...loop %u\n", loop);
			printf("   encrPad PKCS1 decrPad PKCS1\n");
		}
		rtn = doTest(cspHand, &pubKey, &privKey, 
			CSSM_PADDING_PKCS1, CSSM_PADDING_PKCS1,
			quiet, CSSM_OK);
		if(rtn) {
			break;
		}
		
		/* 
		 * Server supports SSLv2 and SSLv3+, client supports SSLv2 only
		 */
		if(!quiet) {
			printf("   encrPad PKCS1 decrPad SSLv2\n");
		}
		rtn = doTest(cspHand, &pubKey, &privKey, 
			CSSM_PADDING_PKCS1, CSSM_PADDING_APPLE_SSLv2,
			quiet, CSSM_OK);
		if(rtn) {
			break;
		}
		 
		/*
		 * Server supports SSLv2 only, client supports SSLv2 and SSLv3+
		 */
		if(!quiet) {
			printf("   encrPad SSLv2 decrPad PKCS1\n");
		}
		rtn = doTest(cspHand, &pubKey, &privKey, 
			CSSM_PADDING_APPLE_SSLv2, CSSM_PADDING_PKCS1,
			quiet, CSSM_OK);
		if(rtn) {
			break;
		}
		 
		/*
		 * Both sides support SSLv3+ but a man in the middle has forced the 
		 * negotiated protocol down to SSLv2 
		 */
		if(!quiet) {
			printf("   encrPad SSLv2 decrPad SSLv2, expect failure\n");
		}
		rtn = doTest(cspHand, &pubKey, &privKey, 
			CSSM_PADDING_APPLE_SSLv2, CSSM_PADDING_APPLE_SSLv2,
			quiet, CSSMERR_CSP_APPLE_SSLv2_ROLLBACK);
		if(rtn) {
			break;
		}

		if(loops && (loop == loops)) {
			break;
		}
	}	/* for loop */

	CSSM_ModuleDetach(cspHand);
	if((rtn == 0) && !quiet) {
		printf("%s test complete\n", argv[0]);
	}
	return rtn;
}