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