sslEcdsa.cpp   [plain text]


/*
 * sslEcdsa.cpp - test SSL connections to a number of known servers.
 *
 * Note this uses the keychain ecdsa.keychain in cwd; it contains an
 * SSL client auth identity. To avoid ACL hassles and to allow this
 * program to run hands-off, the identity is imported into this keychain
 * with no ACL on the private key. This is done with the kcImport tool
 * like so:
 *
 * % kcImport ecc-secp256r1-client.pfx -k ___path_to_cwd___/ecdsa.keychain -f pkcs12 -z password -n
 */
#include <Security/SecureTransport.h>
#include <Security/SecureTransportPriv.h>
#include <Security/Security.h>
#include "sslAppUtils.h"
#include "ioSock.h"
//#include <utilLib/common.h>

#include <Security/SecBase.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/param.h>

#if NO_SERVER
#include <securityd/spi.h>
#endif


static void usage(char **argv)
{
	printf("Usage: %s [options]\n", argv[0]);
	printf("options:\n");
	printf("   -t testNum   -- only do test testNum; default is all\n");
	printf("   -q           -- quiet\n");
	printf("   -b           -- non blocking I/O\n");
	printf("   -p           -- pause for malloc debug\n");
	exit(1);
}

#define IGNORE_SIGPIPE	1
#if 	IGNORE_SIGPIPE
#include <signal.h>

static void sigpipe(int sig)
{
}
#endif	/* IGNORE_SIGPIPE */

/* Test params */
typedef struct {
	const char				*hostName;
	int						port;

	/* We enable exacly one CipherSuite and require that to work */
	SSLCipherSuite			cipherSuite;

	/* Curve to specify; SSL_Curve_None means use default */
	SSL_ECDSA_NamedCurve	specCurve;

	/* Curve to verify; SSL_Curve_None means don't check */
	SSL_ECDSA_NamedCurve	expCurve;

	/*
	 * keychain containing client-side cert, located in LOCAL_BUILD_DIR.
	 * NULL means no keychain.
	 */
	const char				*keychain;

	/* password for above keychain */
	const char				*kcPassword;
} EcdsaTestParams;

static const EcdsaTestParams ecdsaTestParams[] =
{
	/* client auth */
	{
		"tls.secg.org", 8443, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_None,
		"ecdsa.keychain", "password"
	},
	/* tla.secg.org -- port 40023 - secp256r1  */
	{
		"tls.secg.org", 40023, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"tls.secg.org", 40023, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},

	/* tla.secg.org -- port 40024 - secp384r1 */
	/* This one doesn't let you specify a curve */
	{
		"tls.secg.org", 40024, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"tls.secg.org", 40024, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},

	/* tla.secg.org -- port 40025 - secp521r1 */
	{
		"tls.secg.org", 40025, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"tls.secg.org", 40025, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},


	/* ecc.fedora.redhat.com - port 8443 - secp256r1 */
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 8443, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},

	/* ecc.fedora.redhat.com - port 8444 - SSL_Curve_secp384r1 */
	/* This doesn't work, the server requires a redirect ...
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	*/
	{
		"ecc.fedora.redhat.com", 8445, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_secp384r1, SSL_Curve_secp384r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp384r1, SSL_Curve_secp384r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp384r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
		SSL_Curve_secp384r1, SSL_Curve_secp384r1
	},
	{
		"ecc.fedora.redhat.com", 8444, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp384r1, SSL_Curve_secp384r1
	},

	/* ecc.fedora.redhat.com - port 8445 - SSL_Curve_secp521r1 */
	/* This one can't do RC4_128 without some HTTP redirection */
	{
		"ecc.fedora.redhat.com", 8445, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp521r1
	},
	{
		"ecc.fedora.redhat.com", 8445, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_secp521r1, SSL_Curve_secp521r1
	},
	{
		"ecc.fedora.redhat.com", 8445, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_secp521r1, SSL_Curve_secp521r1
	},

	/* ecc.fedora.redhat.com - port 443 - secp256r1 with RSA authentication */
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
		SSL_Curve_secp256r1, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDH_RSA_WITH_RC4_128_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},
	{
		"ecc.fedora.redhat.com", 443, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
		SSL_Curve_None, SSL_Curve_secp256r1
	},

	/* etc. */
};
#define NUM_TEST_PARAMS		(sizeof(ecdsaTestParams) / sizeof(ecdsaTestParams[0]))

static void dumpParams(
	const EcdsaTestParams *testParams)
{
	printf("%s:%d %-33s ",
		testParams->hostName, testParams->port,
		/* skip leading "TLS_" */
		sslGetCipherSuiteString(testParams->cipherSuite)+4);
	if(testParams->expCurve != SSL_Curve_None) {
		printf("expCurve = %s ", sslCurveString(testParams->expCurve));
	}
	if(testParams->specCurve != SSL_Curve_None) {
		printf("specCurve = %s ", sslCurveString(testParams->specCurve));
	}
	if(testParams->keychain) {
		printf("Client Auth Enabled");
	}
	putchar('\n');
}

