1 
2 /** Small helpers for openssl
3  *
4  * This module contains all the OpenSSL related helpers to wrap
5  * functionality of the D language binding provided by the dub module
6  * 'openssl'.
7  *
8  * See: https://github.com/D-Programming-Deimos/openssl
9  */
10 module acme.openssl_helpers;
11 
12 import deimos.openssl.conf;
13 import deimos.openssl.evp;
14 import deimos.openssl.err;
15 import deimos.openssl.pem;
16 import deimos.openssl.x509;
17 import deimos.openssl.x509v3;
18 
19 import std.conv;
20 import std.string;
21 import std.typecons;
22 
23 import acme.exception;
24 
25 /* ----------------------------------------------------------------------- */
26 
27 /** Get the contents of a big number as string
28  *
29  * Param:
30  *  bn - pointer to a big number structure
31  * Returns:
32  *  a string representing the BIGNUM
33  */
34 string getBigNumber(BIGNUM* bn)
35 {
36 	BIO * bio = BIO_new(BIO_s_mem());
37 	scope(exit) BIO_free(bio);
38 	BN_print(bio, bn);
39 	char[2048] buffer;
40 	auto rc = BIO_gets(bio, buffer.ptr, buffer.length);
41 	auto num = buffer[0..rc].to!string;
42 	return num;
43 }
44 
45 /** Get the content bytes of a big number as string
46  *
47  * Param:
48  *  bn - pointer to a big number structure
49  * Returns:
50  *  a string representing the BIGNUM
51  */
52 ubyte[] getBigNumberBytes(const BIGNUM* bn)
53 {
54 	/* Get number of bytes to store a BIGNUM */
55 	int numBytes = BN_num_bytes(bn);
56 	ubyte[] buffer;
57 	buffer.length = numBytes;
58 
59 	/* Copy bytes of BIGNUM to our buffer */
60 	BN_bn2bin(bn, buffer.ptr);
61 
62 	return buffer;
63 }
64 
65 
66 /* ----------------------------------------------------------------------- */
67 
68 /** Export BIO contents as an array of chars
69  *
70  * Param:
71  *   bio - pointer to a BIO structure
72  * Returns:
73  *   An array of chars representing the BIO structure
74  */
75 char[] toVector(BIO * bio)
76 {
77 	enum buffSize = 1024;
78 	char[buffSize] buffer;
79 	char[] rc;
80 
81 	int count = 0;
82 	do
83 	{
84 		count = BIO_read(bio, buffer.ptr, buffer.length);
85 		if (count > 0)
86 		{
87 			rc ~= buffer[0..count];
88 		}
89 	}
90 	while (count > 0);
91 
92 	return rc;
93 }
94 
95 /** Export BIO contents as an array of immutable chars (string)
96  *
97  * Param:
98  *   bio - pointer to a BIO structure
99  * Returns:
100  *   An array of immutable chars representing the BIO structure
101  */
102 string toString(BIO *bio)
103 {
104 	char[] v = toVector(bio);
105 	return to!string(v);
106 }
107 
108 /* ----------------------------------------------------------------------- */
109 
110 /** Encode data as Base64
111  *
112  * We use openssl to do this since we're already linking to it. As an
113  * alternative we could also use the phobos routines.
114  *
115  * Params:
116  *  t - data to encode as base64
117  * Returns:
118  *  An array of chars with the base64 encoded data.
119  */
120 char[] base64Encode(T)(T t)
121 	if ( is(T : string) || is(T : char[]) || is(T : ubyte[]))
122 {
123 	BIO * bio = BIO_new(BIO_s_mem());
124 	BIO * b64 = BIO_new(BIO_f_base64());
125 
126 	// OpenSSL inserts new lines by default to make it look like PEM format.
127 	// Turn that off.
128 	BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
129 
130 	BIO_push(b64, bio);
131 	if (BIO_write(b64, cast(void*)(t.ptr), t.length.to!int) < 0 ||
132 		BIO_flush(b64) < 0)
133 	{
134 		throw new AcmeException("Can't encode data as base64.");
135 	}
136 	return toVector(bio);
137 }
138 
139 /** Encode data as URl-safe Base64
140  *
141  * We need url safe base64 encoding and openssl only gives us regular
142  * base64, so we convert it here. Also trim trailing '=' from data
143  * (see RFC).
144  *
145  * The following replacements are done:
146  *  * '+' is converted to '-'
147  *  * '/' is converted to '_'
148  *  * '=' terminates the output at this point, stripping all '=' chars
149  *
150  * Params:
151  *  t - data to encode as base64
152  * Returns:
153  *  An array of chars with the base64 encoded data.
154  */
155 char[] base64EncodeUrlSafe(T)(T t)
156 	if ( is(T : string) || is(T : char[]) || is(T : ubyte[]))
157 {
158 	/* Do a Standard Base64 Encode */
159 	char[] s = base64Encode(t);
160 
161 	/* Do the replacements */
162 	foreach (i, ref v; s)
163 	{
164 		     if (s[i] == '+') { s[i] = '-'; }
165 		else if (s[i] == '/') {	s[i] = '_';	}
166 		else if (s[i] == '=') {	s.length = i; break; }
167 	}
168 	return s;
169 }
170 
171 /** Encode BIGNUM data as URl-safe Base64
172  *
173  * We need url safe base64 encoding and openssl only gives us regular
174  * base64, so we convert it here. Also trim trailing '=' from data
175  * (see RFC).
176  *
177  * The following replacements are done:
178  *  * '+' is converted to '-'
179  *  * '/' is converted to '_'
180  *  * '=' terminates the output at this point, stripping all '=' chars
181  *
182  * Params:
183  *  bn - pointer to BIGNUM to encode as base64
184  * Returns:
185  *  An array of chars with the base64 encoded data.
186  */
187 char[] base64EncodeUrlSafe(const BIGNUM* bn)
188 {
189 	/* Get contents bytes of a BIGNUM */
190 	ubyte[] buffer = getBigNumberBytes(bn);
191 
192 	/* Encode the buffer as URL-safe base64 string */
193 	return base64EncodeUrlSafe(buffer);
194 }
195 
196 /** Calculate the SHA256 of a string
197  *
198  * We use openssl to do this since we're already linking to it. We could
199  * also use functions from the phobos library.
200  *
201  * Param:
202  *  s - string to calculate hash from
203  * Returns:
204  *  ubyte[SHA256_DIGEST_LENGTH] for the hash
205  */
206 ubyte[SHA256_DIGEST_LENGTH] sha256Encode(const char[] s)
207 {
208 	ubyte[SHA256_DIGEST_LENGTH] hash;
209 	SHA256_CTX sha256;
210 	if (!SHA256_Init(&sha256) ||
211 		!SHA256_Update(&sha256, s.ptr, s.length) ||
212 		!SHA256_Final(hash.ptr, &sha256))
213 	{
214 		throw new AcmeException("Error hashing string data");
215 	}
216 	return hash;
217 }
218 
219 /** Convert certificate from DER format to PEM format
220  *
221  * Params:
222  *   der - DER encoded certificate
223  * Returns:
224  *   a PEM-encoded certificate
225  */
226 string convertDERtoPEM(const char[] der)
227 {
228 	/* Write DER to BIO buffer */
229 	BIO* derBio = BIO_new(BIO_s_mem());
230 	BIO_write(derBio, cast(const(void)*)der.ptr, der.length.to!int);
231 
232 	/* Add conversion filter */
233 	X509* x509 = d2i_X509_bio(derBio, null);
234 
235 	/* Write DER through filter to as PEM to other BIO buffer */
236 	BIO* pemBio = BIO_new(BIO_s_mem());
237 	PEM_write_bio_X509(pemBio, x509);
238 
239 	/* Output data as data string */
240 	return toString(pemBio);
241 }
242 
243 extern(C) ASN1_TIME * C_X509_get_notAfter(const char* certPtr, int certLen);
244 
245 /** Extract expiry date from a PEM encoded Zertificate
246  *
247  * Params:
248  *  cert - PEM encoded certificate to query
249  *  extractor - function or delegate process an ASN1_TIME* argument.
250  */
251 T extractExpiryData(T, alias extractor)(const(char[]) cert)
252 {
253 	ASN1_TIME * t = C_X509_get_notAfter(cert.ptr, cert.length.to!int);
254 /+
255 	BIO* bio = BIO_new(BIO_s_mem());
256 	if (BIO_write(bio, cast(const(void)*) cert.ptr, cert.length.to!int) <= 0)
257 	{
258 		throw new AcmeException("Can't write PEM data to BIO struct.");
259 	}
260 	X509* x509 = PEM_read_bio_X509(bio, null, null, null);
261 
262 	ASN1_TIME * t = X509_get_notAfter(x509);
263 +/
264 	T rc = extractor(t);
265 	return rc;
266 }
267 
268 /* ----------------------------------------------------------------------- */
269 
270 /// Return tuple of makeCertificateSigningRequest
271 alias tupleCsrPkey = Tuple!(string, "csr", string, "pkey");
272 
273 /** Create a CSR with our domains
274  *
275  * Params:
276  *   domainNames - Names of domains, first element is subject of cert
277  * Returns:
278  *   tupleCsrPkey containing CSr and PKey
279  */
280 tupleCsrPkey makeCertificateSigningRequest(string[] domainNames)
281 {
282 	if (domainNames.length < 1) {
283 		throw new AcmeException("We need at least one domain name.");
284 	}
285 
286 	BIGNUM* bn = BN_new();
287 	if (!BN_set_word(bn, RSA_F4)) {
288 		throw new AcmeException("Can't set word.");
289 	}
290 	EVP_PKEY * pkey;
291 	pkey = EVP_PKEY_new();
292 
293 	RSA* rsa = RSA_new();
294 	enum bits = 2048;
295 	if (!RSA_generate_key_ex(rsa, bits, bn, null))
296 	{
297 		throw new AcmeException("Can't generate key.");
298 	}
299 	EVP_PKEY_assign_RSA(pkey, rsa);
300 
301 	/* Set first element of domainNames as cert CN subject */
302 	X509_REQ* x509_req = X509_REQ_new();
303 	auto name = domainNames[0];
304 
305 	X509_REQ_set_version(x509_req, 1);
306 	X509_REQ_set_pubkey(x509_req, pkey);
307 
308 	X509_NAME* cn = X509_REQ_get_subject_name(x509_req);
309 	assert (cn !is null, "Can get X509_REQ_get_subject_name");
310 	auto rc_cn = X509_NAME_add_entry_by_txt(
311 				cn,
312 				"CN",
313 				MBSTRING_ASC,
314 				cast(const ubyte*)(name.toStringz),
315 				-1, -1, 0);
316 	if (!rc_cn)
317 	{
318 		throw new AcmeException("Can't add CN entry.");
319 	}
320 
321 	/* Add other domainName as extension */
322 	if (domainNames.length > 1)
323 	{
324 		// We have multiple Subject Alternative Names
325 		auto extensions = sk_X509_EXTENSION_new_null();
326 		if (!extensions)
327 		{
328 			throw new AcmeException("Unable to allocate Subject Alternative Name extensions");
329 		}
330 
331 		foreach (i, ref v ; domainNames)
332 		{
333 			auto cstr = ("DNS:" ~ v).toStringz;
334 			auto nid = X509V3_EXT_conf_nid(null, null, NID_subject_alt_name, cast(char*)cstr);
335 			if (!sk_X509_EXTENSION_push(extensions, nid))
336 			{
337 				throw new AcmeException("Unable to add Subject Alternative Name to extensions");
338 			}
339 		}
340 
341 		if (X509_REQ_add_extensions(x509_req, extensions) != 1) {
342 			throw new AcmeException("Unable to add Subject Alternative Names to CSR");
343 		}
344 
345 		sk_X509_EXTENSION_pop_free(extensions, &X509_EXTENSION_free);
346 	}
347 
348 	// EVP_PKEY* key = EVP_PKEY_new();
349 	// if (!EVP_PKEY_assign_RSA(key, rsa))
350 	// {
351 	// 	throw new AcmeException("Can't set RSA key.");
352 	// }
353 	//rsa = null;     // rsa will be freed when key is freed.
354 
355 	BIO* keyBio = BIO_new(BIO_s_mem());
356 	if (PEM_write_bio_PrivateKey(keyBio, pkey, null, null, 0, null, null) != 1) {
357 		throw new AcmeException("Can't copy private key to BIO.");
358 	}
359 	string privateKey = toString(keyBio);
360 
361 	if (!X509_REQ_set_pubkey(x509_req, pkey)) {
362 		throw new AcmeException("Can't set subkey.");
363 	}
364 
365 	if (!X509_REQ_sign(x509_req, pkey, EVP_sha256())) {
366 		throw new AcmeException("Can't sign.");
367 	}
368 
369 	BIO* reqBio = BIO_new(BIO_s_mem());
370 	if (i2d_X509_REQ_bio(reqBio, x509_req) < 0)	{
371 		throw new AcmeException("Can't setup sign request");
372 	}
373 
374 	tupleCsrPkey rc = tuple(base64EncodeUrlSafe(toVector(reqBio)).to!string, privateKey);
375 	return rc;
376 }
377 
378 /* ----------------------------------------------------------------------- */
379 
380 /** Sign a given string with an SHA256 hash
381  *
382  * Param:
383  *  s - string to sign
384  *  privateKey - signing key to use
385  *
386  *  Returns:
387  *    A SHA256 signature on provided data
388  * See: https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
389  */
390 char[] signDataWithSHA256(char[] s, EVP_PKEY* privateKey)
391 {
392 	size_t signatureLength = 0;
393 
394 	EVP_MD_CTX* context = EVP_MD_CTX_create();
395 	const EVP_MD * sha256 = EVP_get_digestbyname("SHA256");
396 	if ( !sha256 ||
397 		EVP_DigestInit_ex(context, sha256, null) != 1 ||
398 		EVP_DigestSignInit(context, null, sha256, null, privateKey) != 1 ||
399 		EVP_DigestSignUpdate(context, s.toStringz, s.length) != 1 ||
400 		EVP_DigestSignFinal(context, null, &signatureLength) != 1)
401 	{
402 		throw new AcmeException("Error creating SHA256 digest");
403 	}
404 
405 	ubyte[] signature;
406 	signature.length = signatureLength;
407 	if (EVP_DigestSignFinal(context, signature.ptr, &signatureLength) != 1)
408 	{
409 		throw new AcmeException("Error creating SHA256 digest in final signature");
410 	}
411 
412 	return base64EncodeUrlSafe(signature);
413 }
414 
415 version (HAS_WORKING_SSL)
416 {
417 	/** Initialize SSL library
418 	 *
419 	 * Do any kind of initialization here.
420 	 * Returns:
421 	 *   true or false
422 	 */
423 	bool SSL_OpenLibrary()
424 	{
425 		/* Load the human readable error strings for libcrypto */
426 		OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, null);
427 
428 		/* Load all digest and cipher algorithms */
429 		//OpenSSL_add_all_algorithms(); // Is a macro for
430 		OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
431 						  | OPENSSL_INIT_ADD_ALL_DIGESTS
432 						  | OPENSSL_INIT_LOAD_CONFIG, null);
433 		return true;
434 	}
435 
436 	/** Teardown SSL library
437 	 *
438 	 * Reverse anything done in SSL_OpenLibrary().
439 	 */
440 	void SSL_CloseLibrary()
441 	{
442 		/* Clean up */
443 		OPENSSL_cleanup();
444 	}
445 
446 	/* http://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl
447 	 * http://www.codepool.biz/how-to-use-openssl-to-generate-x-509-certificate-request.html
448 	 */
449 
450 	/** Make a x509 pkey
451 	 *
452 	 * Create a RSA private keys with 2048 bits
453 	 * Returns: pointer to EVP_PKEY structure
454 	 * @internal
455 	 */
456 	EVP_PKEY* SSL_x509_make_pkey(int bits = 4096)
457 	{
458 		EVP_PKEY * pkey;
459 		pkey = EVP_PKEY_new();
460 		RSA * rsa;
461 		rsa = RSA_generate_key(
462 				bits,   /* number of bits for the key - 2048 is a sensible value */
463 				RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
464 				null,   /* callback - can be null if we aren't displaying progress */
465 				null    /* callback argument - not needed in this case */
466 			);
467 		EVP_PKEY_assign_RSA(pkey, rsa);
468 		return pkey;
469 	}
470 
471 	/** Add extension using V3 code: we can set the config file as null,
472 	 * because we wont reference any other sections.
473 	 * @param cert pointer to X509 cert
474 	 * @param nid Extention ID
475 	 * @param value value of nid
476 	 * Returns: bool_t: 0 == False, !=0 True
477 	 */
478 	private bool add_ext(X509* cert, int nid, char[] value)
479 	{
480 		X509_EXTENSION *ex;
481 		X509V3_CTX ctx;
482 		/* This sets the 'context' of the extensions. */
483 		/* No configuration database */
484 		X509V3_set_ctx_nodb(&ctx);
485 		/* Issuer and subject certs: both the target since it is self signed,
486 		 * no request and no CRL
487 		 */
488 		X509V3_set_ctx(&ctx, cert, cert, null, null, 0);
489 		ex = X509V3_EXT_conf_nid(cast(LHASH_OF!(CONF_VALUE)*)null, &ctx, nid, cast(char*)value.toStringz);
490 		if (!ex)
491 			return false;
492 
493 		X509_add_ext(cert, ex, -1);
494 		X509_EXTENSION_free(ex);
495 		return true;
496 	}
497 
498 	/** Make a x509 cert
499 	 *
500 	 * Creates a X509 Zertifikate with direkt library calls.
501 	 * @param pkey pointer to pkey struct to store
502 	 * @param dev_serial pointer to device serial string
503 	 * Returns: pointer to selfsigned x509 structure
504 	 * @todo Add leica fields to x509 cert
505 	 * @todo Add error handling to X509 Cert creation code.
506 	 * @internal
507 	 */
508 	X509* SSL_x509_make_cert(EVP_PKEY* pkey, char[] dev_serial)
509 	{
510 		X509 * x509;
511 		x509 = X509_new();
512 
513 		ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
514 
515 		X509_gmtime_adj(X509_get_notBefore(x509), 0); // now!
516 		X509_gmtime_adj(X509_get_notAfter(x509), 50 * 31536000L); // 99 years
517 
518 		X509_set_pubkey(x509, pkey);
519 
520 		X509_NAME * name;
521 		name = X509_get_subject_name(x509);
522 
523 		X509_NAME_add_entry_by_txt(name, cast(char*)("ST".ptr),  MBSTRING_ASC, cast(ubyte*)("Niedersachsen".ptr), -1, -1, 0);
524 		X509_NAME_add_entry_by_txt(name, cast(char*)("L".ptr),  MBSTRING_ASC, cast(ubyte*)("Hannover".ptr), -1, -1, 0);
525 		//OU Filed BREAKS precessing of CSR on LCG. Also see CON-289 - keep info at minimum for reduced size
526 		//X509_NAME_add_entry_by_txt(name, "OU",  MBSTRING_ASC, (unsigned char *)"IT", -1, -1, 0);
527 		X509_NAME_add_entry_by_txt(name, cast(char*)("O".ptr),  MBSTRING_ASC, cast(ubyte*)("Vahanus ".ptr), -1, -1, 0);
528 		X509_NAME_add_entry_by_txt(name, cast(char*)("C".ptr),  MBSTRING_ASC, cast(ubyte*)("DE".ptr), -1, -1, 0);
529 		X509_NAME_add_entry_by_txt(name, cast(char*)("CN".ptr), MBSTRING_ASC, cast(ubyte*)(dev_serial.toStringz), -1, -1, 0);
530 
531 		X509_set_issuer_name(x509, name);
532 
533 		/* Add various extensions: standard extensions */
534 		add_ext(x509, NID_basic_constraints, "critical,CA:TRUE".dup);
535 		add_ext(x509, NID_key_usage, "critical,keyCertSign,cRLSign".dup);
536 
537 		add_ext(x509, NID_subject_key_identifier, "hash".dup);
538 
539 		/* Some Netscape specific extensions */
540 		add_ext(x509, NID_netscape_cert_type, "sslCA".dup);
541 
542 		add_ext(x509, NID_netscape_comment, "example comment extension".dup);
543 
544 		version(none) {
545 			/* Maybe even add our own extension based on existing */
546 			{
547 				int nid;
548 				nid = OBJ_create("1.2.3.4", "MyAlias", "My Test Alias Extension");
549 				X509V3_EXT_add_alias(nid, NID_netscape_comment);
550 				add_ext(x509, nid, "example comment alias");
551 			}
552 		}
553 		X509_sign(x509, pkey, EVP_sha1());
554 		return x509;
555 	}
556 
557 	/** Make a x509 CSR (cert signing request)
558 	 * @param pkey pointer to pkey struct to store
559 	 * @param dev_serial pointer to device serial string
560 	 * Returns: pointer to X509_REQ structure
561 	 */
562 	X509_REQ* SSL_x509_make_csr(EVP_PKEY* pkey, string[] domainNames)
563 	{
564 		assert(domainNames.length >= 1, "No domain names given.");
565 		auto cnStr = domainNames[0].toStringz;
566 		string[] extStrs; extStrs.length = domainNames.length - 1;
567 
568 		X509_REQ * x509_req;
569 		x509_req = X509_REQ_new();
570 		assert(x509_req.req_info !is null, "The allocated X509_REQ* has req_info member set to NULL. This shouldn't be.");
571 
572 		X509_REQ_set_version(x509_req, 1);
573 		X509_REQ_set_pubkey(x509_req, pkey);
574 
575 		X509_NAME * name;
576 		name = X509_REQ_get_subject_name(x509_req);
577 		assert (name !is null, "Can't read the req subject name struct.");
578 
579 		/* Setup some fields for the CSR */
580 		version(none) {
581 			X509_NAME_add_entry_by_txt(name, cast(char*)("ST".ptr),  MBSTRING_ASC, cast(ubyte*)("Niedersachsen".ptr), -1, -1, 0);
582 			X509_NAME_add_entry_by_txt(name, cast(char*)("L".ptr),  MBSTRING_ASC, cast(ubyte*)("Hannover".ptr), -1, -1, 0);
583 			//OU Filed BREAKS precessing of CSR on LCG. Also see CON-289 - keep info at minimum for reduced size
584 			//X509_NAME_add_entry_by_txt(name, "OU",  MBSTRING_ASC, (unsigned char *)"IT", -1, -1, 0);
585 			X509_NAME_add_entry_by_txt(name, cast(char*)("O".ptr),  MBSTRING_ASC, cast(ubyte*)("Vahanus ".ptr), -1, -1, 0);
586 			X509_NAME_add_entry_by_txt(name, cast(char*)("C".ptr),  MBSTRING_ASC, cast(ubyte*)("DE".ptr), -1, -1, 0);
587 			X509_NAME_add_entry_by_txt(name, cast(char*)("CN".ptr), MBSTRING_ASC, cast(ubyte*)(dev_serial.toStringz), -1, -1, 0);
588 		}
589 		X509_NAME_add_entry_by_txt(name, cast(char*)("CN".ptr), MBSTRING_ASC, cast(ubyte*)(cnStr), -1, -1, 0);
590 		/* Add other domainName as extension */
591 		if (domainNames.length > 1)
592 		{
593 			// We have multiple Subject Alternative Names
594 			auto extensions = sk_X509_EXTENSION_new_null();
595 			if (extensions is null) {
596 				throw new AcmeException("Unable to allocate Subject Alternative Name extensions");
597 			}
598 			foreach (i, ref v ; domainNames[1..$])
599 			{
600 				auto cstr = ("DNS:" ~ v).toStringz;
601 				auto nid = X509V3_EXT_conf_nid(null, null, NID_subject_alt_name, cast(char*)cstr);
602 				if (!sk_X509_EXTENSION_push(extensions, nid)) {
603 					throw new AcmeException("Unable to add Subject Alternative Name to extensions");
604 				}
605 			}
606 			if (X509_REQ_add_extensions(x509_req, extensions) != 1) {
607 				throw new AcmeException("Unable to add Subject Alternative Names to CSR");
608 			}
609 			sk_X509_EXTENSION_pop_free(extensions, &X509_EXTENSION_free);
610 		}
611 
612 		/* Code below BREAKS acception of CSR at LCG. Also see CON-289 - minimize cert size, leave it out. */
613 		version(hasExtentions) {
614 			STACK_OF(X509_EXTENSION) *exts = sk_X509_EXTENSION_new_null();
615 
616 			// # Extensions for client certificates (`man x509v3_config`).
617 			// basicConstraints = CA:FALSE
618 			// nsCertType = client, email
619 			// nsComment = "OpenSSL Generated Client Certificate"
620 			// subjectKeyIdentifier = hash
621 			// authorityKeyIdentifier = keyid,issuer
622 			// keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
623 			// extendedKeyUsage = clientAuth, emailProtection
624 
625 			/* Add various extensions: standard extensions */
626 			add_req_ext(exts, NID_basic_constraints, "CA:FALSE");
627 			add_req_ext(exts, NID_key_usage, "critical, nonRepudiation, digitalSignature, keyEncipherment");
628 			add_req_ext(exts, NID_ext_key_usage, "clientAuth, emailProtection");
629 			add_req_ext(exts, NID_subject_key_identifier, "hash");
630 			add_req_ext(exts, NID_authority_key_identifier, "keyid,issuer");
631 
632 			/* Some Netscape specific extensions */
633 			add_req_ext(exts, NID_netscape_cert_type, "client, email");
634 			add_req_ext(exts, NID_netscape_comment, "OpenSSL Generated Client Certificate");
635 
636 			X509_REQ_add_extensions(x509_req, exts);
637 
638 			sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
639 		}
640 
641 		/* Sign the CSR with our PKEY */
642 		X509_REQ_sign(x509_req, pkey, EVP_sha1());
643 		return x509_req;
644 	}
645 
646 
647 }
648 else
649 {
650 	/* Sick of broken D binding for OpenSSL, I used the stub approach -
651 	 * just compile it as C and call the functions */
652 
653 	extern(C) bool C_SSL_OpenLibrary();
654 	bool SSL_OpenLibrary()
655 	{
656 		return C_SSL_OpenLibrary();
657 	}
658 	extern(C) void C_SSL_CloseLibrary();
659 	void SSL_CloseLibrary()
660 	{
661 		C_SSL_CloseLibrary();
662 	}
663 	extern(C) EVP_PKEY* C_SSL_x509_make_pkey(int bits);
664 	EVP_PKEY* SSL_x509_make_pkey(int bits)
665 	{
666 		return C_SSL_x509_make_pkey(bits);
667 	}
668 	extern(C) bool C_add_ext(X509* cert, int nid, char* value);
669 	private bool add_ext(X509* cert, int nid, char[] value)
670 	{
671 		return C_add_ext(cert, nid, cast(char*)(value.toStringz));
672 	}
673 	extern(C) X509* C_SSL_x509_make_cert(EVP_PKEY* pkey, char* subject);
674 	X509* SSL_x509_make_cert(EVP_PKEY* pkey, char[] subject)
675 	{
676 		return C_SSL_x509_make_cert(pkey, cast(char*)(subject.toStringz));
677 	}
678 	extern(C) X509_REQ* C_SSL_x509_make_csr(EVP_PKEY* pkey, char** domainNames, int domainNamesLength);
679 	X509_REQ* SSL_x509_make_csr(EVP_PKEY* pkey, string[] domainNames)
680 	{
681 		char*[] C_domainNames;
682 		C_domainNames.length =  domainNames.length;
683 		foreach(i, ref v; domainNames) C_domainNames[i] = cast(char*)v.toStringz;
684 		return C_SSL_x509_make_csr(pkey, cast(char**)(C_domainNames.ptr), domainNames.length.to!int);
685 	}
686 }
687 
688 
689 /++
690 /* Code below commented out, obsolete function */
691 /** Add extension using V3 code: we can set the config file as null
692  * because we wont reference any other sections.
693  * @param sk pointer to STACK_OF(X509_EXTENSION
694  * @param nid Extention ID
695  * @param value value of nid
696  * Returns: bool_t: 0 == False, !=0 True
697  * @internal
698  */
699 static
700 bool_t add_req_ext(STACK_OF(X509_EXTENSION) *sk, int nid, cstring_p value)
701 {
702 	X509_EXTENSION *ex;
703 	ex = X509V3_EXT_conf_nid(null, null, nid, value);
704 	if (!ex)
705 		return False;
706 	sk_X509_EXTENSION_push(sk, ex);
707 	return True;
708 }
709 ++/
710 
711 /** Get a CSR as PEM string */
712 char[] SSL_x509_get_PEM(X509_REQ* csr)
713 {
714 	BUF_MEM* mem;
715 	BIO* bio = BIO_new(BIO_s_mem());
716 	PEM_write_bio_X509_REQ(bio, csr);
717 	BIO_get_mem_ptr(bio, &mem);
718 	if (null == mem)  {
719 		return null;
720 	}
721 	//cstring_p rs = strndup(mem->data, mem->length);
722 	char[] rs = mem.data[0..mem.length].dup;
723 	BIO_free(bio);
724 	return rs;
725 }
726 
727 /** Read a x509 pkey pem string from memory
728  */
729 EVP_PKEY* SSL_x509_read_pkey_memory(const char[] pkeyString, RSA** rsaRef = null)
730 {
731 	auto cstr = cast(void*)pkeyString.toStringz;
732 	EVP_PKEY* privateKey = EVP_PKEY_new();
733 
734 	BIO* bio = BIO_new_mem_buf(cstr, -1);
735 	RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, null, null, null);
736 	if (!rsa) {
737 		throw new AcmeException("Unable to read private key");
738 	}
739 	// rsa will get freed when privateKey_ is freed
740 	auto rc = !EVP_PKEY_assign_RSA(privateKey, rsa);
741 	if (rc) {
742 		throw new AcmeException("Unable to assign RSA to private key");
743 	}
744 	if (rsaRef) *rsaRef = rsa;
745 	return privateKey;
746 }
747 
748 /** Save a x509 pkey to a file
749  * @param path pathname of file to write
750  * @param pkey pointer to pkey struct to store
751  * Returns: return value of PEM_write_PrivateKey()
752  * @internal
753  */
754 int SSL_x509_write_pkey(char[] path, EVP_PKEY * pkey)
755 {
756 	import core.stdc.stdio;
757 	int rc = -1;
758 	FILE * f;
759 	if (path is null) path = "key.pem".dup;
760 	f = fopen(cast(char*)(path.toStringz), cast(char*)("wb".toStringz));
761 	if (f !is null) {
762 		alias cbt = extern(C) int function(char*, int, int, void*);
763 		rc = PEM_write_PrivateKey(
764 		        f,                  /* write the key to the file we've opened */
765 		        pkey,               /* our key from earlier */
766 		        EVP_des_ede3_cbc(), /* default cipher for encrypting the key on disk */
767 		        cast(ubyte*)("replace_me".ptr),                /* passphrase required for decrypting the key on disk */
768 		        10,                                            /* length of the passphrase string */
769 		        cast(cbt)null,      /* callback for requesting a password */
770 		        cast(void*)null     /* data to pass to the callback */
771 		    );
772 		fclose(f);
773 	}
774 	return rc;
775 }
776 
777 /** Read a x509 pkey from a file
778  * @param path pathname of file to read
779  * Returns: pointer to EVP_PKEY, return value of PEM_write_PrivateKey()
780  * @internal
781  */
782 EVP_PKEY * SSL_x509_read_pkey(char[] path)
783 {
784 	import core.stdc.stdio;
785 	EVP_PKEY * pkey;
786 	pkey = EVP_PKEY_new();
787 	FILE * f;
788 	if (path is null) path = "key.pem".dup;
789 	f = fopen(cast(char*)(path.toStringz), cast(char*)("rb".ptr));
790 	if (f !is null) {
791 		pkey = PEM_read_PrivateKey(
792 		        f,                  /* read the key to the file we've opened */
793 		        &pkey,              /* our key from earlier */
794 		        null,               /* callback for requesting a password */
795 		        null                /* data to pass to the callback */
796 		    );
797 		fclose(f);
798 	}
799 	return pkey;
800 }
801 
802 /** Save a x509 cert to a file
803  * @param path pathname of file to write
804  * @param x509 pointer to x509 struct to store
805  * Returns: return value of PEM_write_X509()
806  * @internal
807  */
808 int  SSL_x509_write_cert(char[] path, X509* x509)
809 {
810 	import core.stdc.stdio;
811 	int rc = -1;
812 	FILE * f;
813 	if (path is null) path = "cert.pem".dup;
814 	f = fopen(cast(char*)(path.toStringz), cast(char*)("wb".ptr));
815 	if (f !is null) {
816 		rc = PEM_write_X509(
817 		        f,   /* write the certificate to the file we've opened */
818 		        x509 /* our certificate */
819 		    );
820 		fclose(f);
821 	}
822 	return rc;
823 }
824 
825 /* ------------------------------------------------------------------------ */
826 
827 /** Create a SSL private key
828  *
829  * This functions creates an EVP_PKEY with 2048 bits. It's returned as
830  * PEM encoded text.
831  *
832  * Returns:
833  * 		pointer to pem encoded string containing EVP_PKEY private key.
834  */
835 char[] openSSL_CreatePrivateKey(int bits = 4096)
836 {
837 	import std.stdio;
838 	//writeln("Create a SSL pKey.");
839 	char[] rs;
840 
841 	EVP_PKEY * pkey = SSL_x509_make_pkey(bits);
842 	if (null == pkey) {
843 		stderr.writeln("Can't create a pKey");
844 	} else {
845 		BIO *bio = BIO_new(BIO_s_mem());
846 		if (null == bio) {
847 			stderr.writeln("Can't create a BIO");
848 		} else {
849 			alias cbt = extern(C) int function(char*, int, int, void*);
850 			int rc = PEM_write_bio_PrivateKey(bio, pkey,
851 				cast(const(evp_cipher_st)*)null,
852 				cast(ubyte*)null, 0,
853 				cast(cbt)null, cast(void*)null);
854 			if (!rc) {
855 				stderr.writeln("Can't write pKEY to BIO");
856 			} else {
857 				BUF_MEM *mem;
858 				BIO_get_mem_ptr(bio, &mem);
859 				if (null == mem)  {
860 					stderr.writeln("Can't get pointer to BUF_MEM from BIO");
861 				} else {
862 					if (mem.data !is null)
863 						rs = mem.data[0..mem.length];
864 					if (rs is null || rs.empty)  {
865 						stderr.writeln("Can't get data from BIO");
866 					} else {
867 						rs = (rs).dup;
868 					}
869 				}
870 			}
871 			BIO_free(bio);
872 		}
873 		EVP_PKEY_free(pkey);
874 	}
875 	return rs;
876 }
877 unittest {
878 	import std.stdio : writeln, writefln, stdout, stderr;
879 	import std.datetime.stopwatch : benchmark;
880 
881 	/* Test Key Generation */
882 	writeln("Testing the SSL routines ported from C");
883 	writeln("--- Create a private key ---");
884 	stdout.flush;
885 	char[] myPKey = openSSL_CreatePrivateKey();
886 	writeln("Got the following from library:\n", myPKey);
887 	stdout.flush;
888 
889 	/* Benchmark Key Generation */
890 	writeln("--- Benchmark creating a private key ---");
891 	stdout.flush;
892 	void benchCreateKeyStub() {
893 		char[] tmp = openSSL_CreatePrivateKey();
894 		assert(tmp !is null && !tmp.empty, "Empty private key.");
895 	}
896 	auto dur = benchmark!(benchCreateKeyStub)(100);
897 	writeln("Benchmarking 100 calls, duration ", dur);
898 	stdout.flush;
899 }
900 
901 /** Create a SSL cert signing request from a pkey and a serial number
902  *
903  * This functions creates an CertificateSignRequest (CSR) with 2048 bits.
904  * It's returned as PEM encoded text.
905  *
906  * Params:
907  *   prkey - private key as PEM string
908  *   serial - same custom data, e.g. a serial number
909  * Returns:
910  *   ERROR: pointer to pem encoded string of CSR.
911  *   CORRECT: pointer to bas64url encoded DER data! See RFC.
912  */
913 char[] openSSL_CreateCertificateSignRequest(const char[] prkey, string[] domainNames)
914 {
915 	BIO *bio;
916 	int rc;
917 
918 	//HACK
919 	const char[] prkey2 = openSSL_CreatePrivateKey();
920 
921 	/* Get EVP_PKEY from PEM encoded string */
922 	EVP_PKEY* pkey;
923 	//pkey = SSL_x509_read_pkey_memory(prkey);
924 	pkey = SSL_x509_read_pkey_memory(prkey2);
925 
926 	/* Create CSR from private key and serial number */
927 
928 	X509_REQ* x509_req = SSL_x509_make_csr(pkey, domainNames);
929 	assert (x509_req !is null, "Returned empty cert req.");
930 
931 	/* Convert to PEM string */
932 //	auto rs = SSL_x509_get_PEM(x509_req);
933 	BIO* reqBio = BIO_new(BIO_s_mem());
934 	if (i2d_X509_REQ_bio(reqBio, x509_req) < 0)	{
935 		throw new AcmeException("Can't convert CSR to DER.");
936 	}
937 	char[] rs = cast(char[])base64EncodeUrlSafe(toVector(reqBio)).to!string;
938 	BIO_free(reqBio);
939 	EVP_PKEY_free(pkey);
940 	return rs;
941 }
942 unittest {
943 	import std.stdio : writeln, writefln, stdout, stderr;
944 	import std.datetime.stopwatch : benchmark;
945 
946 	/* Test Key Generation */
947 	writeln("Testing the CSR-creation routines ported from C");
948 	writeln("--- Create a private key ---");
949 	stdout.flush;
950 	char[] myPKey = openSSL_CreatePrivateKey();
951 	writeln("Got the following from library:\n", myPKey);
952 	stdout.flush;
953 	char[] myCSR = openSSL_CreateCertificateSignRequest(myPKey, [ "bodylove.myds.me" ]);
954 	writeln("Got the following CSR from library:\n", myCSR);
955 
956 	/* Benchmark CSR Generation */
957 	writeln("--- Benchmark creating a CSR ---");
958 	stdout.flush;
959 	void benchCreateCSRStub() {
960 		char[] tmp = openSSL_CreateCertificateSignRequest(myPKey, [ "bodylove.myds.me" ]);
961 		assert(tmp !is null && !tmp.empty, "Empty CSR.");
962 	}
963 	auto dur = benchmark!(benchCreateCSRStub)(100);
964 	writeln("Benchmarking 100 calls, duration ", dur);
965 	stdout.flush;
966 }
967 
968 /* ------------------------------------------------------------------------ */
969