aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorChristian Hopps <chopps@labn.net>2019-11-03 07:02:15 -0500
committerDamjan Marion <dmarion@me.com>2020-09-07 09:43:27 +0000
commitfb7e7ed2cd10446d5ecd1b1e8df470e706c448ed (patch)
tree2590e834f09c2d936f83d9bc766af99e084a522f /test
parentdce44e4e2302042890dc8b579d4ff6f99509ae7a (diff)
ipsec: fix padding/alignment for native IPsec encryption
Not all ESP crypto algorithms require padding/alignment to be the same as AES block/IV size. CCM, CTR and GCM all have no padding/alignment requirements, and the RFCs indicate that no padding (beyond ESPs 4 octet alignment requirement) should be used unless TFC (traffic flow confidentiality) has been requested. CTR: https://tools.ietf.org/html/rfc3686#section-3.2 GCM: https://tools.ietf.org/html/rfc4106#section-3.2 CCM: https://tools.ietf.org/html/rfc4309#section-3.2 - VPP is incorrectly using the IV/AES block size to pad CTR and GCM. These modes do not require padding (beyond ESPs 4 octet requirement), as a result packets will have unnecessary padding, which will waste bandwidth at least and possibly fail certain network configurations that have finely tuned MTU configurations at worst. Fix this as well as changing the field names from ".*block_size" to ".*block_align" to better represent their actual (and only) use. Rename "block_sz" in esp_encrypt to "esp_align" and set it correctly as well. test: ipsec: Add unit-test to test for RFC correct padding/alignment test: patch scapy to not incorrectly pad ccm, ctr, gcm modes as well - Scapy is also incorrectly using the AES block size of 16 to pad CCM, CTR, and GCM cipher modes. A bug report has been opened with the and acknowledged with the upstream scapy project as well: https://github.com/secdev/scapy/issues/2322 Ticket: VPP-1928 Type: fix Signed-off-by: Christian Hopps <chopps@labn.net> Change-Id: Iaa4d6a325a2e99fdcb2c375a3395bcfe7947770e
Diffstat (limited to 'test')
-rw-r--r--test/patches/scapy-2.4.3/ipsec.patch54
-rw-r--r--test/patches/scapy-2.4/ipsec.patch52
-rw-r--r--test/template_ipsec.py14
-rw-r--r--test/test_ipsec_esp.py14
4 files changed, 101 insertions, 33 deletions
diff --git a/test/patches/scapy-2.4.3/ipsec.patch b/test/patches/scapy-2.4.3/ipsec.patch
index 7ee8316bce3..d97de7fe9b7 100644
--- a/test/patches/scapy-2.4.3/ipsec.patch
+++ b/test/patches/scapy-2.4.3/ipsec.patch
@@ -1,5 +1,5 @@
diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py
-index f8c601fa..f566d288 100644
+index ae057ee1..24a1f6ea 100644
--- a/scapy/layers/ipsec.py
+++ b/scapy/layers/ipsec.py
@@ -138,6 +138,7 @@ bind_layers(IP, ESP, proto=socket.IPPROTO_ESP)
@@ -9,8 +9,8 @@ index f8c601fa..f566d288 100644
+bind_layers(UDP, ESP, dport=4545) # NAT-Traversal encapsulation - random port
###############################################################################
-
-@@ -359,11 +359,8 @@ class CryptAlgo(object):
+
+@@ -359,11 +360,8 @@ class CryptAlgo(object):
encryptor = cipher.encryptor()
if self.is_aead:
@@ -24,7 +24,7 @@ index f8c601fa..f566d288 100644
data = encryptor.update(data) + encryptor.finalize()
data += encryptor.tag[:self.icv_size]
else:
-@@ -401,12 +398,7 @@ class CryptAlgo(object):
+@@ -400,12 +398,7 @@ class CryptAlgo(object):
if self.is_aead:
# Tag value check is done during the finalize method
@@ -38,7 +38,31 @@ index f8c601fa..f566d288 100644
try:
data = decryptor.update(data) + decryptor.finalize()
except InvalidTag as err:
-@@ -545,7 +537,7 @@ class AuthAlgo(object):
+@@ -445,6 +438,7 @@ if algorithms:
+ 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)
+@@ -452,6 +446,7 @@ if algorithms:
+ CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM',
+ cipher=algorithms.AES,
+ mode=modes.GCM,
++ block_size=1,
+ salt_size=4,
+ iv_size=8,
+ icv_size=16,
+@@ -460,6 +455,7 @@ if algorithms:
+ 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,
+@@ -544,7 +540,7 @@ class AuthAlgo(object):
else:
return self.mac(key, self.digestmod(), default_backend())
@@ -47,7 +71,7 @@ index f8c601fa..f566d288 100644
"""
Sign an IPsec (ESP or AH) packet with this algo.
-@@ -561,16 +553,20 @@ class AuthAlgo(object):
+@@ -560,16 +556,20 @@ class AuthAlgo(object):
if pkt.haslayer(ESP):
mac.update(raw(pkt[ESP]))
@@ -69,7 +93,7 @@ index f8c601fa..f566d288 100644
"""
Check that the integrity check value (icv) of a packet is valid.
-@@ -602,6 +598,8 @@ class AuthAlgo(object):
+@@ -600,6 +600,8 @@ class AuthAlgo(object):
clone = zero_mutable_fields(pkt.copy(), sending=False)
mac.update(raw(clone))
@@ -78,7 +102,7 @@ index f8c601fa..f566d288 100644
computed_icv = mac.finalize()[:self.icv_size]
# XXX: Cannot use mac.verify because the ICV can be truncated
-@@ -864,6 +862,23 @@ class SecurityAssociation(object):
+@@ -862,6 +864,23 @@ class SecurityAssociation(object):
raise TypeError('nat_t_header must be %s' % UDP.name)
self.nat_t_header = nat_t_header
@@ -102,7 +126,7 @@ index f8c601fa..f566d288 100644
def check_spi(self, pkt):
if pkt.spi != self.spi:
raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' %
-@@ -877,7 +892,8 @@ class SecurityAssociation(object):
+@@ -875,7 +894,8 @@ class SecurityAssociation(object):
if len(iv) != self.crypt_algo.iv_size:
raise TypeError('iv length must be %s' % self.crypt_algo.iv_size) # noqa: E501
@@ -112,7 +136,7 @@ index f8c601fa..f566d288 100644
if self.tunnel_header:
tunnel = self.tunnel_header.copy()
-@@ -901,7 +917,7 @@ class SecurityAssociation(object):
+@@ -899,7 +919,7 @@ class SecurityAssociation(object):
esn_en=esn_en or self.esn_en,
esn=esn or self.esn)
@@ -121,7 +145,7 @@ index f8c601fa..f566d288 100644
if self.nat_t_header:
nat_t_header = self.nat_t_header.copy()
-@@ -928,7 +944,8 @@ class SecurityAssociation(object):
+@@ -926,7 +946,8 @@ class SecurityAssociation(object):
def _encrypt_ah(self, pkt, seq_num=None):
@@ -131,7 +155,7 @@ index f8c601fa..f566d288 100644
icv=b"\x00" * self.auth_algo.icv_size)
if self.tunnel_header:
-@@ -968,7 +985,8 @@ class SecurityAssociation(object):
+@@ -966,7 +987,8 @@ class SecurityAssociation(object):
else:
ip_header.plen = len(ip_header.payload) + len(ah) + len(payload)
@@ -141,7 +165,7 @@ index f8c601fa..f566d288 100644
# sequence number must always change, unless specified by the user
if seq_num is None:
-@@ -1005,11 +1023,12 @@ class SecurityAssociation(object):
+@@ -1003,11 +1025,12 @@ class SecurityAssociation(object):
def _decrypt_esp(self, pkt, verify=True, esn_en=None, esn=None):
@@ -155,7 +179,7 @@ index f8c601fa..f566d288 100644
esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key,
self.crypt_algo.icv_size or
-@@ -1050,11 +1069,12 @@ class SecurityAssociation(object):
+@@ -1048,9 +1071,10 @@ class SecurityAssociation(object):
def _decrypt_ah(self, pkt, verify=True):
@@ -167,5 +191,3 @@ index f8c601fa..f566d288 100644
ah = pkt[AH]
payload = ah.payload
-
-
diff --git a/test/patches/scapy-2.4/ipsec.patch b/test/patches/scapy-2.4/ipsec.patch
index 083b0b94961..7eb0123a211 100644
--- a/test/patches/scapy-2.4/ipsec.patch
+++ b/test/patches/scapy-2.4/ipsec.patch
@@ -1,5 +1,5 @@
diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py
-index 69e7ae3b..3a1724b2 100644
+index 69e7ae3b..d9c1705b 100644
--- a/scapy/layers/ipsec.py
+++ b/scapy/layers/ipsec.py
@@ -344,8 +344,7 @@ class CryptAlgo(object):
@@ -24,7 +24,31 @@ index 69e7ae3b..3a1724b2 100644
try:
data = decryptor.update(data) + decryptor.finalize()
except InvalidTag as err:
-@@ -518,12 +514,16 @@ class AuthAlgo(object):
+@@ -422,6 +418,7 @@ if algorithms:
+ 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)
+@@ -429,6 +426,7 @@ if algorithms:
+ CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM',
+ cipher=algorithms.AES,
+ mode=modes.GCM,
++ block_size=1,
+ salt_size=4,
+ iv_size=8,
+ icv_size=16,
+@@ -437,6 +435,7 @@ if algorithms:
+ 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,
+@@ -518,12 +517,16 @@ class AuthAlgo(object):
else:
return self.mac(key, self.digestmod(), default_backend())
@@ -42,7 +66,7 @@ index 69e7ae3b..3a1724b2 100644
@return: the signed packet
"""
-@@ -534,16 +534,20 @@ class AuthAlgo(object):
+@@ -534,16 +537,20 @@ class AuthAlgo(object):
if pkt.haslayer(ESP):
mac.update(raw(pkt[ESP]))
@@ -64,7 +88,7 @@ index 69e7ae3b..3a1724b2 100644
"""
Check that the integrity check value (icv) of a packet is valid.
-@@ -574,6 +578,8 @@ class AuthAlgo(object):
+@@ -574,6 +581,8 @@ class AuthAlgo(object):
clone = zero_mutable_fields(pkt.copy(), sending=False)
mac.update(raw(clone))
@@ -73,7 +97,7 @@ index 69e7ae3b..3a1724b2 100644
computed_icv = mac.finalize()[:self.icv_size]
# XXX: Cannot use mac.verify because the ICV can be truncated
-@@ -757,7 +763,8 @@ class SecurityAssociation(object):
+@@ -757,7 +766,8 @@ class SecurityAssociation(object):
SUPPORTED_PROTOS = (IP, IPv6)
def __init__(self, proto, spi, seq_num=1, crypt_algo=None, crypt_key=None,
@@ -83,7 +107,7 @@ index 69e7ae3b..3a1724b2 100644
"""
@param proto: the IPsec proto to use (ESP or AH)
@param spi: the Security Parameters Index of this SA
-@@ -771,6 +778,7 @@ class SecurityAssociation(object):
+@@ -771,6 +781,7 @@ class SecurityAssociation(object):
to encapsulate the encrypted packets.
@param nat_t_header: an instance of a UDP header that will be used
for NAT-Traversal.
@@ -91,7 +115,7 @@ index 69e7ae3b..3a1724b2 100644
"""
if proto not in (ESP, AH, ESP.name, AH.name):
-@@ -782,6 +790,7 @@ class SecurityAssociation(object):
+@@ -782,6 +793,7 @@ class SecurityAssociation(object):
self.spi = spi
self.seq_num = seq_num
@@ -99,7 +123,7 @@ index 69e7ae3b..3a1724b2 100644
if crypt_algo:
if crypt_algo not in CRYPT_ALGOS:
-@@ -827,6 +836,23 @@ class SecurityAssociation(object):
+@@ -827,6 +839,23 @@ class SecurityAssociation(object):
raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' %
(pkt.spi, self.spi))
@@ -123,7 +147,7 @@ index 69e7ae3b..3a1724b2 100644
def _encrypt_esp(self, pkt, seq_num=None, iv=None):
if iv is None:
-@@ -835,7 +861,8 @@ class SecurityAssociation(object):
+@@ -835,7 +864,8 @@ class SecurityAssociation(object):
if len(iv) != self.crypt_algo.iv_size:
raise TypeError('iv length must be %s' % self.crypt_algo.iv_size)
@@ -133,7 +157,7 @@ index 69e7ae3b..3a1724b2 100644
if self.tunnel_header:
tunnel = self.tunnel_header.copy()
-@@ -857,7 +884,7 @@ class SecurityAssociation(object):
+@@ -857,7 +887,7 @@ class SecurityAssociation(object):
esp = self.crypt_algo.pad(esp)
esp = self.crypt_algo.encrypt(self, esp, self.crypt_key)
@@ -142,7 +166,7 @@ index 69e7ae3b..3a1724b2 100644
if self.nat_t_header:
nat_t_header = self.nat_t_header.copy()
-@@ -884,7 +911,8 @@ class SecurityAssociation(object):
+@@ -884,7 +914,8 @@ class SecurityAssociation(object):
def _encrypt_ah(self, pkt, seq_num=None):
@@ -152,7 +176,7 @@ index 69e7ae3b..3a1724b2 100644
icv = b"\x00" * self.auth_algo.icv_size)
if self.tunnel_header:
-@@ -924,7 +952,8 @@ class SecurityAssociation(object):
+@@ -924,7 +955,8 @@ class SecurityAssociation(object):
else:
ip_header.plen = len(ip_header.payload) + len(ah) + len(payload)
@@ -162,7 +186,7 @@ index 69e7ae3b..3a1724b2 100644
# sequence number must always change, unless specified by the user
if seq_num is None:
-@@ -955,11 +984,12 @@ class SecurityAssociation(object):
+@@ -955,11 +987,12 @@ class SecurityAssociation(object):
def _decrypt_esp(self, pkt, verify=True):
@@ -176,7 +200,7 @@ index 69e7ae3b..3a1724b2 100644
esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key,
self.crypt_algo.icv_size or
-@@ -998,9 +1028,11 @@ class SecurityAssociation(object):
+@@ -998,9 +1031,11 @@ class SecurityAssociation(object):
def _decrypt_ah(self, pkt, verify=True):
diff --git a/test/template_ipsec.py b/test/template_ipsec.py
index c3484e1ad0b..532a7a0a189 100644
--- a/test/template_ipsec.py
+++ b/test/template_ipsec.py
@@ -803,6 +803,15 @@ class IpsecTun4(object):
self.assert_equal(rx[IP].dst, self.pg1.remote_ip4)
self.assert_packet_checksums_valid(rx)
+ def verify_esp_padding(self, sa, esp_payload, decrypt_pkt):
+ align = sa.crypt_algo.block_size
+ if align < 4:
+ align = 4
+ exp_len = (len(decrypt_pkt) + 2 + (align - 1)) & ~(align - 1)
+ exp_len += sa.crypt_algo.iv_size
+ exp_len += sa.crypt_algo.icv_size or sa.auth_algo.icv_size
+ self.assertEqual(exp_len, len(esp_payload))
+
def verify_encrypted(self, p, sa, rxs):
decrypt_pkts = []
for rx in rxs:
@@ -811,9 +820,12 @@ class IpsecTun4(object):
self.assert_packet_checksums_valid(rx)
self.assertEqual(len(rx) - len(Ether()), rx[IP].len)
try:
- decrypt_pkt = p.vpp_tun_sa.decrypt(rx[IP])
+ rx_ip = rx[IP]
+ decrypt_pkt = p.vpp_tun_sa.decrypt(rx_ip)
if not decrypt_pkt.haslayer(IP):
decrypt_pkt = IP(decrypt_pkt[Raw].load)
+ if rx_ip.proto == socket.IPPROTO_ESP:
+ self.verify_esp_padding(sa, rx_ip[ESP].data, decrypt_pkt)
decrypt_pkts.append(decrypt_pkt)
self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip4)
self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host)
diff --git a/test/test_ipsec_esp.py b/test/test_ipsec_esp.py
index 7448df1d09a..dbd21f1f5fe 100644
--- a/test/test_ipsec_esp.py
+++ b/test/test_ipsec_esp.py
@@ -1,7 +1,7 @@
import socket
import unittest
from scapy.layers.ipsec import ESP
-from scapy.layers.inet import UDP
+from scapy.layers.inet import IP, ICMP, UDP
from parameterized import parameterized
from framework import VppTestRunner
@@ -544,6 +544,8 @@ class RunTestIpsecEspAll(ConfigIpsecESP,
self.run_a_test(self.engine, self.flag, self.algo)
def run_a_test(self, engine, flag, algo, payload_size=None):
+ if engine == "ia32":
+ engine = "native"
self.vapi.cli("set crypto handler all %s" % engine)
self.ipv4_params = IPsecIPv4Params()
@@ -579,8 +581,16 @@ class RunTestIpsecEspAll(ConfigIpsecESP,
self.verify_tra_basic4(count=NUM_PKTS)
self.verify_tun_66(self.params[socket.AF_INET6],
count=NUM_PKTS)
+ #
+ # Use an odd-byte payload size to check for correct padding.
+ #
+ # 49 + 2 == 51 which should pad +1 to 52 for 4 byte alignment, +5
+ # to 56 for 8 byte alignment, and +13 to 64 for 64 byte alignment.
+ # This should catch bugs where the code is incorrectly over-padding
+ # for algorithms that don't require it
+ psz = 49 - len(IP()/ICMP()) if payload_size is None else payload_size
self.verify_tun_44(self.params[socket.AF_INET],
- count=NUM_PKTS)
+ count=NUM_PKTS, payload_size=psz)
LARGE_PKT_SZ = [
1970, # results in 2 chained buffers entering decrypt node