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     const Vector!string algo_parts = algo_spec.splitter('/');
88     if (algo_parts.empty)
89         throw new InvalidAlgorithmName(algo_spec);
90     
91     if (algo_parts.length < 2)
92         return null;
93     
94     const string cipher_name = algo_parts[0];
95     const BlockCipher cipher = af.prototypeBlockCipher(cipher_name);
96     if (!cipher)
97         return null;
98     const Vector!string mode_info = parseAlgorithmName(algo_parts[1]);
99     if (mode_info.empty)
100         return null;
101     
102     const string mode_name = mode_info[0];
103     
104     const size_t tag_size = (mode_info.length > 1) ? to!uint(mode_info[1]) : cipher.blockSize();
105     
106     static if (BOTAN_HAS_AEAD_CCM) {
107         if (mode_name == "CCM-8")
108         {
109             if (direction == ENCRYPTION)
110                 return new CCMEncryption(cipher.clone(), 8, 3);
111             else
112                 return new CCMDecryption(cipher.clone(), 8, 3);
113         }
114         
115         if (mode_name == "CCM" || mode_name == "CCM-8")
116         {
117             const size_t L = (mode_info.length > 2) ? to!uint(mode_info[2]) : 3;
118             
119             if (direction == ENCRYPTION)
120                 return new CCMEncryption(cipher.clone(), tag_size, L);
121             else
122                 return new CCMDecryption(cipher.clone(), tag_size, L);
123         }
124     }
125     
126     static if (BOTAN_HAS_AEAD_EAX) {
127         if (mode_name == "EAX")
128         {
129             if (direction == ENCRYPTION)
130                 return new EAXEncryption(cipher.clone(), tag_size);
131             else
132                 return new EAXDecryption(cipher.clone(), tag_size);
133         }
134     }
135     
136     static if (BOTAN_HAS_AEAD_SIV) {
137         if (mode_name == "SIV")
138         {
139             assert(tag_size == 16, "Valid tag size for SIV");
140             if (direction == ENCRYPTION)
141                 return new SIVEncryption(cipher.clone());
142             else
143                 return new SIVDecryption(cipher.clone());
144         }
145     }
146     
147     static if (BOTAN_HAS_AEAD_GCM) {
148         if (mode_name == "GCM")
149         {
150             if (direction == ENCRYPTION)
151                 return new GCMEncryption(cipher.clone(), tag_size);
152             else
153                 return new GCMDecryption(cipher.clone(), tag_size);
154         }
155     }
156 
157     static if (BOTAN_HAS_AEAD_OCB) {
158         if (mode_name == "OCB")
159         {
160             if (direction == ENCRYPTION)
161                 return new OCBEncryption(cipher.clone(), tag_size);
162             else
163                 return new OCBDecryption(cipher.clone(), tag_size);
164         }
165     }
166     
167     return null;
168 }
169 
170 static if (BOTAN_TEST):
171 
172 import botan.test;
173 import botan.codec.hex;
174 import core.atomic;
175 shared size_t total_tests;
176 import memutils.hashmap;
177 
178 size_t aeadTest(string algo, string input, string expected, string nonce_hex, string ad_hex, string key_hex)
179 {
180     atomicOp!"+="(total_tests, 5);
181     const SecureVector!ubyte nonce = hexDecodeLocked(nonce_hex);
182     const SecureVector!ubyte ad = hexDecodeLocked(ad_hex);
183     const SecureVector!ubyte key = hexDecodeLocked(key_hex);
184     Unique!CipherMode enc = getAead(algo, ENCRYPTION);
185     Unique!CipherMode dec = getAead(algo, DECRYPTION);
186     if (!enc || !dec)
187         throw new Exception("Unknown AEAD " ~ algo);
188     
189     enc.setKey(key);
190     dec.setKey(key);
191     if (auto aead_enc = cast(AEADMode)(*enc))
192         aead_enc.setAssociatedDataVec(ad);
193     if (auto aead_dec = cast(AEADMode)(*dec))
194         aead_dec.setAssociatedDataVec(ad);
195     
196     size_t fail = 0;
197     
198     const SecureVector!ubyte pt = hexDecodeLocked(input);
199     const SecureVector!ubyte expected_ct = hexDecodeLocked(expected);
200     
201     SecureVector!ubyte vec = pt.dup;
202     enc.start(nonce);
203 
204     // should first update if possible
205     enc.finish(vec);
206     if (vec != expected_ct)
207     {
208         logError("1: ", algo ~ " got ct " ~ hexEncode(vec) ~ " expected " ~ expected);
209         logError(algo ~ " \n");
210         ++fail;
211     }
212     
213     vec = expected_ct.dup;
214     
215     dec.start(nonce);
216     dec.finish(vec);
217     
218     if (vec != pt)
219     {
220         logError("2: ", algo ~ " got pt " ~ hexEncode(vec) ~ " expected " ~ input);
221         ++fail;
222     }
223     
224     if (enc.authenticated())
225     {
226         vec = expected_ct.dup;
227         vec[0] ^= 1;
228         dec.start(nonce);
229         try
230         {
231             dec.finish(vec);
232             logError(algo ~ " accepted message with modified message");
233             ++fail;
234         }
235         catch (Exception) {}
236         
237         if (nonce.length)
238         {
239             auto bad_nonce = nonce.dup;
240             bad_nonce[0] ^= 1;
241             vec = expected_ct.dup;
242             
243             dec.start(bad_nonce);
244             
245             try
246             {
247                 dec.finish(vec);
248                 logError(algo ~ " accepted message with modified nonce");
249                 ++fail;
250             }
251             catch (Exception) {}
252         }
253         
254         if (auto aead_dec = cast(AEADMode)(*dec))
255         {
256             SecureVector!ubyte bad_ad = ad.dup;
257             
258             if (ad.length) {
259                 bad_ad[0] ^= 1;
260             }
261             else {
262                 bad_ad.pushBack(0);
263             }
264             
265             aead_dec.setAssociatedDataVec(bad_ad);
266             
267             vec = expected_ct.dup;
268             dec.start(nonce);
269             
270             try
271             {
272                 dec.finish(vec);
273                 logError(algo ~ " accepted message with modified AD");
274                 ++fail;
275             }
276             catch (Exception) {}
277         }
278     }
279     return fail;
280 }
281 
282 static if (BOTAN_HAS_TESTS && !SKIP_AEAD_TEST) unittest
283 {
284     logDebug("Testing aead.d ...");
285     auto test = delegate(string input)
286     {
287         File vec = File(input, "r");
288         
289         return runTestsBb(vec, "AEAD", "Out", true,
290             (ref HashMap!(string, string) m)
291             {
292                 return aeadTest(m["AEAD"], m["In"], m["Out"], m.get("Nonce"), m.get("AD"), m["Key"]);
293             });
294     };
295     
296     size_t fails = runTestsInDir("../test_data/aead", test);
297     logDebug("Test report");
298     testReport("aead", total_tests, fails);
299 }