Skip to content

Commit d530da6

Browse files
ThomasFaivreguedou
authored andcommitted
layers/ipsec: fix iv handling in special modes
There is a distinction to be made between the IV generated using CryptAlgo.generate_iv and the IV given as argument to the cipher mode in CryptAlgo.new_cipher. The first one is random string which is sent with the ESP packet (first bytes of the data field). The cipher mode only affects the size of the string in our implementation (some modes like GCM may implement a counter instead of pure random). And the second is a combination of the salt, the ESP iv and possibly other things. This can vary a lot depending on the mode. Add an attribute to CryptAlgo to give a function computing this "second" IV based on SA information. Signed-off-by: Thomas Faivre <thomas.faivre@6wind.com>
1 parent a0fd868 commit d530da6

File tree

1 file changed

+41
-22
lines changed

1 file changed

+41
-22
lines changed

scapy/layers/ipsec.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class CryptAlgo(object):
183183
"""
184184

185185
def __init__(self, name, cipher, mode, block_size=None, iv_size=None,
186-
key_size=None, icv_size=None, salt_size=None):
186+
key_size=None, icv_size=None, salt_size=None, format_mode_iv=None):
187187
"""
188188
@param name: the name of this encryption algorithm
189189
@param cipher: a Cipher module
@@ -199,6 +199,9 @@ def __init__(self, name, cipher, mode, block_size=None, iv_size=None,
199199
Used by Combined Mode Algorithms e.g. GCM
200200
@param salt_size: the length of the salt to use as the IV prefix.
201201
Usually used by Counter modes e.g. CTR
202+
@param format_mode_iv: function to format the Initialization Vector
203+
e.g. handle the salt value
204+
Default is the random buffer from `generate_iv`
202205
"""
203206
self.name = name
204207
self.cipher = cipher
@@ -235,6 +238,11 @@ def __init__(self, name, cipher, mode, block_size=None, iv_size=None,
235238
else:
236239
self.salt_size = salt_size
237240

241+
if format_mode_iv is None:
242+
self._format_mode_iv = lambda iv, **kw: iv
243+
else:
244+
self._format_mode_iv = format_mode_iv
245+
238246
def check_key(self, key):
239247
"""
240248
Check that the key length is valid.
@@ -251,31 +259,31 @@ def generate_iv(self):
251259
"""
252260
# XXX: Handle counter modes with real counters? RFCs allow the use of
253261
# XXX: random bytes for counters, so it is not wrong to do it that way
254-
return os.urandom(self.iv_size - self.salt_size)
262+
return os.urandom(self.iv_size)
255263

256264
@crypto_validator
257-
def new_cipher(self, key, iv, digest=None):
265+
def new_cipher(self, key, mode_iv, digest=None):
258266
"""
259-
@param key: the secret key, a byte string
260-
@param iv: the initialization vector, a byte string. Used as the
261-
initial nonce in counter mode
262-
@param digest: also known as tag or icv. A byte string containing the
263-
digest of the encrypted data. Only use this during
264-
decryption!
267+
@param key: the secret key, a byte string
268+
@param mode_iv: the initialization vector or nonce, a byte string.
269+
Formatted by `format_mode_iv`.
270+
@param digest: also known as tag or icv. A byte string containing the
271+
digest of the encrypted data. Only use this during
272+
decryption!
265273
266274
@return: an initialized cipher object for this algo
267275
"""
268276
if self.is_aead and digest is not None:
269277
# With AEAD, the mode needs the digest during decryption.
270278
return Cipher(
271279
self.cipher(key),
272-
self.mode(iv, digest, len(digest)),
280+
self.mode(mode_iv, digest, len(digest)),
273281
default_backend(),
274282
)
275283
else:
276284
return Cipher(
277285
self.cipher(key),
278-
self.mode(iv),
286+
self.mode(mode_iv),
279287
default_backend(),
280288
)
281289

@@ -314,10 +322,11 @@ def pad(self, esp):
314322

315323
return esp
316324

317-
def encrypt(self, esp, key):
325+
def encrypt(self, sa, esp, key):
318326
"""
319327
Encrypt an ESP packet
320328
329+
@param sa: the SecurityAssociation associated with the ESP packet.
321330
@param esp: an unencrypted _ESPPlain packet with valid padding
322331
@param key: the secret key used for encryption
323332
@@ -326,7 +335,8 @@ def encrypt(self, esp, key):
326335
data = esp.data_for_encryption()
327336

328337
if self.cipher:
329-
cipher = self.new_cipher(key, esp.iv)
338+
mode_iv = self._format_mode_iv(algo=self, sa=sa, iv=esp.iv)
339+
cipher = self.new_cipher(key, mode_iv)
330340
encryptor = cipher.encryptor()
331341

332342
if self.is_aead:
@@ -339,10 +349,11 @@ def encrypt(self, esp, key):
339349

340350
return ESP(spi=esp.spi, seq=esp.seq, data=esp.iv + data)
341351

342-
def decrypt(self, esp, key, icv_size=None):
352+
def decrypt(self, sa, esp, key, icv_size=None):
343353
"""
344354
Decrypt an ESP packet
345355
356+
@param sa: the SecurityAssociation associated with the ESP packet.
346357
@param esp: an encrypted ESP packet
347358
@param key: the secret key used for encryption
348359
@param icv_size: the length of the icv used for integrity check
@@ -359,7 +370,8 @@ def decrypt(self, esp, key, icv_size=None):
359370
icv = esp.data[len(esp.data) - icv_size:]
360371

361372
if self.cipher:
362-
cipher = self.new_cipher(key, iv, icv)
373+
mode_iv = self._format_mode_iv(sa=sa, iv=iv)
374+
cipher = self.new_cipher(key, mode_iv, icv)
363375
decryptor = cipher.decryptor()
364376

365377
if self.is_aead:
@@ -402,20 +414,29 @@ def decrypt(self, esp, key, icv_size=None):
402414
CRYPT_ALGOS['AES-CBC'] = CryptAlgo('AES-CBC',
403415
cipher=algorithms.AES,
404416
mode=modes.CBC)
417+
_aes_ctr_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv + b'\x00\x00\x00\x01'
405418
CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR',
406419
cipher=algorithms.AES,
407420
mode=modes.CTR,
408-
salt_size=4)
421+
iv_size=8,
422+
salt_size=4,
423+
format_mode_iv=_aes_ctr_format_mode_iv)
424+
_salt_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv
409425
CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM',
410426
cipher=algorithms.AES,
411427
mode=modes.GCM,
412428
salt_size=4,
413-
icv_size=16)
429+
iv_size=8,
430+
icv_size=16,
431+
format_mode_iv=_salt_format_mode_iv)
414432
if hasattr(modes, 'CCM'):
415433
CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM',
416434
cipher=algorithms.AES,
417435
mode=modes.CCM,
418-
icv_size=16)
436+
iv_size=8,
437+
salt_size=3,
438+
icv_size=16,
439+
format_mode_iv=_salt_format_mode_iv)
419440
# XXX: Flagged as weak by 'cryptography'. Kept for backward compatibility
420441
CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish',
421442
cipher=algorithms.Blowfish,
@@ -806,8 +827,6 @@ def _encrypt_esp(self, pkt, seq_num=None, iv=None):
806827

807828
if iv is None:
808829
iv = self.crypt_algo.generate_iv()
809-
if self.crypt_salt:
810-
iv = self.crypt_salt + iv
811830
else:
812831
if len(iv) != self.crypt_algo.iv_size:
813832
raise TypeError('iv length must be %s' % self.crypt_algo.iv_size)
@@ -832,7 +851,7 @@ def _encrypt_esp(self, pkt, seq_num=None, iv=None):
832851
esp.nh = nh
833852

834853
esp = self.crypt_algo.pad(esp)
835-
esp = self.crypt_algo.encrypt(esp, self.crypt_key)
854+
esp = self.crypt_algo.encrypt(self, esp, self.crypt_key)
836855

837856
self.auth_algo.sign(esp, self.auth_key)
838857

@@ -938,7 +957,7 @@ def _decrypt_esp(self, pkt, verify=True):
938957
self.check_spi(pkt)
939958
self.auth_algo.verify(encrypted, self.auth_key)
940959

941-
esp = self.crypt_algo.decrypt(encrypted, self.crypt_key,
960+
esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key,
942961
self.crypt_algo.icv_size or
943962
self.auth_algo.icv_size)
944963

0 commit comments

Comments
 (0)