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, &params) < 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 }