1 module dcrypt.aead.poly1305_chacha;
2 
3 /// Implementation of the Poly1305-ChaCha20 AEAD cipher.
4 /// 
5 /// Standard: RFC 7539
6 
7 import dcrypt.aead.aead;
8 import dcrypt.streamcipher.chacha;
9 import dcrypt.macs.poly1305;
10 import dcrypt.bitmanip;
11 import dcrypt.util: wipe;
12 
13 static assert(isAEADCipher!Poly1305ChaCha20);
14 
15 alias Poly1305Cipher!ChaCha20 Poly1305ChaCha20;
16 alias AEADCipherWrapper!Poly1305ChaCha20 Poly1305ChaChaEngine;
17 
18 
19 @safe
20 private template isSupportedCipher(T)
21 {
22 	enum bool isSupportedCipher = 
23 		is(T == struct) &&
24 			is(typeof(
25 					{
26 						ubyte[] key, nonce;
27 						T cipher;
28 						cipher.start(true, key, nonce, uint(0)); // Can set initial counter.
29 					}));
30 }
31 
32 @safe
33 private struct Poly1305Cipher(Cipher)
34 	if (isSupportedCipher!Cipher)
35 {
36 
37 	@safe nothrow @nogc:
38 
39 	public enum name = Cipher.name~"-Poly1305";
40 	public enum macSize = 16;
41 
42 	private {
43 		Poly1305Raw poly;
44 		Cipher cipher;
45 
46 		ulong aadLength, cipherTextLength;
47 
48 		bool forEncryption;
49 		bool aadMode;			/// true: AAD can be processed. false: encrypting or decrypting, can't process AAD anymore.
50 		bool initialized;
51 
52 		version (unittest) {
53 			ubyte[32] polyKey;
54 		}
55 	}
56 
57 	///
58 	/// Params:
59 	/// forEncryption = Not relevant.
60 	/// key = Secret key. 32 bytes.
61 	/// constant = Something like an IV (sender ID, ...)
62 	/// nonce = Unique per secret key. 8 bytes.
63 	public void start(bool forEncryption, in ubyte[] key, in uint constant, in ubyte[] nonce)
64 	in {
65 		assert(key.length == 32, "Poly1305Cipher requires a 256 bit key.");
66 		assert(nonce.length == 8, "Poly1305Cipher requires a 64 bit nonce.");
67 	} body {
68 
69 		// start(forEncryption, key, nonce); // Salsa20
70 
71 		ubyte[12] _nonce;
72 		toLittleEndian(constant, _nonce[0..4]);
73 		_nonce[4..12] = nonce;
74 
75 		start(forEncryption, key, _nonce);
76 	}
77 
78 	///
79 	/// Params:
80 	/// forEncryption = Not relevant.
81 	/// key = Secret key. 32 bytes.
82 	/// nonce = Unique per secret key. 12 bytes.
83 	public void start(bool forEncryption, in ubyte[] key, in ubyte[] nonce)
84 	in {
85 		assert(key.length == 32, "ChaCha20 requires a 256 bit key.");
86 		assert(nonce.length == 12, "ChaCha20 requires a 96 bit nonce.");
87 	} body {
88 		this.forEncryption = forEncryption;
89 
90 		// Generate the key for poly1305.
91 		poly.start(poly1305KeyGen!Cipher(key, nonce));
92 
93 		cipher.start(forEncryption, key, nonce, 1);
94 
95 		aadMode = true;
96 		aadLength = cipherTextLength = 0;
97 
98 		initialized = true;
99 	}
100 
101 	public void processAADBytes(in ubyte[] aad)
102 	in {
103 		assert(initialized, "Not initialized.");
104 		assert(aadMode, "Must process AAD before cipher data!");
105 	} body {
106 		poly.put(aad);
107 		aadLength += aad.length;
108 	}
109 
110 	public ubyte[] processBytes(in ubyte[] input, ubyte[] output)
111 	in {
112 		assert(initialized, "Not initialized.");
113 		assert(output.length >= input.length, "Output buffer too small.");
114 	} body {
115 
116 		if(aadMode) {
117 			aadMode = false; // Can't process AAD after this.
118 
119 			// pad AAD
120 			pad16(aadLength);
121 		}
122 
123 		ubyte[] slice = cipher.processBytes(input, output);
124 		poly.put(output);
125 
126 		cipherTextLength += input.length;
127 
128 		return slice;
129 	}
130 
131 	/// Returns: The MAC value of the processed AAD and cipher data.
132 	/// 
133 	/// Note: Must be reinitialized with `start()` after calling finish
134 	public size_t finish(ubyte[] mac, ubyte[] output = null)
135 	in {
136 		assert(mac.length == 16, "MAC buffer must be 16 bytes.");
137 	} body {
138 
139 		if(aadMode) {
140 			pad16(aadLength);
141 		} else {
142 			pad16(cipherTextLength);
143 		}
144 
145 		ubyte[8] buf;
146 
147 		// Mac the lengths.
148 		// Note: Inconsistency in RFC7539: section 2.8.1 says, that lengths get encoded as 4 byte little endian,
149 		// but in the test vectors they get encoded as 8 byte little endian.
150 		// 
151 		toLittleEndian!ulong(aadLength, buf);
152 		poly.put(buf[]);
153 		toLittleEndian!ulong(cipherTextLength, buf);
154 		poly.put(buf[]);
155 
156 		
157 		mac[0..16] = poly.finish();
158 
159 		initialized = false;
160 		return 0;
161 	}
162 
163 	/// Get the minimal size of the output buffer for an input of length `len`.
164 	/// Since this is a stream cipher, input and output are equal in length.
165 	public size_t getUpdateOutputSize(in size_t len) const pure {
166 		return len;
167 	}
168 
169 	/// Get the minimal buffer size needed for a call to `finish()`.
170 	/// Since this is a stream cipher all data gets processed instantaneously.
171 	/// Returns: 0
172 	public size_t getOutputSize(in size_t len) const pure {
173 		return 0;
174 	}
175 
176 private:
177 
178 	/// Pad `poly` by adding as much zero bytes to make `len` a integral multiple of 16.
179 	void pad16(size_t len) {
180 		if(len % 16 != 0) {
181 			ubyte[16] zeros = 0;
182 			poly.put(zeros[0..16-len%16]);
183 		}
184 	}
185 }
186 
187 private ubyte[32] poly1305KeyGen(Cipher)(in ubyte[] key, in ubyte[] nonce) pure 
188 	if(isSupportedCipher!Cipher)
189 {
190 	Cipher cipher;
191 	cipher.start(true, key, nonce, 0);
192 
193 	ubyte[32] poly1305Key;
194 	cipher.processBytes(poly1305Key, poly1305Key);
195 
196 	return poly1305Key;
197 }
198 
199 // Test poly1305KeyGen
200 // Test vectors from RFC7539, section 2.6.2
201 private unittest {
202 	ubyte[32] key = cast(const ubyte[]) x"808182838485868788898a8b8c8d8e8f 909192939495969798999a9b9c9d9e9f";
203 	ubyte[12] nonce = cast(const ubyte[]) x"000000000001020304050607";
204 	
205 	ubyte[32] expectedPoly1305Key = cast(const ubyte[]) x"8ad5a08b905f81cc815040274ab29471 a833b637e3fd0da508dbb8e2fdd1a646";
206 	
207 	ubyte[32] poly1305Key = poly1305KeyGen!ChaCha20(key, nonce);
208 	
209 	assert(poly1305Key == expectedPoly1305Key, "poly1305KeyGen() failed.");
210 }
211 
212 // Test vectors from RFC7539, section 2.8.2
213 unittest {
214 
215 	Poly1305ChaCha20 pcc;
216 
217 	enum string plaintext = x"
218 		4c 61 64 69 65 73 20 61 6e 64 20 47 65 6e 74 6c
219 		65 6d 65 6e 20 6f 66 20 74 68 65 20 63 6c 61 73
220 		73 20 6f 66 20 27 39 39 3a 20 49 66 20 49 20 63
221 		6f 75 6c 64 20 6f 66 66 65 72 20 79 6f 75 20 6f
222 		6e 6c 79 20 6f 6e 65 20 74 69 70 20 66 6f 72 20
223 		74 68 65 20 66 75 74 75 72 65 2c 20 73 75 6e 73
224 		63 72 65 65 6e 20 77 6f 75 6c 64 20 62 65 20 69
225 		74 2e";
226 
227 	enum string expectedCipherText = x"
228 		d3 1a 8d 34 64 8e 60 db 7b 86 af bc 53 ef 7e c2
229 		a4 ad ed 51 29 6e 08 fe a9 e2 b5 a7 36 ee 62 d6
230 		3d be a4 5e 8c a9 67 12 82 fa fb 69 da 92 72 8b
231 		1a 71 de 0a 9e 06 0b 29 05 d6 a5 b6 7e cd 3b 36 
232 		92 dd bd 7f 2d 77 8b 8c 98 03 ae e3 28 09 1b 58 
233 		fa b3 24 e4 fa d6 75 94 55 85 80 8b 48 31 d7 bc
234 		3f f4 de f0 8e 4b 7a 9d e5 76 d2 65 86 ce c6 4b 
235 		61 16";
236 
237 	enum string aad = x"50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7";
238 
239 	ubyte[32] key = cast(const ubyte[]) x"80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f";
240 	ubyte[8] iv = cast(const ubyte[]) x"40 41 42 43 44 45 46 47";
241 	uint senderID = 7;
242 
243 	pcc.start(true, key, senderID, iv);
244 
245 	ubyte[plaintext.length] ciphertext;
246 
247 	pcc.processAADBytes(cast(const ubyte[]) aad[]);
248 	pcc.processBytes(cast(const ubyte[]) plaintext[], ciphertext[]);
249 
250 	ubyte[16] tag;
251 	pcc.finish(tag);
252 
253 	assert(ciphertext == expectedCipherText, Poly1305ChaCha20.name~" produced wrong ciphertext.");
254 	assert(tag == x"1ae10b594f09e26a7e902ecbd0600691", Poly1305ChaCha20.name~" produced wrong tag.");
255 
256 }