static void dumpErrInfo(
	const char *op,
	const EcdsaTestParams *testParams,
	OSStatus ortn)
{
	printf("***%s failed for ", op);
	dumpParams(testParams);
	printf("   error: %s\n", sslGetSSLErrString(ortn));
}

/*
 * Custom ping for this test.
 */
#define RCV_BUF_SIZE		256

static int doSslPing(
	const EcdsaTestParams	*testParams,
	bool					quiet,
	int						nonBlocking)
{
    PeerSpec            peerId;
	otSocket			sock = 0;
    OSStatus            ortn;
    SSLContextRef       ctx = NULL;
	SSLCipherSuite		negCipher;

	/* first make sure requested server is there */
	ortn = MakeServerConnection(testParams->hostName, testParams->port,
		nonBlocking, &sock, &peerId);
    if(ortn) {
	printf("MakeServerConnection(%s) returned %d\n",
			testParams->hostName, (int)ortn);
	return -1;
    }

	/*
	 * Set up a SecureTransport session.
	 * First the standard calls.
	 */
	ortn = SSLNewContext(false, &ctx);
	if(ortn) {
		printSslErrStr("SSLNewContext", ortn);
		goto cleanup;
	}
	ortn = SSLSetIOFuncs(ctx, SocketRead, SocketWrite);
	if(ortn) {
		printSslErrStr("SSLSetIOFuncs", ortn);
		goto cleanup;
	}

	/* Restrict to only TLSv1 - we have to do this because of Radar 6133465 */
	ortn = SSLSetProtocolVersionEnabled(ctx, kSSLProtocolAll, false);
	if(ortn) {
		printSslErrStr("SSLSetProtocolVersionEnabled", ortn);
		goto cleanup;
	}
	ortn = SSLSetProtocolVersionEnabled(ctx, kTLSProtocol1, true);
	if(ortn) {
		printSslErrStr("SSLSetProtocolVersionEnabled", ortn);
		goto cleanup;
	}

	/* Restrict to only one CipherSuite */
	ortn = SSLSetEnabledCiphers(ctx, &testParams->cipherSuite, 1);
	if(ortn) {
		printSslErrStr("SSLSetEnabledCiphers", ortn);
		goto cleanup;
	}

	ortn = SSLSetConnection(ctx, (SSLConnectionRef)(intptr_t)sock);
	if(ortn) {
		printSslErrStr("SSLSetConnection", ortn);
		goto cleanup;
	}

	/* These test servers have custom roots, just allow any roots for this test */
	ortn = SSLSetAllowsExpiredCerts(ctx, true);
	if(ortn) {
		printSslErrStr("SSLSetAllowExpiredCerts", ortn);
		goto cleanup;
	}
	ortn = SSLSetAllowsAnyRoot(ctx, true);
	if(ortn) {
		printSslErrStr("SSLSetAllowAnyRoot", ortn);
		goto cleanup;
	}

	if(testParams->specCurve != SSL_Curve_None) {
		ortn = SSLSetECDSACurves(ctx, &testParams->specCurve, 1);
		if(ortn) {
			printSslErrStr("SSLSetAllowAnyRoot", ortn);
			goto cleanup;
		}
	}

#if 0
	if(testParams->keychain) {
		char kcPath[2000];
		const char *lbd = getenv("LOCAL_BUILD_DIR");
		if(lbd == NULL) {
			printf("WARNING: no LOCAL_BUILD_DIR env var faound\n");
			lbd = "";
		}
		snprintf(kcPath, 2000, "%s/%s", lbd, testParams->keychain);
		SecKeychainRef kcRef = NULL;
		CFArrayRef certArray = getSslCerts(kcPath,
			false,          // encryptOnly
			false,          // completeCertChain
			NULL,			// anchorFile
			&kcRef);
		if(kcRef) {
			/* Unlock it */
			ortn = SecKeychainUnlock(kcRef,
				strlen(testParams->kcPassword), testParams->kcPassword,
				true);
			if(ortn) {
				cssmPerror("SecKeychainUnlock", ortn);
				/* oh well */
			}
			CFRelease(kcRef);
		}
		if(certArray == NULL) {
			printf("***WARNING no keychain found at %s\n", kcPath);
		}
		ortn = SSLSetCertificate(ctx, certArray);
		if(ortn) {
			printSslErrStr("SSLSetAllowAnyRoot", ortn);
			goto cleanup;
		}
		CFRelease(certArray);
	}
#endif
    do {
		ortn = SSLHandshake(ctx);
    } while (ortn == errSSLWouldBlock);

    /* convert normal "shutdown" into zero err rtn */
	switch(ortn) {
		case errSecSuccess:
			break;
		case errSSLClosedGraceful:
		case errSSLClosedNoNotify:
			ortn = errSecSuccess;
			goto cleanup;
		default:
			dumpErrInfo("SSLHandshake", testParams, ortn);
			goto cleanup;
	}


	/*
	 * Unlike other ping tests we don't bother with a GET - just validate
	 * the handshake
	 */
	ortn = SSLGetNegotiatedCipher(ctx, &negCipher);
	if(ortn) {
		dumpErrInfo("SSLHandshake", testParams, ortn);
		goto cleanup;
	}

	/* here is really what we're testing */
	if(negCipher != testParams->cipherSuite) {
		printf("***Cipher mismatch for ");
		dumpParams(testParams);
		printf("Negotiated cipher: %s\n", sslGetCipherSuiteString(negCipher));
		ortn = errSecIO;
		goto cleanup;
	}
	if(testParams->expCurve != SSL_Curve_None) {
		SSL_ECDSA_NamedCurve actNegCurve;
		ortn = SSLGetNegotiatedCurve(ctx, &actNegCurve);
		if(ortn) {
			printSslErrStr("SSLGetNegotiatedCurve", ortn);
			goto cleanup;
		}
		if(actNegCurve != testParams->expCurve) {
			printf("***Negotiated curve error\n");
			printf("Specified curve: %s\n", sslCurveString(testParams->specCurve));
			printf("Expected  curve: %s\n", sslCurveString(testParams->expCurve));
			printf("Obtained  curve: %s\n", sslCurveString(actNegCurve));
			ortn = errSecIO;
			goto cleanup;
		}
	}
	if(testParams->keychain) {
		/* Verify client auth */
		SSLClientCertificateState authState;
		ortn = SSLGetClientCertificateState(ctx, &authState);
		if(ortn) {
			printSslErrStr("SSLGetClientCertificateState", ortn);
			goto cleanup;
		}
		if(authState != kSSLClientCertSent) {
			printf("***Unexpected ClientCertificateState\n");
			printf("   Expected: ClientCertSent\n");
			printf("   Received: %s\n", sslGetClientCertStateString(authState));
			ortn = errSecIO;
			goto cleanup;
		}
	}

    ortn = SSLClose(ctx);

cleanup:
	if(sock) {
		endpointShutdown(sock);
	}
	if(ctx) {
	    SSLDisposeContext(ctx);
	}
	return (int)ortn;
}


