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 		ubyte[64] zeros;
43 		m_chacha.cipher1(zeros.ptr, zeros.length);
44 		
45 		m_poly1305.setKey(zeros.ptr, 32);
46 		// Remainder of output is discard
47 		
48 		m_poly1305.update(m_ad);
49 		
50 		if(cfrgVersion()) {
51 			auto padding = Vector!ubyte(16 - m_ad.length % 16);
52 			m_poly1305.update(padding);
53 		}
54 		else {
55 			updateLength(m_ad.length);
56 		}
57 		
58 		return SecureVector!ubyte();
59 	}
60 	override void setAssociatedData(const(ubyte)* ad, size_t ad_len) {
61 		if(m_ctext_len)
62 			throw new Exception("Too late to set AD for ChaCha20Poly1305");
63 		m_ad = SecureVector!ubyte(ad[0 .. ad_len]);
64 	}
65 	
66 	override @property string name() const { return "ChaCha20Poly1305"; }
67 	override size_t updateGranularity() const { return 64; }
68 	override KeyLengthSpecification keySpec() const { return KeyLengthSpecification(32); }
69 	override bool validNonceLength(size_t n) const { return (n == 8 || n == 12); }
70 	override size_t tagSize() const { return 16; }
71 	
72 	override void clear() {
73 		m_chacha.clear();
74 		m_poly1305.clear();
75 		m_ad.clear();
76 		m_ctext_len = 0;
77 	}
78 	
79 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
80 	override void keySchedule(const(ubyte)* key, size_t length) {   
81 		m_chacha.setKey(key, length);
82 	}
83 
84 protected:
85 	this() {
86 		AlgorithmFactory af = globalState().algorithmFactory();
87 		m_chacha = af.makeStreamCipher("ChaCha");
88 		m_poly1305 = af.makeMac("Poly1305");
89 	}
90 
91 	Unique!StreamCipher m_chacha;
92 	Unique!MessageAuthenticationCode m_poly1305;
93 
94 	SecureVector!ubyte m_ad;
95 	size_t m_nonce_len;
96 	size_t m_ctext_len;
97 	
98 	bool cfrgVersion() const { return m_nonce_len == 12; }
99 	void updateLength(size_t len) {		
100 		ubyte[8] len8;
101 		storeLittleEndian(cast(ulong)len, len8.ptr);
102 		m_poly1305.update(len8.ptr, 8);
103 	}
104 }
105 
106 /**
107 * ChaCha20Poly1305 Encryption
108 */
109 final class ChaCha20Poly1305Encryption : ChaCha20Poly1305Mode, Transformation
110 {
111 public:
112 	this() { super(); }
113 	override size_t outputLength(size_t input_length) const { return input_length + tagSize(); }
114 	
115 	override size_t minimumFinalSize() const { return 0; }
116 	
117 	override void update(ref SecureVector!ubyte buffer, size_t offset = 0)
118 	{
119 		assert(buffer.length >= offset, "Offset is sane");
120 		const size_t sz = buffer.length - offset;
121 		ubyte* buf = buffer.ptr + offset;
122 		
123 		m_chacha.cipher1(buf, sz);
124 		m_poly1305.update(buf, sz); // poly1305 of ciphertext
125 		m_ctext_len += sz;
126 		
127 	}
128 	
129 	override void finish(ref SecureVector!ubyte buffer, size_t offset = 0)
130 	{
131 		update(buffer, offset);
132 		if(cfrgVersion())
133 		{
134 			auto padding = Vector!ubyte(16 - m_ctext_len % 16);
135 			m_poly1305.update(padding);
136 			updateLength(m_ad.length);
137 		}
138 		updateLength(m_ctext_len);
139 		
140 		const SecureVector!ubyte mac = m_poly1305.finished();
141 		buffer ~= mac.ptr[0 .. tagSize()];
142 		m_ctext_len = 0;
143 		
144 	}
145 	
146 	// Interface fallthrough
147 	override string provider() const { return "core"; }
148 	override SecureVector!ubyte startRaw(const(ubyte)* nonce, size_t nonce_len) { return super.startRaw(nonce, nonce_len); }
149 	override size_t updateGranularity() const { return super.updateGranularity(); }
150 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
151 	override bool validNonceLength(size_t nonce_len) const { return super.validNonceLength(nonce_len); }
152 	override @property string name() const { return super.name; }
153 	override void clear() { return super.clear(); }
154 };
155 
156 /**
157 * ChaCha20Poly1305 Decryption
158 */
159 final class ChaCha20Poly1305Decryption : ChaCha20Poly1305Mode, Transformation
160 {
161 public:
162 
163 	this() { super(); }
164 	override size_t outputLength(size_t input_length) const { 
165 		assert(input_length > tagSize(), "Sufficient input");
166 		return input_length - tagSize(); 
167 	}
168 	
169 	override size_t minimumFinalSize() const { return 0; }
170 	
171 	override void update(ref SecureVector!ubyte buffer, size_t offset = 0) {
172 		assert(buffer.length >= offset, "Offset is sane");
173 		const size_t sz = buffer.length - offset;
174 		ubyte* buf = buffer.ptr + offset;
175 		
176 		m_poly1305.update(buf, sz); // poly1305 of ciphertext
177 		m_chacha.cipher1(buf, sz);
178 		m_ctext_len += sz;
179 		
180 	}
181 	
182 	override void finish(ref SecureVector!ubyte buffer, size_t offset = 0) {
183 		assert(buffer.length >= offset, "Offset is sane");
184 		const size_t sz = buffer.length - offset;
185 		ubyte* buf = buffer.ptr + offset;
186 		
187 		assert(sz >= tagSize(), "Have the tag as part of final input");
188 		
189 		const size_t remaining = sz - tagSize();
190 		
191 		if(remaining) {
192 			m_poly1305.update(buf, remaining); // poly1305 of ciphertext
193 			m_chacha.cipher1(buf, remaining);
194 			m_ctext_len += remaining;
195 		}
196 		
197 		if(cfrgVersion()) {
198 			for(size_t i = 0; i != 16 - m_ctext_len % 16; ++i)
199 				m_poly1305.update(0);
200 			updateLength(m_ad.length);
201 		}
202 		
203 		updateLength(m_ctext_len);
204 		const SecureVector!ubyte mac = m_poly1305.finished();
205 		
206 		const ubyte* included_tag = buf + remaining;
207 		
208 		m_ctext_len = 0;
209 		
210 		if(!sameMem(mac.ptr, included_tag, tagSize()))
211 			throw new IntegrityFailure("ChaCha20Poly1305 tag check failed");
212 		
213 		buffer.resize(offset + remaining);
214 	}
215 	
216 	// Interface fallthrough
217 	override string provider() const { return "core"; }
218 	override SecureVector!ubyte startRaw(const(ubyte)* nonce, size_t nonce_len) { return super.startRaw(nonce, nonce_len); }
219 	override size_t updateGranularity() const { return super.updateGranularity(); }
220 	override size_t defaultNonceLength() const { return super.defaultNonceLength(); }
221 	override bool validNonceLength(size_t nonce_len) const { return super.validNonceLength(nonce_len); }
222 	override @property string name() const { return super.name; }
223 	override void clear() { return super.clear(); }
224 }
225