Repository: secdev/scapy

Issue:
ICMPv6 packet could not be parsed with IPSec AH protocol configured 
I am attempting to parse a pcap file by using Scapy as lib, and now I encounter a problem. The ICMPv6 packet, which has been sent via IPSec AH protocol, could not be shown with show()/show2().
Pcap file: 
[AH_IPV6.zip](https://github.com/secdev/scapy/files/805587/AH_IPV6.zip)
My code is easy like this:
    
    from scapy.utils import rdpcap
    from scapy.all import conf

    def main():
        packet_list = rdpcap("/media/sf_ubshare/pcap/AH_IPV6.PCAP")
        pkt2 = packet_list.__getitem__(9)
        pkt2.show2()
    

ICMPv6 packet is missing in show2()
    
###[ IPv6 ]### 
     version   = 6L
     tc        = 0L
     fl        = 0L
     plen      = 128
     nh        = AH Header
     hlim      = 240
     src       = 1::2:1
     dst       = 1::3:2
###[ AH ]### 
        nh        = ipv6
        payloadlen= 4
        reserved  = 0
        spi       = 0x9592d65b
        seq       = 1
        icv       = '\x03\xa77\xc1\xea<\xcf\xb9bO~m`\x00\x00\x00\x00@:@\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x02\x80\x00I\xe2\x00d\x00\x00X\xa6\xa7\x10\x00\x0eJm\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'
        padding   = None
    
but in wireshark the ICMPv6 packet could be parsed successfully
![image](https://cloud.githubusercontent.com/assets/17735279/23388920/e0690f8a-fd9f-11e6-97c1-540c67faccef.png)

It seems like Scapy considers the ICMPv6 packet as a icv field of last packet.
after some dug I suspect there might be a problem in /scapy/utils.py
    
    def read_packet(self, size=MTU):
        """return a single packet read from the file
        
        returns None when no more packets are available
        """
        hdr = self.f.read(16)
        if len(hdr) < 16:
            return None
        sec,usec,caplen,wirelen = struct.unpack(self.endian+"IIII", hdr)  #<-- this line
        s = self.f.read(caplen)[:size]
        return s,(sec,usec,wirelen) # caplen = len(s)

Test bed info:

- Scapy version: 2.3.3

- OS: Ubuntu 14.04.3 LTS





Discussion:

Resolving patch:
diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py
--- a/scapy/layers/ipsec.py
+++ b/scapy/layers/ipsec.py
@@ -47,8 +47,8 @@
 from scapy.config import conf, crypto_validator
 from scapy.data import IP_PROTOS
 from scapy.error import log_loading
-from scapy.fields import (ByteEnumField, ByteField, StrField, XIntField,
-                          IntField, ShortField, PacketField)
+from scapy.fields import (ByteEnumField, ByteField, StrField, StrLenField,
+                          XIntField, IntField, ShortField, PacketField)
 from scapy.packet import Packet, bind_layers, Raw
 from scapy.layers.inet import IP, UDP
 from scapy.layers.inet6 import (IPv6, IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt,
@@ -65,14 +65,26 @@ class AH(Packet):
 
     name = 'AH'
 
+    def __get_icv_len(self):
+        """
+        Compute the size of the ICV based on the payloadlen field.
+        Padding size is included as it can only be known from the authentication
+        algorithm provided by the Security Association.
+        """
+        # payloadlen = length of AH in 32-bit words (4-byte units), minus "2"
+        # payloadlen = 3 32-bit word fixed fields + ICV + padding - 2
+        # ICV = (payloadlen + 2 - 3 - padding) in 32-bit words
+        return (self.payloadlen - 1) * 4
+
     fields_desc = [
         ByteEnumField('nh', None, IP_PROTOS),
         ByteField('payloadlen', None),
         ShortField('reserved', None),
         XIntField('spi', 0x0),
         IntField('seq', 0),
-        StrField('icv', None),
-        StrField('padding', None),
+        StrLenField('icv', None, length_from=__get_icv_len),
+        # Padding len can only be known with the SecurityAssociation.auth_algo
+        StrLenField('padding', None, length_from=lambda x: 0),
     ]
 
     overload_fields = {
@@ -85,6 +97,8 @@ class AH(Packet):
 
 bind_layers(IP, AH, proto=socket.IPPROTO_AH)
 bind_layers(IPv6, AH, nh=socket.IPPROTO_AH)
+bind_layers(AH, IP, nh=socket.IPPROTO_IP)
+bind_layers(AH, IPv6, nh=socket.IPPROTO_IPV6)
 
 #------------------------------------------------------------------------------
 class ESP(Packet):
@@ -527,7 +541,11 @@ def verify(self, pkt, key):
             clone.data = clone.data[:len(clone.data) - self.icv_size]
 
         elif pkt.haslayer(AH):
-            pkt_icv = pkt[AH].icv[:self.icv_size]
+            if len(pkt[AH].icv) != self.icv_size:
+                # Fill padding since we know the actual icv_size
+                pkt[AH].padding = pkt[AH].icv[self.icv_size:]
+                pkt[AH].icv = pkt[AH].icv[:self.icv_size]
+            pkt_icv = pkt[AH].icv
             clone = zero_mutable_fields(pkt.copy(), sending=False)
 
         mac.update(str(clone))


Test patch:
diff --git a/test/ipsec.uts b/test/ipsec.uts
--- a/test/ipsec.uts
+++ b/test/ipsec.uts
@@ -3090,6 +3090,72 @@ assert(e[AH].spi == sa.spi)
 * simulate the alteration of the packet before verification
 e[TCP].dport = 46
 
+* integrity verification should fail
+try:
+    d = sa.decrypt(e)
+    assert(False)
+except IPSecIntegrityError, err:
+    err
+
+#######################################
+= IPv6 / AH - Transport - SHA2-256-128
+
+p = IPv6(src='11::22', dst='22::11')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IPv6(str(p))
+p
+
+sa = SecurityAssociation(AH, spi=0x222,
+                         auth_algo='SHA2-256-128', auth_key='secret key')
+
+e = sa.encrypt(p)
+e
+
+assert(isinstance(e, IPv6))
+assert(e.src == '11::22' and e.dst == '22::11')
+* the encrypted packet should have an AH layer
+assert(e.nh == socket.IPPROTO_AH)
+assert(e.haslayer(AH))
+assert(e.haslayer(TCP))
+assert(e[AH].spi == sa.spi)
+
+* alter mutable fields in the packet
+e.hlim = 2
+
+* integrity verification should pass
+d = sa.decrypt(e)
+d
+
+* after decryption the original packet payload should be unaltered
+assert(d[TCP] == p[TCP])
+
+#######################################
+= IPv6 / AH - Transport - SHA2-256-128 - altered packet
+
+p = IPv6(src='11::22', dst='22::11')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IPv6(str(p))
+p
+
+sa = SecurityAssociation(AH, spi=0x222,
+                         auth_algo='SHA2-256-128', auth_key='secret key')
+
+e = sa.encrypt(p)
+e
+
+assert(isinstance(e, IPv6))
+assert(e.src == '11::22' and e.dst == '22::11')
+* the encrypted packet should have an AH layer
+assert(e.nh == socket.IPPROTO_AH)
+assert(e.haslayer(AH))
+assert(e.haslayer(TCP))
+assert(e[AH].spi == sa.spi)
+
+* simulate the alteration of the packet before verification
+e[TCP].dport = 46
+
 * integrity verification should fail
 try:
     d = sa.decrypt(e)
@@ -3158,6 +3224,74 @@ assert(e[AH].spi == sa.spi)
 * simulate the alteration of the packet before verification
 e.src = 'cc::ee'
 
+* integrity verification should fail
+try:
+    d = sa.decrypt(e)
+    assert(False)
+except IPSecIntegrityError, err:
+    err
+
+#######################################
+= IPv6 / AH - Tunnel - SHA2-256-128
+
+p = IPv6(src='11::22', dst='22::11')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IPv6(str(p))
+p
+
+sa = SecurityAssociation(AH, spi=0x222,
+                         auth_algo='SHA2-256-128', auth_key='secret key',
+                         tunnel_header=IPv6(src='aa::bb', dst='bb::aa'))
+
+e = sa.encrypt(p)
+e
+
+assert(isinstance(e, IPv6))
+* after encryption packet should be encapsulated with the given ip tunnel header
+assert(e.src == 'aa::bb' and e.dst == 'bb::aa')
+assert(e.nh == socket.IPPROTO_AH)
+assert(e.haslayer(AH))
+assert(e.haslayer(TCP))
+assert(e[AH].spi == sa.spi)
+
+* alter mutable fields in the packet
+e.hlim = 2
+
+* integrity verification should pass
+d = sa.decrypt(e)
+d
+
+* after decryption the original packet payload should be unaltered
+assert(d == p)
+
+#######################################
+= IPv6 / AH - Tunnel - SHA2-256-128 - altered packet
+
+p = IPv6(src='11::22', dst='22::11')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IPv6(str(p))
+p
+
+sa = SecurityAssociation(AH, spi=0x222,
+                         auth_algo='SHA2-256-128', auth_key='secret key',
+                         tunnel_header=IPv6(src='aa::bb', dst='bb::aa'))
+
+e = sa.encrypt(p)
+e
+
+assert(isinstance(e, IPv6))
+* after encryption packet should be encapsulated with the given ip tunnel header
+assert(e.src == 'aa::bb' and e.dst == 'bb::aa')
+assert(e.nh == socket.IPPROTO_AH)
+assert(e.haslayer(AH))
+assert(e.haslayer(TCP))
+assert(e[AH].spi == sa.spi)
+
+* simulate the alteration of the packet before verification
+e.src = 'cc::ee'
+
 * integrity verification should fail
 try:
     d = sa.decrypt(e)