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