diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py index 26eea06c5b7ef9f51cf9c3ced36ff1f52e3f268c..1e4c764487e62b6669339148b3cb61b0423f9c2e 100644 --- a/scapy/layers/ipsec.py +++ b/scapy/layers/ipsec.py @@ -183,7 +183,7 @@ class CryptAlgo(object): """ def __init__(self, name, cipher, mode, block_size=None, iv_size=None, - key_size=None, icv_size=None, salt_size=None): + key_size=None, icv_size=None, salt_size=None, format_mode_iv=None): """ @param name: the name of this encryption algorithm @param cipher: a Cipher module @@ -199,6 +199,9 @@ class CryptAlgo(object): Used by Combined Mode Algorithms e.g. GCM @param salt_size: the length of the salt to use as the IV prefix. Usually used by Counter modes e.g. CTR + @param format_mode_iv: function to format the Initialization Vector + e.g. handle the salt value + Default is the random buffer from `generate_iv` """ self.name = name self.cipher = cipher @@ -235,6 +238,11 @@ class CryptAlgo(object): else: self.salt_size = salt_size + if format_mode_iv is None: + self._format_mode_iv = lambda iv, **kw: iv + else: + self._format_mode_iv = format_mode_iv + def check_key(self, key): """ Check that the key length is valid. @@ -251,17 +259,17 @@ class CryptAlgo(object): """ # XXX: Handle counter modes with real counters? RFCs allow the use of # XXX: random bytes for counters, so it is not wrong to do it that way - return os.urandom(self.iv_size - self.salt_size) + return os.urandom(self.iv_size) @crypto_validator - def new_cipher(self, key, iv, digest=None): + def new_cipher(self, key, mode_iv, digest=None): """ - @param key: the secret key, a byte string - @param iv: the initialization vector, a byte string. Used as the - initial nonce in counter mode - @param digest: also known as tag or icv. A byte string containing the - digest of the encrypted data. Only use this during - decryption! + @param key: the secret key, a byte string + @param mode_iv: the initialization vector or nonce, a byte string. + Formatted by `format_mode_iv`. + @param digest: also known as tag or icv. A byte string containing the + digest of the encrypted data. Only use this during + decryption! @return: an initialized cipher object for this algo """ @@ -269,13 +277,13 @@ class CryptAlgo(object): # With AEAD, the mode needs the digest during decryption. return Cipher( self.cipher(key), - self.mode(iv, digest, len(digest)), + self.mode(mode_iv, digest, len(digest)), default_backend(), ) else: return Cipher( self.cipher(key), - self.mode(iv), + self.mode(mode_iv), default_backend(), ) @@ -314,10 +322,11 @@ class CryptAlgo(object): return esp - def encrypt(self, esp, key): + def encrypt(self, sa, esp, key): """ Encrypt an ESP packet + @param sa: the SecurityAssociation associated with the ESP packet. @param esp: an unencrypted _ESPPlain packet with valid padding @param key: the secret key used for encryption @@ -326,7 +335,8 @@ class CryptAlgo(object): data = esp.data_for_encryption() if self.cipher: - cipher = self.new_cipher(key, esp.iv) + mode_iv = self._format_mode_iv(algo=self, sa=sa, iv=esp.iv) + cipher = self.new_cipher(key, mode_iv) encryptor = cipher.encryptor() if self.is_aead: @@ -339,10 +349,11 @@ class CryptAlgo(object): return ESP(spi=esp.spi, seq=esp.seq, data=esp.iv + data) - def decrypt(self, esp, key, icv_size=None): + def decrypt(self, sa, esp, key, icv_size=None): """ Decrypt an ESP packet + @param sa: the SecurityAssociation associated with the ESP packet. @param esp: an encrypted ESP packet @param key: the secret key used for encryption @param icv_size: the length of the icv used for integrity check @@ -359,7 +370,8 @@ class CryptAlgo(object): icv = esp.data[len(esp.data) - icv_size:] if self.cipher: - cipher = self.new_cipher(key, iv, icv) + mode_iv = self._format_mode_iv(sa=sa, iv=iv) + cipher = self.new_cipher(key, mode_iv, icv) decryptor = cipher.decryptor() if self.is_aead: @@ -402,20 +414,29 @@ if algorithms: CRYPT_ALGOS['AES-CBC'] = CryptAlgo('AES-CBC', cipher=algorithms.AES, mode=modes.CBC) + _aes_ctr_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv + b'\x00\x00\x00\x01' CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR', cipher=algorithms.AES, mode=modes.CTR, - salt_size=4) + iv_size=8, + salt_size=4, + format_mode_iv=_aes_ctr_format_mode_iv) + _salt_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM', cipher=algorithms.AES, mode=modes.GCM, salt_size=4, - icv_size=16) + iv_size=8, + icv_size=16, + format_mode_iv=_salt_format_mode_iv) if hasattr(modes, 'CCM'): CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM', cipher=algorithms.AES, mode=modes.CCM, - icv_size=16) + iv_size=8, + salt_size=3, + icv_size=16, + format_mode_iv=_salt_format_mode_iv) # XXX: Flagged as weak by 'cryptography'. Kept for backward compatibility CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish', cipher=algorithms.Blowfish, @@ -806,8 +827,6 @@ class SecurityAssociation(object): if iv is None: iv = self.crypt_algo.generate_iv() - if self.crypt_salt: - iv = self.crypt_salt + iv else: if len(iv) != self.crypt_algo.iv_size: raise TypeError('iv length must be %s' % self.crypt_algo.iv_size) @@ -832,7 +851,7 @@ class SecurityAssociation(object): esp.nh = nh esp = self.crypt_algo.pad(esp) - esp = self.crypt_algo.encrypt(esp, self.crypt_key) + esp = self.crypt_algo.encrypt(self, esp, self.crypt_key) self.auth_algo.sign(esp, self.auth_key) @@ -938,7 +957,7 @@ class SecurityAssociation(object): self.check_spi(pkt) self.auth_algo.verify(encrypted, self.auth_key) - esp = self.crypt_algo.decrypt(encrypted, self.crypt_key, + esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key, self.crypt_algo.icv_size or self.auth_algo.icv_size)