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