ecc.d 11 KB

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