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 }