int main(int argc, char **argv)
{
	int 		ourRtn = 0;
	bool		quiet = false;
	int			nonBlocking = false;
	unsigned	minDex = 0;
	unsigned	maxDex = NUM_TEST_PARAMS-1;
	bool		doPause = false;

	extern char *optarg;
	int arg;
	while ((arg = getopt(argc, argv, "t:bpqh")) != -1) {
		switch (arg) {
			case 't':
				minDex = maxDex = atoi(optarg);
				if(minDex > (NUM_TEST_PARAMS - 1)) {
					printf("***max test number is %u.\n", (unsigned)NUM_TEST_PARAMS);
					exit(1);
				}
				break;
			case 'q':
				quiet = true;
				break;
			case 'b':
				nonBlocking = true;
				break;
			case 'p':
				doPause = true;
				break;
			default:
				usage(argv);
		}
	}
	if(optind != argc) {
		usage(argv);
	}

#if NO_SERVER
# if DEBUG
    securityd_init(NULL);
# endif
#endif

	#if IGNORE_SIGPIPE
	signal(SIGPIPE, sigpipe);
	#endif

	//testStartBanner("sslEcdsa", argc, argv);

	if(doPause) {
		fpurge(stdin);
		printf("Pausing at top of loop; CR to continue: ");
		fflush(stdout);
		getchar();
	}

	for(unsigned dex=minDex; dex<=maxDex; dex++) {
		const EcdsaTestParams *testParams = &ecdsaTestParams[dex];
		if(!quiet) {
			printf("[%u]: ", dex);
			dumpParams(testParams);
		}
		ourRtn = doSslPing(testParams, quiet, nonBlocking);
		if(ourRtn) {
            //printf("** Test %u failed **\n", dex);
			//if(testError(quiet)) {
			//  break;
			//}
		}
	}

	if(doPause) {
		fpurge(stdin);
		printf("Pausing at end of loop; CR to continue: ");
		fflush(stdout);
		getchar();
	}

	if(!quiet) {
		if(ourRtn == 0) {
			printf("===== sslEcdsa test PASSED =====\n");
		}
		else {
			printf("****sslEcdsa test FAILED\n");
		}
	}

	return ourRtn;
}