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 }