sslHdshakeTime.cpp   [plain text]


/*
 * Measure performance of SecureTransport handshake
 *
 * Written by Doug Mitchell. 
 */
#include <Security/SecureTransport.h>
#include <clAppUtils/sslAppUtils.h>
#include <clAppUtils/ioSock.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <utilLib/common.h>

#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/param.h>
#include <CoreFoundation/CoreFoundation.h>

/* default - run both server and client on this machine, port 1200  */
#define HOST_DEF		"localhost"
#define PORT_DEF		1200

/* default keychain */
#define DEFAULT_KC		"localcert"

#define DH_PARAM_FILE	"dhParams_1024.der"

#define GET_MSG			"GET / HTTP/1.0\r\n\r\n"

static void usage(char **argv)
{
    printf("Usage: %s s[erver]|c[lient] loops [option ...]\n", argv[0]);
    printf("Options:\n");
	printf("   -h hostname (default = %s)\n", HOST_DEF);
	printf("   -p port (default = %d)\n", PORT_DEF);
	printf("   -k keychain (default = %s)\n", DEFAULT_KC);
	printf("   -c cipher (default = RSA/RC4; server side only)\n");
	printf("      ciphers: r=RSA/RC4; d=RSA/DES; D=RSA/3DES; h=DHA/RC4; "
					"H=DH/DSS/DES\n");
	printf("   -v version (t|2|3 default = t(TLS1); server side only)\n");
	printf("   -w password  (unlock server keychain with password)\n");
	printf("   -a (enable client authentication)\n");
	printf("   -r (resumable session enabled; default is disabled)\n");
	printf("   -n (No client side anchor specification; root is in system KC)\n");
	printf("   -d (disable cert verify)\n");
	printf("   -o (Allow hostname spoofing)\n");
	printf("   -g Send GET msg (needs for talking to real servers)\n");
	printf("   -V (verbose)\n");
    exit(1);
}

#include <signal.h>

void sigpipe(int sig) 
{ 
	fflush(stdin);
	printf("***SIGPIPE***\n");
}

