1 module chloride.lockbox;
2 
3 import chloride.core;
4 import chloride.random : randomArray;
5 
6 import std.array : uninitializedArray;
7 
8 import deimos.sodium.crypto_box;
9 
10 ///
11 unittest {
12     import std..string : representation;
13     immutable ubyte[] message = representation("hello");
14     auto alice = makeKeyPair();
15     auto bob = makeKeyPair();
16     auto box = lockBox(message, bob.publicKey, alice.privateKey);
17     assert(openLockBox(box, alice.publicKey, bob.privateKey) == message);
18 }
19 
20 ///
21 unittest {
22     import std..string : representation;
23     immutable ubyte[] message = representation("hello");
24     auto keys = makeKeyPair();
25     auto box = sealBox(message, keys.publicKey);
26     assert(openSealedBox(box, keys) == message);
27 }
28 
29 alias PublicKey = ubyte[crypto_box_PUBLICKEYBYTES];
30 alias PrivateKey = ubyte[crypto_box_SECRETKEYBYTES];
31 alias LockBoxSeed = ubyte[crypto_box_SEEDBYTES];
32 alias Nonce = ubyte[crypto_box_NONCEBYTES];
33 alias LockBoxMacLength = crypto_box_MACBYTES;
34 
35 /**
36  * Struct containing a key pair for public key encryption
37  */
38 struct KeyPair {
39     PublicKey publicKey;
40     PrivateKey privateKey;
41 }
42 
43 /**
44  * Generate a new random key pair.
45  */
46 KeyPair makeKeyPair() {
47     KeyPair pair;
48     auto result = crypto_box_keypair(pair.publicKey.ptr, pair.privateKey.ptr);
49     enforceSodium(result == 0);
50     return pair;
51 }
52 
53 /**
54  * Generate a key pair from a seed.
55  */
56 KeyPair makeKeyPair(in LockBoxSeed seed) {
57     KeyPair pair;
58     auto result = crypto_box_seed_keypair(pair.publicKey.ptr,
59                                           pair.privateKey.ptr,
60                                           seed.ptr);
61     enforceSodium(result == 0);
62     return pair;
63 }
64 
65 /**
66  * Generate a seed suitable for use with makeKeyPair
67  */
68 alias makeLockBoxSeed = randomArray!LockBoxSeed;
69 
70 /**
71  * Generate a nonce suitable for encrypting a lock box.
72  */
73 alias makeNonce = randomArray!Nonce;
74 
75 /**
76  * Struct containing a message encrypted and signed using public key cryptography.
77  * Includes the ciphertext and the nonce used to encrypt it.
78  */
79 struct LockBox {
80     /// The encrypted message
81     ubyte[] ciphertext;
82     /// The nonce used to encrypt it (and needed for decryption)
83     Nonce nonce;
84 }
85 
86 /**
87  * Encrypt a message using a public key for the recipient  (`pk`) and signed with the private key of the
88  * sender `sk`.
89  */
90 ubyte[] encryptLockBox(in ubyte[] message, in Nonce nonce, in PublicKey pk, in PrivateKey sk) {
91     auto cipher = uninitializedArray!(ubyte[])(message.length + LockBoxMacLength);
92     auto result = crypto_box_easy(cipher.ptr, message.ptr, message.length,
93                                   nonce.ptr, pk.ptr, sk.ptr);
94     enforceSodium(result == 0);
95     return cipher;
96 }
97 
98 /**
99  * Decrypt a message using a private key for the recipient (`sk`) and verify the signature with the public
100  * key of the sender (`pk`). Returns null if decryption or verification failed.
101  */
102 ubyte[] decryptLockBox(in ubyte[] cipher, in Nonce nonce, in PublicKey pk, in PrivateKey sk) in {
103     assert(cipher.length > LockBoxMacLength);
104 } body {
105     auto message = uninitializedArray!(ubyte[])(cipher.length - LockBoxMacLength);
106     auto result = crypto_box_open_easy(message.ptr, cipher.ptr, cipher.length,
107                                        nonce.ptr, pk.ptr, sk.ptr);
108     if (result == 0) {
109         return message;
110     } else {
111         return null;
112     }
113 }
114 
115 /**
116  * Encrypt and sign `message` using public key cryptography. The message
117  * is encrypted using the public key of the recipient (`pk`) and signed with
118  * the private key of the sender (`sk`).
119  */
120 LockBox lockBox(in ubyte[] message, in PublicKey pk, in PrivateKey sk) {
121     LockBox result = {nonce: makeNonce()};
122     result.ciphertext = encryptLockBox(message, result.nonce, pk, sk);
123     return result;
124 }
125 
126 /**
127  * Decrypt a LockBox encrypted and signed using public key cryptography.
128  * The message is decrypted using the private key of the recipient (`sk`) and
129  * the signature is verified using the public key of the sender (`pk`).
130  * If decryption or verification fails, null is returned.
131  */
132 ubyte[] openLockBox(in LockBox box, in PublicKey pk, in PrivateKey sk) {
133     return decryptLockBox(box.ciphertext, box.nonce, pk, sk);
134 }
135 
136 /**
137  * Anonymously encrypt a message using the recipients public key. A new key pair is generated for
138  * each invocation, so a nonce isn't needed.
139  */
140 ubyte[] sealBox(in ubyte[] message, in PublicKey pk) {
141     auto cipher = uninitializedArray!(ubyte[])(message.length + crypto_box_SEALBYTES);
142     auto result = crypto_box_seal(cipher.ptr, message.ptr, message.length, pk.ptr);
143     enforceSodium(result == 0);
144     return cipher;
145 }
146 
147 /**
148  * Decrypt a sealed box using the recipient's key pair.
149  * If decryption fails, return null.
150  */
151 ubyte[] openSealedBox(in ubyte[] cipher, in KeyPair keys) in {
152     assert(cipher.length > crypto_box_SEALBYTES);
153 } body {
154     auto message = uninitializedArray!(ubyte[])(cipher.length - crypto_box_SEALBYTES);
155     auto result = crypto_box_seal_open(message.ptr, cipher.ptr, cipher.length,
156                                        keys.publicKey.ptr, keys.privateKey.ptr);
157     if (result == 0) {
158         return message;
159     } else {
160         return null;
161     }
162 }