1 /**
2 * ChaCha20Poly1305 AEAD
3 * 
4 * Copyright:
5 * (C) 2014 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.modes.aead.chacha20poly1305;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_AEAD_CHACHA20_POLY1305):
15 import botan.modes.aead.aead;
16 import botan.stream.stream_cipher;
17 import botan.mac.mac;
18 import botan.algo_base.transform;
19 import botan.block.block_cipher;
20 import botan.libstate.libstate;
21 import botan.utils.loadstor;
22 import botan.utils.mem_ops;
23 
24 /**
25 * Base class
26 * See draft-irtf-cfrg-chacha20-poly1305-03 for specification
27 * If a nonce of 64 bits is used the older version described in
28 * draft-agl-tls-chacha20poly1305-04 is used instead.
29 */
30 abstract class ChaCha20Poly1305Mode : AEADMode, Transformation
31 {
32 public:
33 	override SecureVector!ubyte startRaw(const(ubyte)* nonce, size_t nonce_len) {
34 		if(!validNonceLength(nonce_len))
35 			throw new InvalidIVLength(name, nonce_len);
36 		
37 		m_ctext_len = 0;
38 		m_nonce_len = nonce_len;
39 		
40 		m_chacha.setIv(nonce, nonce_len);
41 		
42 		{
43 			ubyte[64] zeros;
44 			m_chacha.cipher1(zeros.ptr, zeros.length);
45 			m_poly1305.setKey(zeros.ptr, 32);
46 		}
47 		// Remainder of output is discard
48 		
49 		m_poly1305.update(m_ad);
50 		
51 		if(cfrgVersion()) {
52 			if (m_ad.length % 16) {
53 				ubyte[64] zeros;
54 				m_poly1305.update(zeros.ptr, 16 - m_ad.length() % 16);
55 			}
56 		}
57 		else {
58 			updateLength(m_ad.length);
59 		}
60 		
61 		return SecureVector!ubyte();
62 	}
63 	override void setAssociatedData(const(ubyte)* ad, size_t ad_len) {
64 		if(m_ctext_len)
65 			throw new Exception("Too late to set AD for ChaCha20Poly1305");
66 		m_ad = SecureVector!ubyte(ad[0 .. ad_len]);
67 	}
68 	
69 	override @property string name() const { return "ChaCha20Poly1305"; }
70 	override size_t updateGranularity() const { return 64; }
71 	override KeyLengthSpecification keySpec() const { return KeyLengthSpecification(32); }
72 	override bool validNonceLength(size_t n) const { return (n == 8 || n == 12); }
73 	override size_t tagSize() const { return 16; }
74 	
75 	override void clear() {
76 		m_chacha.clear();
77 		m_poly1305.clear();
78 		m_ad.clear();
79 		m_ctext_len = 0;
80 	}
81 	
82 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
83 	override void keySchedule(const(ubyte)* key, size_t length) {   
84 		m_chacha.setKey(key, length);
85 	}
86 
87 protected:
88 	this() {
89 		AlgorithmFactory af = globalState().algorithmFactory();
90 		m_chacha = af.makeStreamCipher("ChaCha");
91 		m_poly1305 = af.makeMac("Poly1305");
92 	}
93 
94 	Unique!StreamCipher m_chacha;
95 	Unique!MessageAuthenticationCode m_poly1305;
96 
97 	SecureVector!ubyte m_ad;
98 	size_t m_nonce_len;
99 	size_t m_ctext_len;
100 	
101 	bool cfrgVersion() const { return m_nonce_len == 12; }
102 	void updateLength(size_t len) {		
103 		ubyte[8] len8;
104 		storeLittleEndian(cast(ulong)len, len8.ptr);
105 		m_poly1305.update(len8.ptr, 8);
106 	}
107 }
108 
109 /**
110 * ChaCha20Poly1305 Encryption
111 */
112 final class ChaCha20Poly1305Encryption : ChaCha20Poly1305Mode, Transformation
113 {
114 public:
115 	this() { super(); }
116 	override size_t outputLength(size_t input_length) const { return input_length + tagSize(); }
117 	
118 	override size_t minimumFinalSize() const { return 0; }
119 	
120 	override void update(ref SecureVector!ubyte buffer, size_t offset = 0)
121 	{
122 		assert(buffer.length >= offset, "Offset is sane");
123 		const size_t sz = buffer.length - offset;
124 		ubyte* buf = buffer.ptr + offset;
125 		
126 		m_chacha.cipher1(buf, sz);
127 		m_poly1305.update(buf, sz); // poly1305 of ciphertext
128 		m_ctext_len += sz;
129 		
130 	}
131 	
132 	override void finish(ref SecureVector!ubyte buffer, size_t offset = 0)
133 	{
134 		update(buffer, offset);
135 		if(cfrgVersion())
136 		{
137 			if (m_ctext_len % 16)
138 			{
139 				ubyte[16] zeros;
140 				m_poly1305.update(zeros.ptr, 16-m_ctext_len%16);
141 			}
142 			updateLength(m_ad.length);
143 		}
144 		updateLength(m_ctext_len);
145 		
146 		const SecureVector!ubyte mac = m_poly1305.finished();
147 		buffer ~= mac.ptr[0 .. tagSize()];
148 		m_ctext_len = 0;
149 		
150 	}
151 	
152 	// Interface fallthrough
153 	override string provider() const { return "core"; }
154 	override SecureVector!ubyte startRaw(const(ubyte)* nonce, size_t nonce_len) { return super.startRaw(nonce, nonce_len); }
155 	override size_t updateGranularity() const { return super.updateGranularity(); }
156 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
157 	override bool validNonceLength(size_t nonce_len) const { return super.validNonceLength(nonce_len); }
158 	override @property string name() const { return super.name; }
159 	override void clear() { return super.clear(); }
160 };
161 
162 /**
163 * ChaCha20Poly1305 Decryption
164 */
165 final class ChaCha20Poly1305Decryption : ChaCha20Poly1305Mode, Transformation
166 {
167 public:
168 
169 	this() { super(); }
170 	override size_t outputLength(size_t input_length) const { 
171 		assert(input_length > tagSize(), "Sufficient input");
172 		return input_length - tagSize(); 
173 	}
174 	
175 	override size_t minimumFinalSize() const { return 0; }
176 	
177 	override void update(ref SecureVector!ubyte buffer, size_t offset = 0) {
178 		assert(buffer.length >= offset, "Offset is sane");
179 		const size_t sz = buffer.length - offset;
180 		ubyte* buf = buffer.ptr + offset;
181 		
182 		m_poly1305.update(buf, sz); // poly1305 of ciphertext
183 		m_chacha.cipher1(buf, sz);
184 		m_ctext_len += sz;
185 		
186 	}
187 	
188 	override void finish(ref SecureVector!ubyte buffer, size_t offset = 0) {
189 		assert(buffer.length >= offset, "Offset is sane");
190 		const size_t sz = buffer.length - offset;
191 		ubyte* buf = buffer.ptr + offset;
192 		
193 		assert(sz >= tagSize(), "Have the tag as part of final input");
194 		
195 		const size_t remaining = sz - tagSize();
196 		
197 		if(remaining) {
198 			m_poly1305.update(buf, remaining); // poly1305 of ciphertext
199 			m_chacha.cipher1(buf, remaining);
200 			m_ctext_len += remaining;
201 		}
202 		
203 		if(cfrgVersion()) {
204 			if (m_ctext_len % 16)
205 			{
206 				ubyte[16] zeros;
207 				m_poly1305.update(zeros.ptr, 16-m_ctext_len%16);
208 			}
209 			updateLength(m_ad.length);
210 		}
211 		
212 		updateLength(m_ctext_len);
213 		const SecureVector!ubyte mac = m_poly1305.finished();
214 		
215 		const ubyte* included_tag = buf + remaining;
216 		
217 		m_ctext_len = 0;
218 		
219 		if(!sameMem(mac.ptr, included_tag, tagSize()))
220 			throw new IntegrityFailure("ChaCha20Poly1305 tag check failed");
221 		
222 		buffer.resize(offset + remaining);
223 	}
224 	
225 	// Interface fallthrough
226 	override string provider() const { return "core"; }
227 	override SecureVector!ubyte startRaw(const(ubyte)* nonce, size_t nonce_len) { return super.startRaw(nonce, nonce_len); }
228 	override size_t updateGranularity() const { return super.updateGranularity(); }
229 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
230 	override bool validNonceLength(size_t nonce_len) const { return super.validNonceLength(nonce_len); }
231 	override @property string name() const { return super.name; }
232 	override void clear() { return super.clear(); }
233 }
234