1 /**
2 * Interface for AEAD modes
3 * 
4 * Copyright:
5 * (C) 2013 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.aead;
12 import botan.constants;
13 static if (BOTAN_HAS_AEAD_CCM || BOTAN_HAS_AEAD_EAX || BOTAN_HAS_AEAD_GCM || BOTAN_HAS_AEAD_SIV || BOTAN_HAS_AEAD_OCB || BOTAN_HAS_AEAD_CHACHA20_POLY1305):
14 
15 import botan.modes.cipher_mode;
16 import botan.block.block_cipher;
17 import botan.libstate.libstate;
18 import botan.utils.parsing;
19 static if (BOTAN_HAS_AEAD_CCM) import botan.modes.aead.ccm;
20 static if (BOTAN_HAS_AEAD_EAX) import botan.modes.aead.eax;
21 static if (BOTAN_HAS_AEAD_GCM) import botan.modes.aead.gcm;
22 static if (BOTAN_HAS_AEAD_SIV) import botan.modes.aead.siv;
23 static if (BOTAN_HAS_AEAD_OCB) import botan.modes.aead.ocb;
24 static if (BOTAN_HAS_AEAD_CHACHA20_POLY1305) import botan.modes.aead.chacha20poly1305;
25 
26 /**
27 * Interface for AEAD (Authenticated Encryption with Associated Data)
28 * modes. These modes provide both encryption and message
29 * authentication, and can authenticate additional per-message data
30 * which is not included in the ciphertext (for instance a sequence
31 * number).
32 */
33 class AEADMode : CipherMode, Transformation
34 {
35 public:
36     final override bool authenticated() const { return true; }
37 
38     /**
39     * Set associated data that is not included in the ciphertext but
40     * that should be authenticated. Must be called after setKey
41     * and before finish.
42     *
43     * Unless reset by another call, the associated data is kept
44     * between messages. Thus, if the AD does not change, calling
45     * once (after setKey) is the optimum.
46     *
47     * Params:
48     *  ad = the associated data
49     *  ad_len = length of add in bytes
50     */
51     abstract void setAssociatedData(const(ubyte)* ad, size_t ad_len);
52 
53     final void setAssociatedDataVec(Alloc)(const ref Vector!( ubyte, Alloc ) ad)
54     {
55         setAssociatedData(ad.ptr, ad.length);
56     }
57 
58     /**
59     * Default AEAD nonce size (a commonly supported value among AEAD
60     * modes, and large enough that random collisions are unlikely).
61     */
62     override size_t defaultNonceLength() const { return 12; }
63 
64     /**
65     * Return the size of the authentication tag used (in bytes)
66     */
67     abstract size_t tagSize() const;
68 }
69 
70 /**
71 * Get an AEAD mode by name (eg "AES-128/GCM" or "Serpent/EAX")
72 */
73 AEADMode getAead(in string algo_spec, CipherDir direction)
74 {
75 	
76 	static if (BOTAN_HAS_AEAD_CHACHA20_POLY1305) {
77 		if (algo_spec == "ChaCha20Poly1305")
78 		{
79 			if (direction == ENCRYPTION)
80 				return new ChaCha20Poly1305Encryption;
81 			else
82 				return new ChaCha20Poly1305Decryption;
83 		}
84 	}
85     AlgorithmFactory af = globalState().algorithmFactory();
86     
87 	static Vector!string last_algo_parts;
88 	static string last_algo_spec;
89 	Vector!string algo_parts;
90 	if (last_algo_spec == algo_spec)
91 		algo_parts = last_algo_parts.dup;
92 	else {
93 		algo_parts = algo_spec.splitter('/');
94 		last_algo_spec = algo_spec;
95 		last_algo_parts = algo_parts.dup;
96 	}
97 	if (algo_parts.empty)
98         throw new InvalidAlgorithmName(algo_spec);
99     
100     if (algo_parts.length < 2)
101         return null;
102     
103     const string cipher_name = algo_parts[0];
104     const BlockCipher cipher = af.prototypeBlockCipher(cipher_name);
105     if (!cipher)
106         return null;
107 
108 	static Vector!string last_mode_info;
109 	static string last_algo_part_1;
110 	Vector!string mode_info;
111 	if (last_algo_part_1 == algo_parts[1])
112 		mode_info = last_mode_info.dup;
113 	else {
114 		mode_info = parseAlgorithmName(algo_parts[1]);
115 		last_mode_info = mode_info.dup;
116 		last_algo_part_1 = algo_parts[1];
117 	}
118 
119     if (mode_info.empty)
120         return null;
121     
122     const string mode_name = mode_info[0];
123     
124     const size_t tag_size = (mode_info.length > 1) ? to!uint(mode_info[1]) : cipher.blockSize();
125     
126     static if (BOTAN_HAS_AEAD_CCM) {
127         if (mode_name == "CCM-8")
128         {
129             if (direction == ENCRYPTION)
130                 return new CCMEncryption(cipher.clone(), 8, 3);
131             else
132                 return new CCMDecryption(cipher.clone(), 8, 3);
133         }
134         
135         if (mode_name == "CCM" || mode_name == "CCM-8")
136         {
137             const size_t L = (mode_info.length > 2) ? to!uint(mode_info[2]) : 3;
138             
139             if (direction == ENCRYPTION)
140                 return new CCMEncryption(cipher.clone(), tag_size, L);
141             else
142                 return new CCMDecryption(cipher.clone(), tag_size, L);
143         }
144     }
145     
146     static if (BOTAN_HAS_AEAD_EAX) {
147         if (mode_name == "EAX")
148         {
149             if (direction == ENCRYPTION)
150                 return new EAXEncryption(cipher.clone(), tag_size);
151             else
152                 return new EAXDecryption(cipher.clone(), tag_size);
153         }
154     }
155     
156     static if (BOTAN_HAS_AEAD_SIV) {
157         if (mode_name == "SIV")
158         {
159             assert(tag_size == 16, "Valid tag size for SIV");
160             if (direction == ENCRYPTION)
161                 return new SIVEncryption(cipher.clone());
162             else
163                 return new SIVDecryption(cipher.clone());
164         }
165     }
166     
167     static if (BOTAN_HAS_AEAD_GCM) {
168         if (mode_name == "GCM")
169         {
170             if (direction == ENCRYPTION)
171                 return new GCMEncryption(cipher.clone(), tag_size);
172             else
173                 return new GCMDecryption(cipher.clone(), tag_size);
174         }
175     }
176 
177     static if (BOTAN_HAS_AEAD_OCB) {
178         if (mode_name == "OCB")
179         {
180             if (direction == ENCRYPTION)
181                 return new OCBEncryption(cipher.clone(), tag_size);
182             else
183                 return new OCBDecryption(cipher.clone(), tag_size);
184         }
185     }
186     
187     return null;
188 }
189 
190 static if (BOTAN_TEST):
191 
192 import botan.test;
193 import botan.codec.hex;
194 import core.atomic;
195 shared size_t total_tests;
196 import memutils.hashmap;
197 
198 size_t aeadTest(string algo, string input, string expected, string nonce_hex, string ad_hex, string key_hex)
199 {
200     atomicOp!"+="(total_tests, 5);
201     const SecureVector!ubyte nonce = hexDecodeLocked(nonce_hex);
202     const SecureVector!ubyte ad = hexDecodeLocked(ad_hex);
203     const SecureVector!ubyte key = hexDecodeLocked(key_hex);
204     Unique!CipherMode enc = getAead(algo, ENCRYPTION);
205     Unique!CipherMode dec = getAead(algo, DECRYPTION);
206     if (!enc || !dec)
207         throw new Exception("Unknown AEAD " ~ algo);
208     
209     enc.setKey(key);
210     dec.setKey(key);
211     if (auto aead_enc = cast(AEADMode)(*enc))
212         aead_enc.setAssociatedDataVec(ad);
213     if (auto aead_dec = cast(AEADMode)(*dec))
214         aead_dec.setAssociatedDataVec(ad);
215     
216     size_t fail = 0;
217     
218     const SecureVector!ubyte pt = hexDecodeLocked(input);
219     const SecureVector!ubyte expected_ct = hexDecodeLocked(expected);
220     
221     SecureVector!ubyte vec = pt.dup;
222     enc.start(nonce);
223 
224     // should first update if possible
225     enc.finish(vec);
226     if (vec != expected_ct)
227     {
228         logError("1: ", algo ~ " got ct " ~ hexEncode(vec) ~ " expected " ~ expected);
229         logError(algo ~ " \n");
230         ++fail;
231     }
232     
233     vec = expected_ct.dup;
234     
235     dec.start(nonce);
236     dec.finish(vec);
237     
238     if (vec != pt)
239     {
240         logError("2: ", algo ~ " got pt " ~ hexEncode(vec) ~ " expected " ~ input);
241         ++fail;
242     }
243     
244     if (enc.authenticated())
245     {
246         vec = expected_ct.dup;
247         vec[0] ^= 1;
248         dec.start(nonce);
249         try
250         {
251             dec.finish(vec);
252             logError(algo ~ " accepted message with modified message");
253             ++fail;
254         }
255         catch (Exception) {}
256         
257         if (nonce.length)
258         {
259             auto bad_nonce = nonce.dup;
260             bad_nonce[0] ^= 1;
261             vec = expected_ct.dup;
262             
263             dec.start(bad_nonce);
264             
265             try
266             {
267                 dec.finish(vec);
268                 logError(algo ~ " accepted message with modified nonce");
269                 ++fail;
270             }
271             catch (Exception) {}
272         }
273         
274         if (auto aead_dec = cast(AEADMode)(*dec))
275         {
276             SecureVector!ubyte bad_ad = ad.dup;
277             
278             if (ad.length) {
279                 bad_ad[0] ^= 1;
280             }
281             else {
282                 bad_ad.pushBack(0);
283             }
284             
285             aead_dec.setAssociatedDataVec(bad_ad);
286             
287             vec = expected_ct.dup;
288             dec.start(nonce);
289             
290             try
291             {
292                 dec.finish(vec);
293                 logError(algo ~ " accepted message with modified AD");
294                 ++fail;
295             }
296             catch (Exception) {}
297         }
298     }
299     return fail;
300 }
301 
302 static if (BOTAN_HAS_TESTS && !SKIP_AEAD_TEST) unittest
303 {
304     logDebug("Testing aead.d ...");
305     auto test = delegate(string input)
306     {
307         File vec = File(input, "r");
308         
309         return runTestsBb(vec, "AEAD", "Out", true,
310             (ref HashMap!(string, string) m)
311             {
312                 return aeadTest(m["AEAD"], m["In"], m["Out"], m.get("Nonce"), m.get("AD"), m["Key"]);
313             });
314     };
315     
316     size_t fails = runTestsInDir("test_data/aead", test);
317     logDebug("Test report");
318     testReport("aead", total_tests, fails);
319 }