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 }