1 module tagion.crypto.SecureNet; 2 3 import std.range; 4 import std.typecons : TypedefType; 5 import std.traits; 6 import tagion.basic.ConsensusExceptions; 7 import tagion.basic.Types : Buffer; 8 import tagion.basic.Version : ver; 9 public import tagion.crypto.SecureInterfaceNet; 10 import tagion.crypto.Types : Fingerprint, Signature; 11 import tagion.crypto.aes.AESCrypto; 12 import tagion.crypto.random.random; 13 import tagion.hibon.Document : Document; 14 15 package alias check = Check!SecurityConsensusException; 16 17 @safe 18 class StdHashNet : HashNet { 19 import std.format; 20 21 enum HASH_SIZE = 32; 22 @nogc final uint hashSize() const pure nothrow scope { 23 return HASH_SIZE; 24 } 25 26 immutable(Buffer) rawCalcHash(scope const(ubyte[]) data) const scope { 27 import std.digest; 28 import std.digest.sha : SHA256; 29 30 return digest!SHA256(data).idup; 31 } 32 33 Fingerprint calcHash(scope const(ubyte[]) data) const { 34 return Fingerprint(rawCalcHash(data)); 35 } 36 37 final immutable(Buffer) HMAC(scope const(ubyte[]) data) const pure { 38 import std.digest.hmac : digestHMAC = HMAC; 39 import std.digest.sha : SHA256; 40 41 auto hmac = digestHMAC!SHA256(data); 42 return hmac.finish.idup; 43 } 44 45 Fingerprint calcHash(const(Document) doc) const { 46 return Fingerprint(rawCalcHash(doc.serialize)); 47 } 48 49 enum hashname = "sha256"; 50 string multihash() const pure nothrow @nogc { 51 return hashname; 52 } 53 } 54 55 @safe 56 class StdSecureNet : StdHashNet, SecureNet { 57 import tagion.crypto.secp256k1.NativeSecp256k1; 58 import std.format; 59 import std.string : representation; 60 import tagion.basic.ConsensusExceptions; 61 import tagion.crypto.Types : Pubkey; 62 import tagion.crypto.aes.AESCrypto; 63 64 private Pubkey _pubkey; 65 /** 66 This function 67 returns 68 If method is SIGN the signed message or 69 If method is DERIVE it returns the derived privat key 70 */ 71 @safe 72 interface SecretMethods { 73 immutable(ubyte[]) sign(const(ubyte[]) message) const; 74 void tweak(const(ubyte[]) tweek_code, out ubyte[] tweak_privkey) const; 75 immutable(ubyte[]) ECDHSecret(scope const(Pubkey) pubkey) const; 76 void clone(StdSecureNet net) const; 77 void __expose(out scope ubyte[] _privkey) const ; 78 } 79 80 protected SecretMethods _secret; 81 82 @nogc final Pubkey pubkey() pure const nothrow { 83 return _pubkey; 84 } 85 86 final Buffer hmacPubkey() const { 87 return HMAC(cast(Buffer) _pubkey); 88 } 89 90 final Pubkey derivePubkey(string tweak_word) const { 91 const tweak_code = HMAC(tweak_word.representation); 92 return derivePubkey(tweak_code); 93 } 94 95 final Pubkey derivePubkey(const(ubyte[]) tweak_code) const { 96 Pubkey result; 97 const pkey = cast(const(ubyte[])) _pubkey; 98 result = crypt.pubTweak(pkey, tweak_code); 99 return result; 100 } 101 102 const NativeSecp256k1 crypt; 103 104 bool verify(const Fingerprint message, const Signature signature, const Pubkey pubkey) const { 105 consensusCheck!(SecurityConsensusException)( 106 signature.length == NativeSecp256k1.SIGNATURE_SIZE, 107 ConsensusFailCode.SECURITY_SIGNATURE_SIZE_FAULT); 108 return crypt.verify(cast(Buffer) message, cast(Buffer) signature, cast(Buffer) pubkey); 109 } 110 111 Signature sign(const Fingerprint message) const 112 in (_secret !is null, 113 format("Signature function has not been intialized. Use the %s function", basename!generatePrivKey)) 114 in (_secret !is null, 115 format("Signature function has not been intialized. Use the %s function", fullyQualifiedName!generateKeyPair)) 116 do { 117 return Signature(_secret.sign(cast(Buffer) message)); 118 } 119 120 final void derive(string tweak_word, ref ubyte[] tweak_privkey) { 121 const data = HMAC(tweak_word.representation); 122 derive(data, tweak_privkey); 123 } 124 125 final void derive(const(ubyte[]) tweak_code, ref ubyte[] tweak_privkey) 126 in (tweak_privkey.length == NativeSecp256k1.TWEAK_SIZE) 127 do { 128 _secret.tweak(tweak_code, tweak_privkey); 129 } 130 131 void derive(string tweak_word, shared(SecureNet) secure_net) { 132 const tweak_code = HMAC(tweak_word.representation); 133 derive(tweak_code, secure_net); 134 135 } 136 137 @trusted 138 void derive(const(ubyte[]) tweak_code, shared(SecureNet) secure_net) 139 in (_secret !is null) 140 do { 141 synchronized (secure_net) { 142 ubyte[] tweak_privkey = tweak_code.dup; 143 auto unshared_secure_net = cast(SecureNet) secure_net; 144 unshared_secure_net.derive(tweak_code, tweak_privkey); 145 createKeyPair(tweak_privkey); 146 } 147 } 148 149 const(SecureNet) derive(const(ubyte[]) tweak_code) const { 150 ubyte[] tweak_privkey; 151 _secret.tweak(tweak_code, tweak_privkey); 152 auto result = new StdSecureNet; 153 result.createKeyPair(tweak_privkey); 154 return result; 155 } 156 157 final void createKeyPair(ref ubyte[] seckey) 158 in (seckey.length == SECKEY_SIZE) 159 do { 160 scope (exit) { 161 getRandom(seckey); 162 } 163 164 ubyte[] privkey; 165 scope (exit) { 166 privkey[] = 0; 167 } 168 crypt.createKeyPair(seckey, privkey); 169 alias AES = AESCrypto!256; 170 _pubkey = crypt.getPubkey(privkey); 171 auto aes_key_iv = new ubyte[AES.KEY_SIZE + AES.BLOCK_SIZE]; 172 getRandom(aes_key_iv); 173 auto aes_key = aes_key_iv[0 .. AES.KEY_SIZE]; 174 auto aes_iv = aes_key_iv[AES.KEY_SIZE .. $]; 175 // Encrypt private key 176 auto encrypted_privkey = new ubyte[privkey.length]; 177 AES.encrypt(aes_key, aes_iv, privkey, encrypted_privkey); 178 @safe 179 void do_secret_stuff(scope void delegate(const(ubyte[]) privkey) @safe dg) { 180 // CBR: 181 // Yes I know it is security by obscurity 182 // But just don't want to have the private in clear text in memory 183 // for long period of time 184 auto tmp_privkey = new ubyte[encrypted_privkey.length]; 185 scope (exit) { 186 getRandom(aes_key_iv); 187 AES.encrypt(aes_key, aes_iv, tmp_privkey, encrypted_privkey); 188 AES.encrypt(rawCalcHash(encrypted_privkey ~ aes_key_iv), aes_iv, encrypted_privkey, tmp_privkey); 189 } 190 AES.decrypt(aes_key, aes_iv, encrypted_privkey, tmp_privkey); 191 dg(tmp_privkey); 192 } 193 194 @safe class LocalSecret : SecretMethods { 195 immutable(ubyte[]) sign(const(ubyte[]) message) const { 196 immutable(ubyte)[] result; 197 ubyte[crypt.MESSAGE_SIZE] _aux_random; 198 ubyte[] aux_random = _aux_random; 199 getRandom(aux_random); 200 do_secret_stuff((const(ubyte[]) privkey) { result = crypt.sign(message, privkey, aux_random); }); 201 return result; 202 } 203 204 void tweak(const(ubyte[]) tweak_code, out ubyte[] tweak_privkey) const { 205 do_secret_stuff((const(ubyte[]) privkey) @safe { crypt.privTweak(privkey, tweak_code, tweak_privkey); }); 206 } 207 208 immutable(ubyte[]) ECDHSecret(scope const(Pubkey) pubkey) const { 209 Buffer result; 210 do_secret_stuff((const(ubyte[]) privkey) @safe { 211 ubyte[] seckey; 212 scope (exit) { 213 seckey[] = 0; 214 } 215 crypt.getSecretKey(privkey, seckey); 216 result = crypt.createECDHSecret(seckey, cast(Buffer) pubkey); 217 }); 218 return result; 219 } 220 221 void clone(StdSecureNet net) const { 222 do_secret_stuff((const(ubyte[]) privkey) @safe { 223 auto _privkey = privkey.dup; 224 net.createKeyPair(_privkey); // Not createKeyPair scrambles the privkey 225 }); 226 } 227 228 void __expose(out scope ubyte[] _privkey) const { 229 do_secret_stuff((const(ubyte[]) privkey) @safe { _privkey = privkey.dup; }); 230 } 231 } 232 233 _secret = new LocalSecret; 234 } 235 236 void __expose(out scope ubyte[] privkey) const { 237 _secret.__expose(privkey); 238 } 239 240 /** 241 Params: 242 passphrase = Passphrase is compatible with bip39 243 salt = In bip39 the salt should be "mnemonic"~word 244 */ 245 void generateKeyPair( 246 scope const(char[]) passphrase, 247 scope const(char[]) salt = null, 248 void delegate(scope const(ubyte[]) data) @safe dg = null) 249 in (_secret is null) 250 do { 251 import tagion.crypto.pbkdf2; 252 import std.digest.sha : SHA512; 253 254 enum count = 2048; 255 enum dk_length = 64; 256 257 alias pbkdf2_sha512 = pbkdf2!SHA512; 258 auto data = pbkdf2_sha512(passphrase.representation, salt.representation, count, dk_length); 259 scope (exit) { 260 data[] = 0; 261 } 262 auto _priv_key = data[0 .. 32]; 263 264 if (dg !is null) { 265 dg(_priv_key); 266 } 267 createKeyPair(_priv_key); 268 } 269 270 immutable(ubyte[]) ECDHSecret( 271 scope const(ubyte[]) seckey, 272 scope const(Pubkey) pubkey) const { 273 return crypt.createECDHSecret(seckey, cast(Buffer) pubkey); 274 } 275 276 immutable(ubyte[]) ECDHSecret(scope const(Pubkey) pubkey) const { 277 return _secret.ECDHSecret(pubkey); 278 } 279 280 Pubkey getPubkey(scope const(ubyte[]) seckey) const { 281 return Pubkey(crypt.getPubkey(seckey)); 282 } 283 284 this() nothrow { 285 crypt = new NativeSecp256k1; 286 } 287 288 this(shared(StdSecureNet) other_net) @trusted { 289 this(); 290 synchronized (other_net) { 291 auto unshared_secure_net = cast(StdSecureNet) other_net; 292 unshared_secure_net._secret.clone(this); 293 } 294 } 295 296 SecureNet clone() const { 297 StdSecureNet result = new StdSecureNet; 298 this._secret.clone(result); 299 return result; 300 } 301 302 unittest { 303 auto other_net = new StdSecureNet; 304 other_net.generateKeyPair("Secret password to be copied"); 305 auto shared_net = (() @trusted => cast(shared) other_net)(); 306 SecureNet copy_net = new StdSecureNet(shared_net); 307 assert(other_net.pubkey == copy_net.pubkey); 308 309 } 310 311 void eraseKey() pure nothrow { 312 _secret = null; 313 } 314 315 unittest { // StdSecureNet rawSign 316 const some_data = "some message"; 317 SecureNet net = new StdSecureNet; 318 net.generateKeyPair("Secret password"); 319 SecureNet bad_net = new StdSecureNet; 320 bad_net.generateKeyPair("Wrong Secret password"); 321 322 const message = net.calcHash(some_data.representation); 323 324 Signature signature = net.sign(message); 325 326 assert(!net.verify(message, signature, bad_net.pubkey)); 327 assert(net.verify(message, signature, net.pubkey)); 328 329 } 330 331 unittest { // StdSecureNet document 332 import std.exception : assertThrown; 333 import tagion.basic.ConsensusExceptions : SecurityConsensusException; 334 import tagion.hibon.HiBON; 335 import tagion.hibon.HiBONJSON; 336 337 SecureNet net = new StdSecureNet; 338 net.generateKeyPair("Secret password"); 339 340 Document doc; 341 { 342 auto h = new HiBON; 343 h["message"] = "Some message"; 344 doc = Document(h); 345 } 346 347 const doc_signed = net.sign(doc); 348 349 assert(net.rawCalcHash(doc.serialize) == net.calcHash(doc.serialize), "should produce same hash"); 350 351 assert(doc_signed.message == net.rawCalcHash(doc.serialize)); 352 assert(net.verify(doc, doc_signed.signature, net.pubkey)); 353 354 SecureNet bad_net = new StdSecureNet; 355 bad_net.generateKeyPair("Wrong Secret password"); 356 assert(!net.verify(doc, doc_signed.signature, bad_net.pubkey)); 357 } 358 359 unittest { 360 SecureNet net = new StdSecureNet; 361 net.generateKeyPair("Secret password"); 362 363 foreach (i; 0 .. 10) { 364 SimpleRecord data; 365 data.x = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX20%s".format(i); 366 367 auto fingerprint = net.calcHash(data); 368 auto second_fingerprint = net.calcHash(data.toDoc.serialize); 369 assert(fingerprint == second_fingerprint); 370 371 auto sig = net.sign(data).signature; 372 assert(net.verify(fingerprint, sig, net.pubkey)); 373 } 374 375 } 376 377 unittest { /// clone 378 SecureNet net = new StdSecureNet; 379 net.generateKeyPair("Very secret word"); 380 auto net_clone = net.clone; 381 assert(net_clone.pubkey == net.pubkey); 382 SimpleRecord doc; 383 doc.x = "Hugo"; 384 const sig = net.sign(doc).signature; 385 const clone_sig = net_clone.sign(doc).signature; 386 const net_check = new StdSecureNet; 387 const msg = net.calcHash(doc); 388 assert(net_check.verify(msg, sig, net.pubkey)); 389 assert(net_check.verify(msg, clone_sig, net_clone.pubkey)); 390 } 391 } 392 393 version (unittest) { 394 import std.format; 395 import tagion.hibon.HiBONRecord; 396 397 static struct SimpleRecord { 398 string x; 399 400 mixin HiBONRecord; 401 } 402 } 403 404 @safe 405 class BadSecureNet : StdSecureNet { 406 this(string passphrase) { 407 super(); 408 generateKeyPair(passphrase); 409 } 410 411 override Signature sign(const Fingerprint message) const { 412 const false_message = super.calcHash(message ~ message); 413 return super.sign(false_message); 414 } 415 }