1 // Written in the D programming language.
2 
3 module wrapper.sodium.crypto_aead_xchacha20poly1305;
4 
5 import wrapper.sodium.core; // assure sodium got initialized
6 
7 public
8 import  deimos.sodium.crypto_aead_xchacha20poly1305 : crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
9                                                       crypto_aead_xchacha20poly1305_ietf_keybytes,
10                                                       crypto_aead_xchacha20poly1305_ietf_NSECBYTES,
11                                                       crypto_aead_xchacha20poly1305_ietf_nsecbytes,
12                                                       crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
13                                                       crypto_aead_xchacha20poly1305_ietf_npubbytes,
14                                                       crypto_aead_xchacha20poly1305_ietf_ABYTES,
15                                                       crypto_aead_xchacha20poly1305_ietf_abytes,
16                                                       crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX,
17                                                       crypto_aead_xchacha20poly1305_ietf_messagebytes_max,
18 /*                                                    crypto_aead_xchacha20poly1305_ietf_encrypt,
19                                                       crypto_aead_xchacha20poly1305_ietf_decrypt,
20                                                       crypto_aead_xchacha20poly1305_ietf_encrypt_detached,
21                                                       crypto_aead_xchacha20poly1305_ietf_decrypt_detached, */
22                                                       crypto_aead_xchacha20poly1305_ietf_keygen;
23 ;
24 
25 
26 import std.exception : assertThrown, assertNotThrown;
27 import nogc.exception: enforce;
28 
29 // overloading some functions between module deimos.sodium.crypto_aead_xchacha20poly1305 and this module
30 
31 alias crypto_aead_xchacha20poly1305_ietf_encrypt = deimos.sodium.crypto_aead_xchacha20poly1305.crypto_aead_xchacha20poly1305_ietf_encrypt;
32 
33 /**
34  * The function crypto_aead_xchacha20poly1305_ietf_encrypt()
35  * encrypts a message `m` using a secret key `k` (crypto_aead_xchacha20poly1305_ietf_KEYBYTES bytes)
36  * and a public nonce `npub` (crypto_aead_xchacha20poly1305_ietf_NPUBBYTES bytes).
37  * The encrypted message, as well as a tag authenticating both the confidential message m and ad.length bytes of non-confidential data `ad`,
38  * are put into `c`.
39  * ad can also be an empty array if no additional data are required.
40  * At most m.length + crypto_aead_xchacha20poly1305_ietf_ABYTES bytes are put into `c`, and the actual number of bytes is stored into `clen_p`.
41  * The function always returns true.
42  * The public nonce npub should never ever be reused with the same key. The recommended way to generate it is to use
43  * randombytes_buf() for the first message, and then to increment it for each subsequent message using the same key.
44  */
45 bool crypto_aead_xchacha20poly1305_ietf_encrypt(scope ubyte[] c,
46                                                 scope const ubyte[] m,
47                                                 scope const ubyte[] ad,
48                                                 const ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] npub,
49                                                 const ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
50 {
51 //  enforce(m.length, "Error invoking crypto_aead_xchacha20poly1305_ietf_encrypt: m is null"); // TODO check if m.ptr==null would be okay
52   enforce(c.length == m.length + crypto_aead_xchacha20poly1305_ietf_ABYTES, "Error invoking crypto_aead_xchacha20poly1305_ietf_encrypt: out buffer too small");
53   ulong clen_p;
54   bool result = crypto_aead_xchacha20poly1305_ietf_encrypt(c.ptr, &clen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
55   if (result)
56     assert(clen_p ==  m.length + crypto_aead_xchacha20poly1305_ietf_ABYTES); // okay to be removed in release code
57   return  result;
58 }
59 
60 alias crypto_aead_xchacha20poly1305_ietf_decrypt = deimos.sodium.crypto_aead_xchacha20poly1305.crypto_aead_xchacha20poly1305_ietf_decrypt;
61 
62 /**
63  * The function crypto_aead_xchacha20poly1305_ietf_decrypt()
64   verifies that the ciphertext `c` (as produced by crypto_aead_xchacha20poly1305_ietf_encrypt()),
65  * includes a valid tag using a secret key `k`, a public nonce `npub`, and additional data `ad`. c.length is the ciphertext length
66  * in bytes with the authenticator, so it has to be at least crypto_aead_xchacha20poly1305_ietf_ABYTES.
67  *
68  * ad can be an empty array if no additional data are required.
69  * The function returns false if the verification fails.
70  * If the verification succeeds, the function returns true, puts the decrypted message into `m` and stores its actual number of bytes into `mlen_p`.
71  * At most c.length - crypto_aead_xchacha20poly1305_ietf_ABYTES bytes will be put into m.
72  */
73 bool crypto_aead_xchacha20poly1305_ietf_decrypt(scope ubyte[] m,
74                                                 scope const ubyte[] c,
75                                                 scope const ubyte[] ad,
76                                                 const ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] npub,
77                                                 const ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
78 {
79 //  enforce(c.length >= crypto_aead_xchacha20poly1305_ietf_ABYTES, "Expected c.length: ", c.length, " to be greater_equal to crypto_aead_xchacha20poly1305_ietf_ABYTES: ", crypto_aead_xchacha20poly1305_ietf_ABYTES);
80   enforce(c.length >= crypto_aead_xchacha20poly1305_ietf_ABYTES, "Expected c.length is not greater_equal to crypto_aead_xchacha20poly1305_ietf_ABYTES");
81   const  m_expect_len = c.length - crypto_aead_xchacha20poly1305_ietf_ABYTES;
82 //  enforce(m.length == m_expect_len, "Expected m.length: ", m.length, " to be equal to c.length - crypto_aead_xchacha20poly1305_ietf_ABYTES: ", m_expect_len);
83   enforce(m.length == m_expect_len, "Expected m.length is not equal to c.length - crypto_aead_xchacha20poly1305_ietf_ABYTES");
84   ulong mlen_p;
85   bool result = crypto_aead_xchacha20poly1305_ietf_decrypt(m.ptr, &mlen_p, null, c.ptr, c.length, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
86   if (result)
87     assert(mlen_p  ==  m_expect_len); // okay to be removed in release code
88   return  result;
89 }
90 
91 alias crypto_aead_xchacha20poly1305_ietf_encrypt_detached = deimos.sodium.crypto_aead_xchacha20poly1305.crypto_aead_xchacha20poly1305_ietf_encrypt_detached;
92 
93 bool  crypto_aead_xchacha20poly1305_ietf_encrypt_detached(scope ubyte[] c,
94                                                           out ubyte[crypto_aead_xchacha20poly1305_ietf_ABYTES] mac,
95                                                           scope const ubyte[] m,
96                                                           scope const ubyte[] ad,
97                                                           const ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] npub,
98                                                           const ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
99 {
100 //  enforce(m.length, "Error invoking crypto_aead_xchacha20poly1305_ietf_encrypt_detached: m is null"); // TODO check if m.ptr==null would be okay
101 //  enforce(c.length == m.length, "Expected c.length: ", c.length, " to be equal to m.length: ", m.length);
102   enforce(c.length == m.length, "Expected c.length is not equal to m.length");
103   ulong maclen_p;
104   bool result = crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c.ptr, mac.ptr, &maclen_p, m.ptr, m.length, ad.ptr, ad.length, null, npub.ptr, k.ptr) == 0;
105   assert(maclen_p == crypto_aead_xchacha20poly1305_ietf_ABYTES);
106   return  result;
107 }
108 
109 alias crypto_aead_xchacha20poly1305_ietf_decrypt_detached = deimos.sodium.crypto_aead_xchacha20poly1305.crypto_aead_xchacha20poly1305_ietf_decrypt_detached;
110 
111 bool crypto_aead_xchacha20poly1305_ietf_decrypt_detached(scope ubyte[] m,
112                                                          scope const ubyte[] c,
113                                                          const ubyte[crypto_aead_xchacha20poly1305_ietf_ABYTES] mac,
114                                                          scope const ubyte[] ad,
115                                                          const ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] npub,
116                                                          const ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] k) @nogc @trusted
117 {
118 //  enforce(c.length, "Error invoking crypto_aead_xchacha20poly1305_ietf_decrypt_detached: c is null"); // TODO check if c.ptr==null would be okay
119 //  enforce(m.length == c.length, "Expected m.length: ", m.length, " to be equal to c.length: ", c.length);
120   enforce(m.length == c.length, "Expected m.length is not equal to c.length");
121   return  crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m.ptr, null, c.ptr, c.length, mac.ptr, ad.ptr, ad.length, npub.ptr, k.ptr) == 0;
122 }
123 
124 
125 
126 version(unittest)
127 {
128   import wrapper.sodium.randombytes : randombytes;
129   // share a key and nonce in the following unittests
130   ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES]  nonce = void;
131   ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]   key   = void;
132 
133   static this() {
134     randombytes(nonce);
135     randombytes(key);
136   }
137 }
138 
139 
140 @system
141 unittest
142 {
143   import std..string : representation;
144   import std.stdio : writeln;
145   debug writeln("unittest block 1 from sodium.crypto_aead_xchacha20poly1305.d");
146 
147   auto message         = representation("test");
148   enum message_len = 4UL;
149   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
150     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
151   ubyte[message_len + crypto_aead_xchacha20poly1305_ietf_ABYTES]  ciphertext;
152   ulong  ciphertext_len;
153 
154   crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext.ptr, &ciphertext_len, message.ptr, message.length,
155     additional_data.ptr, additional_data.length, null, nonce.ptr, key.ptr);
156 
157   ubyte[message_len]  decrypted;
158   ulong decrypted_len;
159   assert(ciphertext_len == ciphertext.length);
160   assert(crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted.ptr, &decrypted_len, null, ciphertext.ptr, ciphertext_len,
161       additional_data.ptr, additional_data.length, nonce.ptr, key.ptr) == 0);
162   assert(decrypted == message); //writeln("Decrypted message (aead_chacha20poly1305): ", cast(string)decrypted);
163   assert(decrypted_len == decrypted.length);
164 
165   // test null for &ciphertext_len / decrypted_len
166   crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext.ptr, null, message.ptr, message.length,
167     additional_data.ptr, additional_data.length, null, nonce.ptr, key.ptr);
168   crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted.ptr, null, null, ciphertext.ptr, ciphertext_len,
169     additional_data.ptr, additional_data.length, nonce.ptr, key.ptr);
170 
171   ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] k;
172   crypto_aead_xchacha20poly1305_ietf_keygen(k);
173 }
174 
175 @safe
176 unittest
177 {
178   import std..string : representation;
179   import std.stdio : writeln, writefln;
180   import wrapper.sodium.utils : sodium_increment;
181   debug writeln("unittest block 2 from sodium.crypto_aead_xchacha20poly1305.d");
182 
183   assert(crypto_aead_xchacha20poly1305_ietf_keybytes()         == crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
184   assert(crypto_aead_xchacha20poly1305_ietf_nsecbytes()        == crypto_aead_xchacha20poly1305_ietf_NSECBYTES);
185   assert(crypto_aead_xchacha20poly1305_ietf_npubbytes()        == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
186   assert(crypto_aead_xchacha20poly1305_ietf_abytes()           == crypto_aead_xchacha20poly1305_ietf_ABYTES);
187   assert(crypto_aead_xchacha20poly1305_ietf_messagebytes_max() == crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX);
188 
189 
190   auto message         = representation("test");
191   enum message_len = 4UL;
192   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
193     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
194   ubyte[message_len + crypto_aead_xchacha20poly1305_ietf_ABYTES]  ciphertext1;
195   sodium_increment(nonce);
196 
197   assertThrown   (crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext1[0..$-1],              message, additional_data, nonce, key));
198   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext1[0..$-message.length], null,    additional_data, nonce, key));
199   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext1,                      message, null,            nonce, key));
200 
201   assert(crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext1, message, additional_data, nonce, key));
202 
203   ubyte[message_len]  decrypted;
204   assertThrown   (crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted,         ciphertext1[0..crypto_aead_xchacha20poly1305_ietf_ABYTES-1], additional_data, nonce, key));
205   assertThrown   (crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted[0..$-1], ciphertext1,                                                 additional_data, nonce, key));
206   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted,         ciphertext1,                                                 null,            nonce, key));
207 
208   assert(crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted, ciphertext1, additional_data, nonce, key));
209   assert(decrypted == message);
210 //
211   ubyte[message_len] ciphertext2;
212   ubyte[crypto_aead_xchacha20poly1305_ietf_ABYTES] mac;
213   sodium_increment(nonce);
214 
215   assertThrown   (crypto_aead_xchacha20poly1305_ietf_encrypt_detached(ciphertext2[0..$-1],              mac, message, additional_data, nonce, key));
216   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_encrypt_detached(ciphertext2[0..$-message.length], mac, null,    additional_data, nonce, key));
217   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_encrypt_detached(ciphertext2,                      mac, message, null,            nonce, key));
218 
219   assert(crypto_aead_xchacha20poly1305_ietf_encrypt_detached(ciphertext2, mac, message, additional_data, nonce, key));
220 
221   assertThrown   (crypto_aead_xchacha20poly1305_ietf_decrypt_detached(decrypted[0..$-1],              ciphertext2, mac, additional_data, nonce, key));
222   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_decrypt_detached(decrypted[0..$-message.length], null,       mac, additional_data, nonce, key));
223   assertNotThrown(crypto_aead_xchacha20poly1305_ietf_decrypt_detached(decrypted,                      ciphertext2, mac, null,            nonce, key));
224 
225   assert(crypto_aead_xchacha20poly1305_ietf_decrypt_detached(decrypted, ciphertext2, mac, additional_data, nonce, key));
226   assert(decrypted == message);
227 }
228 
229 
230 @nogc @safe
231 unittest
232 {
233   // usage is not cryptographically safe here; it's purpose is to test @nogc @safe
234   import std..string : representation;
235   ubyte[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES]  n = nonce;
236   ubyte[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]   k;
237   crypto_aead_xchacha20poly1305_ietf_keygen(k);
238   ubyte[4] message  = [116, 101, 115, 116]; //representation("test");
239   ubyte[4] decrypted;
240   enum  m_len  = 4UL;
241   auto additional_data = representation("A typical use case for additional data is to store protocol-specific metadata " ~
242     "about the message, such as its length and encoding. (non-confidential, non-encrypted data");
243   ubyte[m_len+crypto_aead_xchacha20poly1305_ietf_ABYTES] ciphertext1;
244   ubyte[m_len]                                           ciphertext2;
245   ubyte[      crypto_aead_xchacha20poly1305_ietf_ABYTES] mac;
246 
247   assert(crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext1,   message, additional_data, n, k));
248   assert(crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted, ciphertext1, additional_data, n, k));
249   assert(decrypted == message);
250   decrypted = decrypted.init;
251   assert(crypto_aead_xchacha20poly1305_ietf_encrypt_detached(ciphertext2, mac,   message, additional_data, n, k));
252   assert(crypto_aead_xchacha20poly1305_ietf_decrypt_detached(decrypted, ciphertext2, mac, additional_data, n, k));
253   assert(decrypted == message);
254 }