Repository: secdev/scapy

Issue:
IPsec GCM padding wrong.
#### Things to consider

#### Brief description

ipsec.py incorrectly uses AES block size (16) to pad ESP with GCM (at least). ESP with GCM does not require this padding, and it could result in packets not being delivered (e.g., MTU violation).

https://www.rfc-editor.org/rfc/rfc4106.txt

```
   Implementations that do not seek to hide the length of the plaintext
   SHOULD use the minimum amount of padding required, which will be less
   than four octets.
```

#### How to reproduce

Setup a GCM-256 based IPsec SA and construct a packet with an ipsec payload of 1446 which should result in a IPsec packet of lenth 1500. 

1446 + 20 (iphdr) + 8 (esphdr) + 2 (espfoot) + 8(iv) + 16(icv) = 1500

#### Actual result

IPsec packet described above is incorrectly padded 8 bytes resulting in a packet 1508 bytes long.

#### Expected result

IPsec packet should be 1500 bytes long.

#### Related resources



Discussion:
Thanks for the report.

Do you think you could provide a code snippet (or even better a PR) that reproduces (/fixes) precisely the issue ?
I brought up this issue with my team and we confirm the behavior expected by @choppsv1. Current implementation always pads to the cipher's blocksize, which is not _necessary_ in some cases, such as AES-GCM.

I'll try to come up with a PR when I can make some time for it.
@g-mahieux thanks!

Resolving patch:
diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py
--- a/scapy/layers/ipsec.py
+++ b/scapy/layers/ipsec.py
@@ -446,6 +446,7 @@ def decrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0):
     CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR',
                                        cipher=algorithms.AES,
                                        mode=modes.CTR,
+                                       block_size=1,
                                        iv_size=8,
                                        salt_size=4,
                                        format_mode_iv=_aes_ctr_format_mode_iv)
@@ -454,6 +455,7 @@ def decrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0):
                                        cipher=algorithms.AES,
                                        mode=modes.GCM,
                                        salt_size=4,
+                                       block_size=1,
                                        iv_size=8,
                                        icv_size=16,
                                        format_mode_iv=_salt_format_mode_iv)
@@ -461,6 +463,7 @@ def decrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0):
         CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM',
                                            cipher=algorithms.AES,
                                            mode=modes.CCM,
+                                           block_size=1,
                                            iv_size=8,
                                            salt_size=3,
                                            icv_size=16,


Test patch:
diff --git a/test/scapy/layers/ipsec.uts b/test/scapy/layers/ipsec.uts
--- a/test/scapy/layers/ipsec.uts
+++ b/test/scapy/layers/ipsec.uts
@@ -1991,6 +1991,77 @@ try:
 except IPSecIntegrityError as err:
     err    
 
+#######################################
+= IPv4 / ESP - Tunnel - AES-CTR - NULL - verify no cipher align padding
+
+p = IP(src='1.1.1.1', dst='2.2.2.2')
+p /= UDP(sport=1000, dport=1001)
+p /= Raw("\x00" * 3)
+p
+print("len p: {}".format(len(p)))
+
+# oiphdr esphdr iiphdr udphdr data esptail iv
+#   20  +  8   +  20  +  8   + 3   +  2   + 8 = 69
+# CipherInput: iiphdr udphdr data esptail
+#                20  +  8   + 3   +  2   = 33
+#   good: (33 % 4) == 1, pad == (4-1) == 3, len == 36+33+3 == 72
+#   bad: (33 % 16) == 1, pad == (16-1) == 15, len == 36+33+15 == 84
+
+sa = SecurityAssociation(ESP, spi=0x222,
+                         crypt_algo='AES-CTR', crypt_key=b'16bytekey+4bytenonce',
+                         auth_algo='NULL', auth_key=None,
+                         tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'))
+print("crypt_algo.icv_size {}".format(sa.crypt_algo.icv_size))
+print("auth_algo.icv_size {}".format(sa.auth_algo.icv_size))
+
+e = sa.encrypt(p)
+e
+print("len e: {}".format(len(e)))
+
+esp = sa.crypt_algo.decrypt(sa, e[ESP], sa.crypt_key,
+                            sa.crypt_algo.icv_size or
+                            sa.auth_algo.icv_size,
+                            esn_en=sa.esn_en,
+                            esn=sa.esn)
+esp
+print("len(esp.data): {}".format(len(esp.data)))
+
+* after encryption packet should be padded for 4 byte alignment
+assert len(e) == 72 and esp.padlen == 3, "bad length/padding {}/{}".format(len(e), esp.padlen)
+
+#######################################
+= IPv4 / ESP - Tunnel - AES-GCM - NULL - verify no cipher align padding
+
+p = IP(src='1.1.1.1', dst='2.2.2.2')
+p /= UDP(sport=1000, dport=1001)
+p /= Raw("\x00" * 1418)
+print("len p: {}".format(len(p)))
+
+# oiphdr esphdr iiphdr udphdr data esptail iv icv
+#   20  +  8   +  20  +  8   +1418 + 2    +8 +16 = 1500
+# CipherInput: iiphdr udphdr data esptail
+#                20  +  8   +1418 + 2 = 1448
+#   good: (1448 % 4) == 0, pad == 0, len == 52+1448+0 == 1500
+#   bad: (1448 % 16) == 8, pad == (16-8) == 8, len == 52+1448+8 == 1508
+
+sa = SecurityAssociation(ESP, spi=0x222,
+                         crypt_algo='AES-GCM', crypt_key=b'16bytekey+4bytenonce',
+                         auth_algo='NULL', auth_key=None,
+                         tunnel_header=IP(src='11.11.11.11', dst='22.22.22.22'))
+
+e = sa.encrypt(p)
+print("len e: {}".format(len(e)))
+
+esp = sa.crypt_algo.decrypt(sa, e[ESP], sa.crypt_key,
+                            sa.crypt_algo.icv_size or
+                            sa.auth_algo.icv_size,
+                            esn_en=sa.esn_en,
+                            esn=sa.esn)
+print("len(esp.data): {}".format(len(esp.data)))
+
+* after encryption packet should be padded for 4 byte alignment
+assert len(e) == 1500 and esp.padlen == 0, "bad length/padding {}/{}".format(len(e), esp.padlen)
+
 #######################################
 = IPv4 / ESP - Tunnel - AES-CCM - NULL
 ~ crypto_advanced