1 module secured.ecc; 2 3 import std.stdio; 4 import std.string; 5 6 import deimos.openssl.evp; 7 import deimos.openssl.rand; 8 import deimos.openssl.pem; 9 import deimos.openssl.bio; 10 11 import secured.hash; 12 import secured.kdf; 13 import secured.random; 14 import secured.util; 15 16 public enum EccCurve 17 { 18 P256, 19 P384, 20 P521, 21 } 22 23 @trusted: 24 25 public class EllipticCurve 26 { 27 private EVP_PKEY_CTX* paramsctx; 28 private EVP_PKEY* params; 29 private EVP_PKEY_CTX* keyctx; 30 private EVP_PKEY* key; 31 32 private bool _hasPrivateKey; 33 public @property bool hasPrivateKey() { return _hasPrivateKey; } 34 35 public this(EccCurve curve = EccCurve.P384) 36 { 37 //Reseed the OpenSSL RNG every time we create a new ECC Key to ensure that the result is truely random in threading/forking scenarios. 38 ubyte[] seedbuf = random(32); 39 RAND_seed(seedbuf.ptr, cast(int)seedbuf.length); 40 41 //Generate the key parameters 42 paramsctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, null); 43 if (paramsctx is null) { 44 throw new CryptographicException("Cannot get an OpenSSL public key context."); 45 } 46 if (EVP_PKEY_paramgen_init(paramsctx) < 1) { 47 throw new CryptographicException("Cannot initialize the OpenSSL public key context."); 48 } 49 if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(paramsctx, getOpenSSLCurveId(curve)) < 1) { 50 throw new CryptographicException("Cannot set the requested curve."); 51 } 52 if (EVP_PKEY_paramgen(paramsctx, ¶ms) < 1) { 53 throw new CryptographicException("Unable to generate the key parameters."); 54 } 55 56 //Generate the public and private keys 57 keyctx = EVP_PKEY_CTX_new(params, null); 58 if (keyctx is null) { 59 throw new CryptographicException("Cannot get an OpenSSL private key context."); 60 } 61 if (EVP_PKEY_keygen_init(keyctx) < 1) { 62 throw new CryptographicException("Cannot initialize the OpenSSL private key context."); 63 } 64 if (EVP_PKEY_keygen(keyctx, &key) < 1) { 65 throw new CryptographicException("Unable to generate the private key."); 66 } 67 68 _hasPrivateKey = true; 69 } 70 71 public this(string privateKey, string password) 72 { 73 //Reseed the OpenSSL RNG every time we load an existing ECC Key to ensure that the result is truely random in threading/forking scenarios. 74 ubyte[] seedbuf = random(32); 75 RAND_seed(seedbuf.ptr, cast(int)seedbuf.length); 76 77 _hasPrivateKey = true; 78 ubyte[] pk = cast(ubyte[])privateKey; 79 80 BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length); 81 if (password is null) { 82 key = PEM_read_bio_PrivateKey(bio, null, null, null); 83 } else { 84 ubyte[] pwd = cast(ubyte[])password; 85 pwd = pwd ~ '\0'; 86 87 key = PEM_read_bio_PrivateKey(bio, null, null, pwd.ptr); 88 } 89 BIO_free_all(bio); 90 } 91 92 public this(string publicKey) 93 { 94 //Reseed the OpenSSL RNG every time we load an existing ECC Key to ensure that the result is truely random in threading/forking scenarios. 95 ubyte[] seedbuf = random(32); 96 RAND_seed(seedbuf.ptr, cast(int)seedbuf.length); 97 98 _hasPrivateKey = false; 99 ubyte[] pk = cast(ubyte[])publicKey; 100 101 BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length); 102 key = PEM_read_bio_PUBKEY(bio, null, null, null); 103 BIO_free_all(bio); 104 } 105 106 public ~this() 107 { 108 if (key !is null) { 109 EVP_PKEY_free(key); 110 } 111 if (keyctx !is null) { 112 EVP_PKEY_CTX_free(keyctx); 113 } 114 if (params !is null) { 115 EVP_PKEY_free(params); 116 } 117 if (paramsctx !is null) { 118 EVP_PKEY_CTX_free(paramsctx); 119 } 120 } 121 122 public ubyte[] derive(string peerKey) 123 { 124 ubyte[] pk = cast(ubyte[])peerKey; 125 BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length); 126 EVP_PKEY* peer = PEM_read_bio_PUBKEY(bio, null, null, null); 127 BIO_free_all(bio); 128 129 //Initialize the key derivation context. 130 EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, null); 131 if (ctx is null) { 132 throw new CryptographicException("Unable to create the key derivation context."); 133 } 134 if (EVP_PKEY_derive_init(ctx) <= 0) { 135 throw new CryptographicException("Unable to initialize the key derivation context."); 136 } 137 if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) { 138 throw new CryptographicException("Unable to set the peer key."); 139 } 140 141 //Derive the key 142 size_t dklen = 0; 143 if (EVP_PKEY_derive(ctx, null, &dklen) <= 0) { 144 throw new CryptographicException("Unable to determine the length of the derived key."); 145 } 146 ubyte[] derivedKey = new ubyte[dklen]; 147 if (EVP_PKEY_derive(ctx, derivedKey.ptr, &dklen) <= 0) { 148 throw new CryptographicException("Unable to determine the length of the derived key."); 149 } 150 151 return derivedKey; 152 } 153 154 public ubyte[] sign(ubyte[] data, bool useSha256 = false) 155 { 156 EVP_PKEY_CTX* pkeyctx = null; 157 158 pkeyctx = EVP_PKEY_CTX_new(key, null); 159 if (pkeyctx is null) { 160 throw new CryptographicException("Unable to create the key signing context."); 161 } 162 scope(exit) { 163 if (pkeyctx !is null) { 164 EVP_PKEY_CTX_free(pkeyctx); 165 } 166 } 167 168 if (EVP_PKEY_sign_init(pkeyctx) <= 0) { 169 throw new CryptographicException("Unable to initialize the signing digest."); 170 } 171 172 if (EVP_PKEY_CTX_set_signature_md(pkeyctx, cast(void*)(!useSha256 ? EVP_sha384() : EVP_sha256())) <= 0) { 173 throw new CryptographicException("Unable to set the signing digest."); 174 } 175 176 size_t signlen = 0; 177 if (EVP_PKEY_sign(pkeyctx, null, &signlen, data.ptr, data.length) <= 0) { 178 throw new CryptographicException("Unable to calculate signature length."); 179 } 180 181 ubyte[] sign = new ubyte[signlen]; 182 if (EVP_PKEY_sign(pkeyctx, sign.ptr, &signlen, data.ptr, data.length) <= 0) { 183 throw new CryptographicException("Unable to calculate signature."); 184 } 185 186 return sign; 187 } 188 189 public bool verify(ubyte[] data, ubyte[] signature, bool useSha256 = false) 190 { 191 EVP_PKEY_CTX* pkeyctx = null; 192 193 pkeyctx = EVP_PKEY_CTX_new(key, null); 194 if (pkeyctx is null) { 195 throw new CryptographicException("Unable to create the key signing context."); 196 } 197 scope(exit) { 198 if (pkeyctx !is null) { 199 EVP_PKEY_CTX_free(pkeyctx); 200 } 201 } 202 203 if (EVP_PKEY_verify_init(pkeyctx) <= 0) { 204 throw new CryptographicException("Unable to initialize the signing digest."); 205 } 206 207 if (EVP_PKEY_CTX_set_signature_md(pkeyctx, cast(void*)(!useSha256 ? EVP_sha384() : EVP_sha256())) <= 0) { 208 throw new CryptographicException("Unable to set the signing digest."); 209 } 210 211 int ret = EVP_PKEY_verify(pkeyctx, data.ptr, cast(long)data.length, signature.ptr, cast(long)signature.length); 212 213 return ret != 1; 214 } 215 216 public string getPublicKey() 217 { 218 BIO* bio = BIO_new(BIO_s_mem()); 219 220 PEM_write_bio_PUBKEY(bio, key); 221 222 ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)]; 223 BIO_read(bio, buffer.ptr, cast(int)buffer.length); 224 BIO_free_all(bio); 225 226 return cast(string)buffer; 227 } 228 229 public string getPrivateKey(string password, bool use3Des = false) 230 { 231 if (!_hasPrivateKey) { 232 return null; 233 } 234 235 BIO* bio = BIO_new(BIO_s_mem()); 236 237 if (password is null) { 238 PEM_write_bio_PKCS8PrivateKey(bio, key, null, null, 0, null, null); 239 } else { 240 ubyte[] pwd = cast(ubyte[])password; 241 pwd = pwd ~ '\0'; 242 243 PEM_write_bio_PKCS8PrivateKey( 244 bio, 245 key, 246 !use3Des ? EVP_aes_256_cbc() : EVP_des_ede3_cbc(), 247 null, 248 0, 249 null, 250 pwd.ptr); 251 } 252 253 if(BIO_ctrl_pending(bio) == 0) { 254 throw new CryptographicException("No private key written."); 255 } 256 257 ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)]; 258 BIO_read(bio, buffer.ptr, cast(int)buffer.length); 259 BIO_free_all(bio); 260 261 return cast(string)buffer; 262 } 263 } 264 265 unittest 266 { 267 import std.digest; 268 269 writeln("Testing EllipticCurve Private Key Extraction/Recreation:"); 270 271 EllipticCurve eckey = new EllipticCurve(); 272 string pub = eckey.getPublicKey(); 273 274 writeln("Extracting No Password"); 275 string pkNoPwd = eckey.getPrivateKey(null); 276 writeln("Extracting With Password"); 277 string pkPwd = eckey.getPrivateKey("Test Password"); 278 279 writeln("Private Key Without Password: "); 280 writeln(pkNoPwd); 281 writeln("Private Key With Password:"); 282 writeln(pkPwd); 283 284 assert(pkNoPwd !is null); 285 assert(pkPwd !is null); 286 287 EllipticCurve eckeyr1 = new EllipticCurve(pkNoPwd, null); 288 EllipticCurve eckeyr2 = new EllipticCurve(pkPwd, "Test Password"); 289 290 string pkRecPwd = eckeyr2.getPrivateKey("Test Password"); 291 string pkRecNoPwd = eckeyr1.getPrivateKey(null); 292 293 writeln("Recreated Private Key Without Password: "); 294 writeln(pkRecNoPwd); 295 writeln("Recreated Private Key With Password:"); 296 writeln(pkRecPwd); 297 298 assert(pkNoPwd == pkRecNoPwd); 299 } 300 301 unittest 302 { 303 import std.digest; 304 305 writeln("Testing EllipticCurve Key Derivation:"); 306 307 EllipticCurve eckey1 = new EllipticCurve(); 308 writeln("Created Key 1"); 309 EllipticCurve eckey2 = new EllipticCurve(); 310 writeln("Created Key 2"); 311 312 string privKey1 = eckey1.getPrivateKey(null); 313 writeln("Retrieved Private Key 1"); 314 315 316 string pubKey1 = eckey1.getPublicKey(); 317 writeln("Retrieved Public Key 1"); 318 string pubKey2 = eckey2.getPublicKey(); 319 writeln("Retrieved Public Key 2"); 320 ubyte[] key1 = eckey1.derive(pubKey2); 321 writeln("Derived Key 1"); 322 ubyte[] key2 = eckey2.derive(pubKey1); 323 writeln("Derived Key 2"); 324 325 writeln("Derived Key 1: ", toHexString!(LetterCase.lower)(key1)); 326 writeln("Derived Key 2: ", toHexString!(LetterCase.lower)(key2)); 327 328 assert(key1 !is null); 329 assert(key2 !is null); 330 assert(constantTimeEquality(key1, key2)); 331 } 332 333 unittest 334 { 335 import std.digest; 336 337 writeln("Testing EllipticCurve Signing/Verification:"); 338 339 EllipticCurve eckey = new EllipticCurve(); 340 ubyte[48] data = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 341 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 342 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ]; 343 344 ubyte[] sig = eckey.sign(data); 345 writeln("Signature: ", toHexString!(LetterCase.lower)(sig)); 346 assert(eckey.verify(data, sig)); 347 } 348 349 private int getOpenSSLCurveId(EccCurve curve) { 350 import std.conv; 351 import std.format; 352 353 switch (curve) { 354 case EccCurve.P256: return NID_secp256k1; 355 case EccCurve.P384: return NID_secp384r1; 356 case EccCurve.P521: return NID_secp521r1; 357 default: 358 throw new CryptographicException(format("ECC Curve '%s' not supported.", to!string(curve))); 359 } 360 }