1 module tagion.crypto.Cipher; 2 3 import std.exception : assumeUnique, ifThrown; 4 import tagion.basic.Types : Buffer; 5 import tagion.crypto.Types : Pubkey; 6 import tagion.crypto.random.random; 7 import tagion.hibon.Document; 8 import tagion.hibon.HiBONRecord; 9 10 @safe 11 struct Cipher { 12 import tagion.crypto.secp256k1.NativeSecp256k1; 13 import std.digest.crc : crc64ECMAOf; 14 import tagion.basic.ConsensusExceptions : ConsensusException, ConsensusFailCode, SecurityConsensusException; 15 import tagion.crypto.SecureInterfaceNet : SecureNet; 16 import tagion.crypto.SecureNet : check; 17 import tagion.crypto.random.random; 18 import tagion.crypto.aes.AESCrypto : AESCrypto; 19 20 alias AES = AESCrypto!256; 21 enum CRC_SIZE = crc64ECMAOf.length; 22 23 @recordType("TCD") 24 struct CipherDocument { 25 @label("$m") Buffer ciphermsg; 26 @label("$n") Buffer nonce; 27 @label("$a") Buffer authTag; 28 @label("$k") Pubkey cipherPubkey; 29 mixin HiBONRecord; 30 } 31 32 static const(CipherDocument) encrypt(const(SecureNet) net, const(Pubkey) pubkey, const(Document) msg) { 33 34 scope ubyte[32] secret_key_alloc; 35 scope ubyte[] secret_key = secret_key_alloc; 36 scope (exit) { 37 secret_key[] = 0; 38 } 39 getRandom(secret_key); 40 CipherDocument result; 41 result.cipherPubkey = net.getPubkey(secret_key); 42 scope ubyte[AES.BLOCK_SIZE] nonce_alloc; 43 scope ubyte[] nonce = nonce_alloc; 44 getRandom(nonce); 45 result.nonce = nonce.idup; 46 // Appand CRC 47 auto ciphermsg = new ubyte[AES.enclength(msg.data.length + CRC_SIZE)]; 48 49 // Put random padding to in the last block 50 auto last_block = ciphermsg[$ - AES.BLOCK_SIZE + CRC_SIZE .. $]; 51 getRandom(last_block); 52 const crc = msg.data.crc64ECMAOf; 53 ciphermsg[0 .. msg.data.length] = msg.data; 54 ciphermsg[msg.data.length .. msg.data.length + CRC_SIZE] = crc; 55 56 scope sharedECCKey = net.ECDHSecret(secret_key, pubkey); 57 AES.encrypt(sharedECCKey, result.nonce, ciphermsg, ciphermsg); 58 Buffer get_ciphermsg() @trusted { 59 return assumeUnique(ciphermsg); 60 } 61 62 result.ciphermsg = get_ciphermsg; 63 return result; 64 } 65 66 static const(CipherDocument) encrypt(const(SecureNet) net, const(Document) msg) { 67 return encrypt(net, net.pubkey, msg); 68 } 69 70 static const(Document) decrypt(const(SecureNet) net, const(CipherDocument) cipher_doc) { 71 scope sharedECCKey = net.ECDHSecret(cipher_doc.cipherPubkey); 72 auto clearmsg = new ubyte[cipher_doc.ciphermsg.length]; 73 AES.decrypt(sharedECCKey, cipher_doc.nonce, cipher_doc.ciphermsg, clearmsg); 74 Buffer data = (() @trusted => assumeUnique(clearmsg))(); 75 const result = Document(data); 76 immutable full_size = result.full_size; 77 check(full_size + CRC_SIZE <= data.length && full_size !is 0, 78 ConsensusFailCode.CIPHER_DECRYPT_ERROR); 79 const crc = data[full_size .. full_size + CRC_SIZE]; 80 check(data[0 .. full_size].crc64ECMAOf == crc, 81 ConsensusFailCode.CIPHER_DECRYPT_CRC_ERROR); 82 return result; 83 } 84 85 unittest { 86 import std.algorithm.searching : all, any; 87 import tagion.basic.Types : FileExtension; 88 import tagion.basic.basic : fileId; 89 import tagion.crypto.SecureNet; 90 import tagion.hibon.Document : Document; 91 import tagion.hibon.HiBON : HiBON; 92 import tagion.utils.Miscellaneous : decode; 93 94 immutable passphrase = "Secret pass word"; 95 auto net = new StdSecureNet; /// Only works with ECDSA for now 96 net.generateKeyPair(passphrase); 97 98 immutable some_secret_message = "Text to be encrypted by ECC public key and " ~ 99 "decrypted by its corresponding ECC private key"; 100 auto hibon = new HiBON; 101 hibon["text"] = some_secret_message; 102 const secret_doc = Document(hibon); 103 104 { // Encrypt and Decrypt secrte message 105 auto dummy_net = new StdSecureNet; 106 const secret_cipher_doc = Cipher.encrypt(dummy_net, net.pubkey, secret_doc); 107 const encrypted_doc = Cipher.decrypt(net, secret_cipher_doc); 108 assert(encrypted_doc["text"].get!string == some_secret_message); 109 assert(secret_doc.data == encrypted_doc.data); 110 } 111 112 { // Use of the wrong privat-key 113 auto dummy_net = new StdSecureNet; 114 auto wrong_net = new StdSecureNet; 115 immutable wrong_passphrase = "wrong word"; 116 wrong_net.generateKeyPair(wrong_passphrase); 117 bool cipher_decrypt_error; 118 bool cipher_decrypt_crc_error; 119 while (!cipher_decrypt_error || !cipher_decrypt_crc_error) { 120 const secret_cipher_doc = Cipher.encrypt(dummy_net, wrong_net.pubkey, secret_doc); 121 try { 122 const encrypted_doc = Cipher.decrypt(net, secret_cipher_doc); 123 assert(secret_doc != encrypted_doc); 124 if (!encrypted_doc.empty) { 125 break; /// Run the loop until the decrypt does not fail 126 } 127 } 128 catch (ConsensusException e) { 129 cipher_decrypt_error |= (e.code == ConsensusFailCode.CIPHER_DECRYPT_ERROR); 130 cipher_decrypt_crc_error |= (e.code == ConsensusFailCode.CIPHER_DECRYPT_CRC_ERROR); 131 } 132 } 133 } 134 135 { // Encrypt and Decrypt secrte message with owner privat-key 136 const secret_cipher_doc = Cipher.encrypt(net, secret_doc); 137 const encrypted_doc = Cipher.decrypt(net, secret_cipher_doc); 138 assert(encrypted_doc["text"].get!string == some_secret_message); 139 assert(secret_doc.data == encrypted_doc.data); 140 } 141 142 } 143 144 }