int main(int argc, char **argv)
{
	/* user-spec'd variables */
	unsigned		loops;
	char 			*kcName = DEFAULT_KC;
	int 			port = PORT_DEF;
	char 			*hostName = HOST_DEF;
	SSLCipherSuite 	cipherSuite = SSL_RSA_WITH_RC4_128_SHA;
	SSLProtocol 	prot = kTLSProtocol1Only;
	char 			password[200];
	bool 			clientAuthEnable = false;
	bool 			isServer = false;
	bool			diffieHellman = false;
	bool 			verbose = false;
	bool			resumeEnable = false;
	bool			setClientAnchor = true;
	bool			certVerifyEnable = true;
	bool			checkHostName = true;
	bool			sendGet = false;
	
	otSocket 		listenSock = 0;			// for server only
	CFArrayRef 		myCerts = NULL;
	
	signal(SIGPIPE, sigpipe);
	if(argc < 3) {
		usage(argv);
	}
	password[0] = 0;
	switch(argv[1][0]) {
		case 's':
			isServer = true;
			break;
		case 'c':
			isServer = false;
			break;
		default:
			usage(argv);
	}
	loops = atoi(argv[2]);
	if(loops == 0) {
		usage(argv);
	}
	
	extern int optind;
	extern char *optarg;
	int arg;
	optind = 3;
	while ((arg = getopt(argc, argv, "h:p:k:x:c:v:w:b:aVrndog")) != -1) {
		switch (arg) {
			case 'h':
				hostName = optarg;
				break;
			case 'p':
				port = atoi(optarg);
				break;
			case 'k':
				kcName = optarg;
				break;
			case 'c':
				if(!isServer) {
					printf("***Specify cipherSuite on server side.\n");
					exit(1);
				}
				switch(optarg[0]) {
					case 'r':
						cipherSuite = SSL_RSA_WITH_RC4_128_SHA;
						break;
					case 'd':
						cipherSuite = SSL_RSA_WITH_DES_CBC_SHA;
						break;
					case 'D':
						cipherSuite = SSL_RSA_WITH_3DES_EDE_CBC_SHA;
						break;
					case 'h':
						cipherSuite = SSL_DH_anon_WITH_RC4_128_MD5;
						diffieHellman = true;
						break;
					case 'H':
						cipherSuite = SSL_DHE_DSS_WITH_DES_CBC_SHA;
						diffieHellman = true;
						break;
					default:
						usage(argv);
				}
				break;
			case 'v':
				if(!isServer) {
					printf("***Specify protocol on server side.\n");
					exit(1);
				}
				switch(optarg[0]) {
					case 't':
						prot = kTLSProtocol1Only;
						break;
					case '2':
						prot = kSSLProtocol2;
						break;
					case '3':
						prot = kSSLProtocol3Only;
						break;
					default:
						usage(argv);
				}
				break;
			case 'w':
				strcpy(password, optarg);
				break;
			case 'a':
				clientAuthEnable = true;
				break;
			case 'V':
				verbose = true;
				break;
			case 'r':
				resumeEnable = true;
				break;
			case 'n':
				setClientAnchor = false;
				break;
			case 'd':
				certVerifyEnable = false;
				break;
			case 'o':
				checkHostName = false;
				break;
			case 'g':
				sendGet = true;
				break;
			default:
				usage(argv);
		}
	}
	
	/* gather Diffie-Hellman params from cwd */
	if(JAGUAR_BUILD && diffieHellman) {
		printf("***SOrry, DIffie Hellman not available in this config.\n");
		exit(1);
	}
	unsigned char *dhParams = NULL;
	unsigned dhParamsLen = 0;
	if(diffieHellman && isServer) {
		if(readFile(DH_PARAM_FILE, &dhParams, &dhParamsLen)) {
			printf("***Error reading Diffie-Hellman Params. Prepare to "
				"wait for a minute during SSL handshake.\n");
		}
	}
	
	/*
	 * Open keychain; both sides use the same one.
	 */
	OSStatus ortn;
	SecKeychainRef certKc = NULL;
	if(isServer || clientAuthEnable || setClientAnchor) {
		ortn = SecKeychainOpen(kcName, &certKc);
		if(ortn) {
			printf("Error opening keychain %s (%d); aborting.\n",
				kcName, (int)ortn);
			exit(1);
		}
		if(password[0]) {
			ortn = SecKeychainUnlock(certKc, strlen(password), password, true);
			if(ortn) {
				printf("SecKeychainUnlock returned %lu\n", ortn);
				/* oh well */
			}
		}
	}

	/* just do this once */
	if(clientAuthEnable || isServer || setClientAnchor) {
		myCerts = sslKcRefToCertArray(certKc, CSSM_FALSE, CSSM_TRUE, NULL);
		if(myCerts == NULL) {
			exit(1);
		}
	}
	
	/* server sets up listen port just once */
	if(isServer) {
		printf("...listening for client connection on port %d\n", port);
		ortn = ListenForClients(port, 0, &listenSock);
		if(ortn) {
			printf("...error establishing a listen socket. Aborting.\n");
			exit(1);
		}
	}

	CFAbsoluteTime setupTotal = 0;
	CFAbsoluteTime handShakeTotal = 0;
	
	for(unsigned loop=0; loop<loops; loop++) {
		otSocket peerSock = 0;
		PeerSpec peerId;
		
		if(isServer) {
			ortn = AcceptClientConnection(listenSock, &peerSock, &peerId);
			if(ortn) {
				printf("...error listening for connection. Aborting.\n");
				exit(1);
			}
		}
		else {
			/* client side */
			if(verbose) {
				printf("...connecting to host %s at port %d\n", hostName, port);
			}
			ortn = MakeServerConnection(hostName, port, 0, &peerSock,
				&peerId);
			if(ortn) {
				printf("...error connecting to server %s. Aborting.\n",
					hostName);
				exit(1);
			}
		}
	
		/* start timing SSL setup */
		CFAbsoluteTime setupStart = CFAbsoluteTimeGetCurrent();
	
		SSLContextRef ctx;
		ortn = SSLNewContext(isServer, &ctx);
		if(ortn) {
			printSslErrStr("SSLNewContext", ortn);
			exit(1);
		} 
		ortn = SSLSetIOFuncs(ctx, SocketRead, SocketWrite);
		if(ortn) {
			printSslErrStr("SSLSetIOFuncs", ortn);
			exit(1);
		} 
		ortn = SSLSetConnection(ctx, peerSock);
		if(ortn) {
			printSslErrStr("SSLSetConnection", ortn);
			exit(1);
		}
		if(checkHostName) {
			ortn = SSLSetPeerDomainName(ctx, hostName, strlen(hostName) + 1);
			if(ortn) {
				printSslErrStr("SSLSetPeerDomainName", ortn);
				exit(1);
			}	
		}
		if(resumeEnable) {
			ortn = SSLSetPeerID(ctx, &peerId, sizeof(PeerSpec));
			if(ortn) {
				printSslErrStr("SSLSetPeerID", ortn);
				exit(1);
			}
		}
		if(!certVerifyEnable) {
			/*
			 * Do this before setting up certs to allow for optimization 
			 * on server side (setting valid cert without verifying them)
			 */
			ortn = SSLSetEnableCertVerify(ctx, false);
			if(ortn) {
				printSslErrStr("SSLSetPeerID", ortn);
				exit(1);
			}
		}
		
		/*
		 * Server/client specific setup.
		 *
		 * Client uses the same keychain as server, but it uses it for 
		 * sslAddTrustedRoots() instead of getSslCerts() and 
		 * SSLSetCertificate().
		 */
		if(clientAuthEnable || isServer) {
			if(!certVerifyEnable) {
				/* don't bother...this is heavyweight */
				ortn = addIdentityAsTrustedRoot(ctx, myCerts);
				if(ortn) {
					exit(1);
				}
			}
			ortn = SSLSetCertificate(ctx, myCerts);
			if(ortn) {
				printSslErrStr("SSLSetCertificate", ortn);
				exit(1);
			}
		}
		if(isServer) {
			SSLAuthenticate auth;
			if(clientAuthEnable) {
				auth = kAlwaysAuthenticate;
			}
			else {
				auth = kNeverAuthenticate;
			}
			ortn = SSLSetClientSideAuthenticate(ctx, auth);
			if(ortn) {
				printSslErrStr("SSLSetClientSideAuthenticate", ortn);
				exit(1);
			}
			ortn = SSLSetEnabledCiphers(ctx, &cipherSuite, 1);
			if(ortn) {
				printSslErrStr("SSLSetEnabledCiphers", ortn);
				exit(1);
			}
			#if 0
			/* FIXME why does this fail on Jaguar? */
			ortn = SSLSetProtocolVersion(ctx, prot);
			if(ortn) {
				printSslErrStr("SSLSetProtocolVersion", ortn);
				exit(1);
			}
			#endif
			#if !JAGUAR_BUILD
			if(dhParams != NULL) {
				ortn = SSLSetDiffieHellmanParams(ctx, dhParams, dhParamsLen);
				if(ortn) {
					printSslErrStr("SSLSetDiffieHellmanParams", ortn);
					exit(1);
				}
			}
			#endif
		}
		else {
			/* client setup */
			if(!clientAuthEnable && setClientAnchor) {
				/* We're not presenting a cert; trust the server certs */
				bool foundOne;
				if(certKc == NULL) {
					printf("sslAddTrustedRoots screwup\n");
					exit(1);
				}
				ortn = sslAddTrustedRoots(ctx, certKc, &foundOne);
				if(ortn) {
					printSslErrStr("sslAddTrustedRoots", ortn);
					exit(1);
				}
			}
		}
		
		/*
		 * Context setup complete. Start timing handshake.
		 */
		CFAbsoluteTime hshakeStart = CFAbsoluteTimeGetCurrent();
		do {   
			ortn = SSLHandshake(ctx);
		} while (ortn == errSSLWouldBlock);
		if(ortn) {
			printSslErrStr("SSLHandshake", ortn);
			exit(1);
		}
		CFAbsoluteTime hshakeEnd = CFAbsoluteTimeGetCurrent();
		
		/* snag these before data xfer possibly shuts down connection */
		SSLProtocol	negVersion;
		SSLCipherSuite negCipher;
		SSLClientCertificateState certState;		// RETURNED
	
		SSLGetNegotiatedCipher(ctx, &negCipher);
		SSLGetNegotiatedProtocolVersion(ctx, &negVersion);
		SSLGetClientCertificateState(ctx, &certState);
			
		/*
		 * Shut down. Server does the SSLClose while client tries to read. Client gets
		 * a errSSLClosedGraceful then does its SSLCLose.
		 */
		if(!isServer) {
			char data[2];
			size_t actRead;
			bool done = false;
			if(sendGet) {
				ortn = SSLWrite(ctx, GET_MSG, strlen(GET_MSG), &actRead);
				if(ortn) {
					printSslErrStr("SSLWrite", ortn);
				}
			}
			do {
				ortn = SSLRead(ctx, data, 2, &actRead);
				switch(ortn) {
					case errSSLClosedGraceful:
						done = true;
						break;
					case noErr:
						/* try again */
						break;
					default:
						printf("Unexpected rtn on client SSLRead(); bytesRead %u\n",
							(unsigned)actRead);
						printSslErrStr("SSLRead", ortn);
						done = true;
						/* ah, keep going */
						break;
				}
			} while(!done);
		}
		/* shut down channel */
		ortn = SSLClose(ctx);
		if(ortn) {
			printSslErrStr("SSLCLose", ortn);
			exit(1);
		}
	
		/* how'd we do? */
		if(verbose) {
			printf("SSL version          : %s\n", 
				sslGetProtocolVersionString(negVersion));
			printf("CipherSuite          : %s\n",
				sslGetCipherSuiteString(negCipher));
			printf("Client Cert State    : %s\n",
					sslGetClientCertStateString(certState));
			printf("SSLContext setup     : %f s\n", hshakeStart - setupStart);
			printf("SSL Handshake        : %f s\n", hshakeEnd - hshakeStart);
		}
		setupTotal     += (hshakeStart - setupStart);
		handShakeTotal += (hshakeEnd - hshakeStart);
	}
	
	printf("\n");
	printf("SSL setup     avg %f s\n", setupTotal / loops);
	printf("SSL handshake avg %f s\n", handShakeTotal / loops);
	return 0;
}