1//
2//  unit_tests.m
3//  openssl
4//
5//  Created by J. Osborne on 1/25/10.
6//  Copyright 2010 Apple. All rights reserved.
7//
8
9#import "unit_tests.h"
10
11#include <stdarg.h>
12#include <err.h>
13#include <spawn.h>
14#include <openssl/bio.h>
15#include <openssl/ssl.h>
16#include <openssl/err.h>
17#include <crt_externs.h>
18
19static char *client_pem, *signed_pem, *url;
20static int port = 9001;
21
22@implementation unit_tests
23
24void (^comm_test)(BOOL cert_fail, BOOL handshake_fail) = NULL;
25void (^comm_teardown)(void) = NULL;
26
27static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
28{
29	fprintf(stderr, "Peer certificate verification callback fired, ctx->error %d, preverify_ok %d\n", ctx->error, preverify_ok);
30	return 0;
31}
32
33void comm_setup(id self)
34{
35	NSBundle* myBundle = [NSBundle bundleWithIdentifier:@"com.apple.OpenSSL098-unit-tests"];
36	
37	char *cert_file = strdup([[myBundle pathForResource:@"server" ofType:@"pem"] UTF8String]);
38	char *priv_key_file = strdup([[myBundle pathForResource:@"server" ofType:@"pem"] UTF8String]);
39	client_pem = strdup([[myBundle pathForResource:@"client" ofType:@"pem"] UTF8String]);
40	signed_pem = strdup([[myBundle pathForResource:@"signed" ofType:@"pem"] UTF8String]);
41	
42	STAssertTrue(NULL != cert_file, @"Can't setup test, missing cert_file");
43	STAssertTrue(NULL != priv_key_file, @"Can't setup test, missing priv_key_file");
44	STAssertTrue(NULL != client_pem, @"Can't setup test, missing client_pem");
45	STAssertTrue(NULL != signed_pem, @"Can't setup test, missing signed_pem");
46
47	SSL_library_init();
48	SSL_load_error_strings();
49	ERR_load_BIO_strings();
50	ERR_load_SSL_strings();
51	
52	__block SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
53	if (ctx == NULL) {
54		STFail(@"Failed to create SSL context");
55		return;
56	}
57	if (!SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM)) {
58		STFail(@"Failed to load certificate file %s", cert_file);
59		// if it is important to get these errors into the "real" failure log, look at funopen
60		ERR_print_errors_fp(stderr);
61		SSL_CTX_free(ctx);
62		return;
63	}
64	if (!SSL_CTX_use_PrivateKey_file(ctx, priv_key_file, SSL_FILETYPE_PEM))
65	{
66		STFail(@"Failed to load private key file %s", priv_key_file);
67		ERR_print_errors_fp(stdout);
68		SSL_CTX_free(ctx);
69		return;
70	}
71	// at this point we have loaded the certificate and private key
72	
73	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);
74	
75	__block BIO *bio = BIO_new_ssl(ctx, 0);
76	if (bio == NULL)
77	{
78		STFail(@"Failed to acquire I/O chain for SSL context");
79		ERR_print_errors_fp(stdout);
80		SSL_CTX_free(ctx);
81		return;
82	}
83	
84	char *portstr;
85	asprintf(&portstr, "%d", port);
86	asprintf(&url, "https://127.0.0.1:%d", port);
87	__block BIO *abio = BIO_new_accept(portstr);
88	BIO_set_bind_mode(abio, BIO_BIND_REUSEADDR);
89	free(portstr);
90
91	BIO_set_accept_bios(abio, bio);
92	if (BIO_do_accept(abio) <= 0)
93	{
94		STFail(@"Failed to listen on port %d", port);
95		ERR_print_errors_fp(stdout);
96		SSL_CTX_free(ctx);
97		BIO_free_all(bio);
98		BIO_free_all(abio);
99		return;
100	}
101	
102	comm_test = ^(BOOL cert_fail, BOOL handshake_fail) {
103		if (BIO_do_accept(abio) <= 0)
104		{
105			STFail(@"Failed to accept a connection");
106			ERR_print_errors_fp(stdout);
107			SSL_CTX_free(ctx);
108			BIO_free_all(bio);
109			BIO_free_all(abio);
110			return;
111		}
112		
113		BIO *out = BIO_pop(abio);
114
115		int handshake = BIO_do_handshake(out);
116		
117		SSL *ssl;
118		BIO_get_ssl(out, &ssl);
119		int result = SSL_get_verify_result(ssl);
120		if (result == X509_V_OK) {
121			STAssertFalse(cert_fail, @"Client certificate passed verification");
122		} else {
123			STAssertTrue(cert_fail, @"Client certificate failed verification -- did you set it to trusted in your keychain (security add-certificate client.crt; security add-trusted-cert client.crt)?");
124		}
125		
126		if (handshake <= 0)
127		{
128			STAssertTrue(handshake_fail, @"Handshake failed, resetting connection");
129			BIO_free_all(out);
130			return;
131		} else {
132			STAssertFalse(handshake_fail, @"Unexpected handshake success");
133		}
134		
135		char msg[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 34\r\n\r\n[+] Secure connection established\n";
136		BIO_puts(out, msg);
137		BIO_flush(out);
138		
139		BIO_free_all(out);
140	};
141	comm_test = Block_copy(comm_test);
142	
143	comm_teardown = ^{
144        BIO_free_all(abio);
145        SSL_CTX_free(ctx);
146	};
147	comm_teardown = Block_copy(comm_teardown);
148};
149
150+(void)tearDown
151{
152	if (comm_teardown) {
153		comm_teardown();
154	}
155}
156
157static void spawn_for_test(char *program, ...)
158{
159	const int m_args = 48;
160	int a_count;
161	char *args[m_args];
162	va_list ap;
163	
164	va_start(ap, program);
165	for(a_count = 0; a_count < m_args; a_count++) {
166		args[a_count] = va_arg(ap, char*);
167		if (args[a_count] == NULL) {
168			break;
169		}
170	}
171	if (a_count >= m_args) {
172		errx(3, "Too many arguments (%d max; program='%s')", m_args, program);
173	}
174	va_end(ap);
175	
176	pid_t pid;
177	int rc = posix_spawn(&pid, program, NULL, NULL, args, *_NSGetEnviron());
178	if (rc != 0) {
179		err(3, "spawn failed for program '%s' ", program);
180	}
181	
182	// Avoid zombies, but we don't really care about processes exit code
183	int ignored;
184	waitpid(pid, &ignored, 0);
185}
186
187-(void)testTrustedKeyExists
188{
189	if (!comm_test) {
190		comm_setup(self);
191	}
192	
193	if (comm_test) {
194		dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
195			spawn_for_test("/usr/bin/curl", "curl", "-s", "-k", "-E", signed_pem, url, NULL);
196		});
197		comm_test(NO, YES);
198	} else {
199		STFail(@"Couldn't set up test");
200	}
201}
202
203-(void)testTrustedKeyDoesntOveride
204{
205	if (!comm_test) {
206		comm_setup(self);
207	}
208
209	if (comm_test) {
210		dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
211			spawn_for_test("/usr/bin/curl", "curl", "-s", "-k", "-E", client_pem, url, NULL);
212		});
213		comm_test(YES, YES);
214	} else {
215		STFail(@"Couldn't set up test");
216	}
217}
218
219@end
220