1f7d24e3fSHanoh Haim#############################################################################
2f7d24e3fSHanoh Haim## ipsec.py --- IPSec support for Scapy                                    ##
3f7d24e3fSHanoh Haim##                                                                         ##
4f7d24e3fSHanoh Haim## Copyright (C) 2014  6WIND                                               ##
5f7d24e3fSHanoh Haim##                                                                         ##
6f7d24e3fSHanoh Haim## This program is free software; you can redistribute it and/or modify it ##
7f7d24e3fSHanoh Haim## under the terms of the GNU General Public License version 2 as          ##
8f7d24e3fSHanoh Haim## published by the Free Software Foundation.                              ##
9f7d24e3fSHanoh Haim##                                                                         ##
10f7d24e3fSHanoh Haim## This program is distributed in the hope that it will be useful, but     ##
11f7d24e3fSHanoh Haim## WITHOUT ANY WARRANTY; without even the implied warranty of              ##
13f7d24e3fSHanoh Haim## General Public License for more details.                                ##
14f7d24e3fSHanoh Haim#############################################################################
15f7d24e3fSHanoh Haim"""
16f7d24e3fSHanoh HaimIPSec layer
17f7d24e3fSHanoh Haim===========
18f7d24e3fSHanoh Haim
19f7d24e3fSHanoh HaimExample of use:
20f7d24e3fSHanoh Haim
21f7d24e3fSHanoh Haim>>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',
22f7d24e3fSHanoh Haim...                          crypt_key='sixteenbytes key')
23f7d24e3fSHanoh Haim>>> p = IP(src='', dst='')
24f7d24e3fSHanoh Haim>>> p /= TCP(sport=45012, dport=80)
25f7d24e3fSHanoh Haim>>> p /= Raw('testdata')
26f7d24e3fSHanoh Haim>>> p = IP(str(p))
27f7d24e3fSHanoh Haim>>> p
28f7d24e3fSHanoh Haim<IP  version=4L ihl=5L tos=0x0 len=48 id=1 flags= frag=0L ttl=64 proto=tcp chksum=0x74c2 src= dst= options=[] |<TCP  sport=45012 dport=http seq=0 ack=0 dataofs=5L reserved=0L flags=S window=8192 chksum=0x1914 urgptr=0 options=[] |<Raw  load='testdata' |>>>
29f7d24e3fSHanoh Haim>>>
30f7d24e3fSHanoh Haim>>> e = sa.encrypt(p)
31f7d24e3fSHanoh Haim>>> e
32f7d24e3fSHanoh Haim<IP  version=4L ihl=5L tos=0x0 len=76 id=1 flags= frag=0L ttl=64 proto=esp chksum=0x747a src= dst= |<ESP  spi=0xdeadbeef seq=1 data='\xf8\xdb\x1e\x83[T\xab\\\xd2\x1b\xed\xd1\xe5\xc8Y\xc2\xa5d\x92\xc1\x05\x17\xa6\x92\x831\xe6\xc1]\x9a\xd6K}W\x8bFfd\xa5B*+\xde\xc8\x89\xbf{\xa9' |>>
33f7d24e3fSHanoh Haim>>>
34f7d24e3fSHanoh Haim>>> d = sa.decrypt(e)
35f7d24e3fSHanoh Haim>>> d
36f7d24e3fSHanoh Haim<IP  version=4L ihl=5L tos=0x0 len=48 id=1 flags= frag=0L ttl=64 proto=tcp chksum=0x74c2 src= dst= |<TCP  sport=45012 dport=http seq=0 ack=0 dataofs=5L reserved=0L flags=S window=8192 chksum=0x1914 urgptr=0 options=[] |<Raw  load='testdata' |>>>
37f7d24e3fSHanoh Haim>>>
38f7d24e3fSHanoh Haim>>> d == p
39f7d24e3fSHanoh HaimTrue
40f7d24e3fSHanoh Haim"""
41f7d24e3fSHanoh Haim
42f7d24e3fSHanoh Haimimport socket
43f7d24e3fSHanoh Haimimport fractions
44f7d24e3fSHanoh Haim
45f7d24e3fSHanoh Haimfrom scapy.data import IP_PROTOS
46f7d24e3fSHanoh Haim
47f7d24e3fSHanoh Haimfrom scapy.fields import ByteEnumField, ByteField, StrField, XIntField, IntField, \
48f7d24e3fSHanoh Haim    ShortField, PacketField
49f7d24e3fSHanoh Haim
50f7d24e3fSHanoh Haimfrom scapy.packet import Packet, bind_layers, Raw
51f7d24e3fSHanoh Haim
52f7d24e3fSHanoh Haimfrom scapy.layers.inet import IP, UDP
53f7d24e3fSHanoh Haimfrom scapy.layers.inet6 import IPv6, IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt, \
54f7d24e3fSHanoh Haim    IPv6ExtHdrRouting
55f7d24e3fSHanoh Haim
56f7d24e3fSHanoh Haim
57f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
58f7d24e3fSHanoh Haimclass AH(Packet):
59f7d24e3fSHanoh Haim    """
60f7d24e3fSHanoh Haim    Authentication Header
61f7d24e3fSHanoh Haim
62f7d24e3fSHanoh Haim    See https://tools.ietf.org/rfc/rfc4302.txt
63f7d24e3fSHanoh Haim    """
64f7d24e3fSHanoh Haim
65f7d24e3fSHanoh Haim    name = 'AH'
66f7d24e3fSHanoh Haim
67f7d24e3fSHanoh Haim    fields_desc = [
68f7d24e3fSHanoh Haim        ByteEnumField('nh', None, IP_PROTOS),
69f7d24e3fSHanoh Haim        ByteField('payloadlen', None),
70f7d24e3fSHanoh Haim        ShortField('reserved', None),
71f7d24e3fSHanoh Haim        XIntField('spi', 0x0),
72f7d24e3fSHanoh Haim        IntField('seq', 0),
73f7d24e3fSHanoh Haim        StrField('icv', None),
74f7d24e3fSHanoh Haim        StrField('padding', None),
75f7d24e3fSHanoh Haim    ]
76f7d24e3fSHanoh Haim
77f7d24e3fSHanoh Haim    overload_fields = {
78f7d24e3fSHanoh Haim        IP: {'proto': socket.IPPROTO_AH},
79f7d24e3fSHanoh Haim        IPv6: {'nh': socket.IPPROTO_AH},
80f7d24e3fSHanoh Haim        IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_AH},
81f7d24e3fSHanoh Haim        IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_AH},
82f7d24e3fSHanoh Haim        IPv6ExtHdrRouting: {'nh': socket.IPPROTO_AH},
83f7d24e3fSHanoh Haim    }
84f7d24e3fSHanoh Haim
85f7d24e3fSHanoh Haimbind_layers(IP, AH, proto=socket.IPPROTO_AH)
86f7d24e3fSHanoh Haimbind_layers(IPv6, AH, nh=socket.IPPROTO_AH)
87f7d24e3fSHanoh Haim
88f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
89f7d24e3fSHanoh Haimclass ESP(Packet):
90f7d24e3fSHanoh Haim    """
91f7d24e3fSHanoh Haim    Encapsulated Security Payload
92f7d24e3fSHanoh Haim
93f7d24e3fSHanoh Haim    See https://tools.ietf.org/rfc/rfc4303.txt
94f7d24e3fSHanoh Haim    """
95f7d24e3fSHanoh Haim    name = 'ESP'
96f7d24e3fSHanoh Haim
97f7d24e3fSHanoh Haim    fields_desc = [
98f7d24e3fSHanoh Haim        XIntField('spi', 0x0),
99f7d24e3fSHanoh Haim        IntField('seq', 0),
100f7d24e3fSHanoh Haim        StrField('data', None),
101f7d24e3fSHanoh Haim    ]
102f7d24e3fSHanoh Haim
103f7d24e3fSHanoh Haim    overload_fields = {
104f7d24e3fSHanoh Haim        IP: {'proto': socket.IPPROTO_ESP},
105f7d24e3fSHanoh Haim        IPv6: {'nh': socket.IPPROTO_ESP},
106f7d24e3fSHanoh Haim        IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_ESP},
107f7d24e3fSHanoh Haim        IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_ESP},
108f7d24e3fSHanoh Haim        IPv6ExtHdrRouting: {'nh': socket.IPPROTO_ESP},
109f7d24e3fSHanoh Haim    }
110f7d24e3fSHanoh Haim
111f7d24e3fSHanoh Haimbind_layers(IP, ESP, proto=socket.IPPROTO_ESP)
112f7d24e3fSHanoh Haimbind_layers(IPv6, ESP, nh=socket.IPPROTO_ESP)
113f7d24e3fSHanoh Haimbind_layers(UDP, ESP, dport=4500)  # NAT-Traversal encapsulation
114f7d24e3fSHanoh Haimbind_layers(UDP, ESP, sport=4500)  # NAT-Traversal encapsulation
115f7d24e3fSHanoh Haim
116f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
117f7d24e3fSHanoh Haimclass _ESPPlain(Packet):
118f7d24e3fSHanoh Haim    """
119f7d24e3fSHanoh Haim    Internal class to represent unencrypted ESP packets.
120f7d24e3fSHanoh Haim    """
121f7d24e3fSHanoh Haim    name = 'ESP'
122f7d24e3fSHanoh Haim
123f7d24e3fSHanoh Haim    fields_desc = [
124f7d24e3fSHanoh Haim        XIntField('spi', 0x0),
125f7d24e3fSHanoh Haim        IntField('seq', 0),
126f7d24e3fSHanoh Haim
127f7d24e3fSHanoh Haim        StrField('iv', ''),
128f7d24e3fSHanoh Haim        PacketField('data', '', Raw),
129f7d24e3fSHanoh Haim        StrField('padding', ''),
130f7d24e3fSHanoh Haim
131f7d24e3fSHanoh Haim        ByteField('padlen', 0),
132f7d24e3fSHanoh Haim        ByteEnumField('nh', 0, IP_PROTOS),
133f7d24e3fSHanoh Haim        StrField('icv', ''),
134f7d24e3fSHanoh Haim    ]
135f7d24e3fSHanoh Haim
136f7d24e3fSHanoh Haim    def data_for_encryption(self):
137f7d24e3fSHanoh Haim        return str(self.data) + self.padding + chr(self.padlen) + chr(self.nh)
138f7d24e3fSHanoh Haim
139f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
140f7d24e3fSHanoh Haimtry:
141f7d24e3fSHanoh Haim    from Crypto.Cipher import AES
142f7d24e3fSHanoh Haim    from Crypto.Cipher import DES
143f7d24e3fSHanoh Haim    from Crypto.Cipher import DES3
144f7d24e3fSHanoh Haim    from Crypto.Cipher import CAST
145f7d24e3fSHanoh Haim    from Crypto.Cipher import Blowfish
146f7d24e3fSHanoh Haim    from Crypto.Util import Counter
147f7d24e3fSHanoh Haim    from Crypto import Random
148f7d24e3fSHanoh Haimexcept ImportError:
149f7d24e3fSHanoh Haim    # no error if pycrypto is not available but encryption won't be supported
150f7d24e3fSHanoh Haim    AES = None
151f7d24e3fSHanoh Haim    DES = None
152f7d24e3fSHanoh Haim    DES3 = None
153f7d24e3fSHanoh Haim    CAST = None
154f7d24e3fSHanoh Haim    Blowfish = None
155f7d24e3fSHanoh Haim    Random = None
156f7d24e3fSHanoh Haim
157f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
158f7d24e3fSHanoh Haimdef _lcm(a, b):
159f7d24e3fSHanoh Haim    """
160f7d24e3fSHanoh Haim    Least Common Multiple between 2 integers.
161f7d24e3fSHanoh Haim    """
162f7d24e3fSHanoh Haim    if a == 0 or b == 0:
163f7d24e3fSHanoh Haim        return 0
164f7d24e3fSHanoh Haim    else:
165f7d24e3fSHanoh Haim        return abs(a * b) // fractions.gcd(a, b)
166f7d24e3fSHanoh Haim
167f7d24e3fSHanoh Haimclass CryptAlgo(object):
168f7d24e3fSHanoh Haim    """
169f7d24e3fSHanoh Haim    IPSec encryption algorithm
170f7d24e3fSHanoh Haim    """
171f7d24e3fSHanoh Haim
172f7d24e3fSHanoh Haim    def __init__(self, name, cipher, mode, block_size=None, iv_size=None, key_size=None):
173f7d24e3fSHanoh Haim        """
174f7d24e3fSHanoh Haim        @param name: the name of this encryption algorithm
175f7d24e3fSHanoh Haim        @param cipher: a Cipher module
176f7d24e3fSHanoh Haim        @param mode: the mode used with the cipher module
177f7d24e3fSHanoh Haim        @param block_size: the length a block for this algo. Defaults to the
178f7d24e3fSHanoh Haim                           `block_size` of the cipher.
179f7d24e3fSHanoh Haim        @param iv_size: the length of the initialization vector of this algo.
180f7d24e3fSHanoh Haim                        Defaults to the `block_size` of the cipher.
181f7d24e3fSHanoh Haim        @param key_size: an integer or list/tuple of integers. If specified,
182f7d24e3fSHanoh Haim                         force the secret keys length to one of the values.
183f7d24e3fSHanoh Haim                         Defaults to the `key_size` of the cipher.
184f7d24e3fSHanoh Haim        """
185f7d24e3fSHanoh Haim        self.name = name
186f7d24e3fSHanoh Haim        self.cipher = cipher
187f7d24e3fSHanoh Haim        self.mode = mode
188f7d24e3fSHanoh Haim
189f7d24e3fSHanoh Haim        if block_size is not None:
190f7d24e3fSHanoh Haim            self.block_size = block_size
191f7d24e3fSHanoh Haim        elif cipher is not None:
192f7d24e3fSHanoh Haim            self.block_size = cipher.block_size
193f7d24e3fSHanoh Haim        else:
194f7d24e3fSHanoh Haim            self.block_size = 1
195f7d24e3fSHanoh Haim
196f7d24e3fSHanoh Haim        if iv_size is None:
197f7d24e3fSHanoh Haim            self.iv_size = self.block_size
198f7d24e3fSHanoh Haim        else:
199f7d24e3fSHanoh Haim            self.iv_size = iv_size
200f7d24e3fSHanoh Haim
201f7d24e3fSHanoh Haim        if key_size is not None:
202f7d24e3fSHanoh Haim            self.key_size = key_size
203f7d24e3fSHanoh Haim        elif cipher is not None:
204f7d24e3fSHanoh Haim            self.key_size = cipher.key_size
205f7d24e3fSHanoh Haim        else:
206f7d24e3fSHanoh Haim            self.key_size = None
207f7d24e3fSHanoh Haim
208f7d24e3fSHanoh Haim    def check_key(self, key):
209f7d24e3fSHanoh Haim        """
210f7d24e3fSHanoh Haim        Check that the key length is valid.
211f7d24e3fSHanoh Haim
212f7d24e3fSHanoh Haim        @param key:    a byte string
213f7d24e3fSHanoh Haim        """
214f7d24e3fSHanoh Haim        if self.key_size and not (len(key) == self.key_size or len(key) in self.key_size):
215f7d24e3fSHanoh Haim            raise TypeError('invalid key size %s, must be %s' %
216f7d24e3fSHanoh Haim                            (len(key), self.key_size))
217f7d24e3fSHanoh Haim
218f7d24e3fSHanoh Haim    def generate_iv(self):
219f7d24e3fSHanoh Haim        """
220f7d24e3fSHanoh Haim        Generate a random initialization vector. If pycrypto is not available,
221f7d24e3fSHanoh Haim        return a buffer of the correct length filled with only '\x00'.
222f7d24e3fSHanoh Haim        """
223f7d24e3fSHanoh Haim        if Random:
224f7d24e3fSHanoh Haim            return Random.get_random_bytes(self.iv_size)
225f7d24e3fSHanoh Haim        else:
226f7d24e3fSHanoh Haim            return chr(0) * self.iv_size
227f7d24e3fSHanoh Haim
228f7d24e3fSHanoh Haim    def new_cipher(self, key, iv):
229f7d24e3fSHanoh Haim        """
230f7d24e3fSHanoh Haim        @param key:    the secret key, a byte string
231f7d24e3fSHanoh Haim        @param iv:     the initialization vector, a byte string
232f7d24e3fSHanoh Haim        @return:    an initialized cipher object for this algo
233f7d24e3fSHanoh Haim        """
234f7d24e3fSHanoh Haim        if (hasattr(self.cipher, 'MODE_CTR') and self.mode == self.cipher.MODE_CTR
235f7d24e3fSHanoh Haim            or hasattr(self.cipher, 'MODE_GCM') and self.mode == self.cipher.MODE_GCM):
236f7d24e3fSHanoh Haim            # in counter mode, the "iv" must be incremented for each block
237f7d24e3fSHanoh Haim            # it is calculated like this:
238f7d24e3fSHanoh Haim            # +---------+------------------+---------+
239f7d24e3fSHanoh Haim            # |  nonce  |        IV        | counter |
240f7d24e3fSHanoh Haim            # +---------+------------------+---------+
241f7d24e3fSHanoh Haim            #   m bytes       n bytes        4 bytes
242f7d24e3fSHanoh Haim            # <-------------------------------------->
243f7d24e3fSHanoh Haim            #               block_size
244f7d24e3fSHanoh Haim            nonce_size = self.cipher.block_size - self.iv_size - 4
245f7d24e3fSHanoh Haim
246f7d24e3fSHanoh Haim            # instead of asking for an extra parameter, we extract the last
247f7d24e3fSHanoh Haim            # nonce_size bytes of the key and use them as the nonce.
248f7d24e3fSHanoh Haim            # +----------------------------+---------+
249f7d24e3fSHanoh Haim            # |        cipher key          |  nonce  |
250f7d24e3fSHanoh Haim            # +----------------------------+---------+
251f7d24e3fSHanoh Haim            #                              <--------->
252f7d24e3fSHanoh Haim            #                               nonce_size
253f7d24e3fSHanoh Haim            cipher_key, nonce = key[:-nonce_size], key[-nonce_size:]
254f7d24e3fSHanoh Haim
255f7d24e3fSHanoh Haim            return self.cipher.new(cipher_key, self.mode,
256f7d24e3fSHanoh Haim                                   counter=Counter.new(4 * 8, prefix=nonce + iv))
257f7d24e3fSHanoh Haim        else:
258f7d24e3fSHanoh Haim            return self.cipher.new(key, self.mode, iv)
259f7d24e3fSHanoh Haim
260f7d24e3fSHanoh Haim    def pad(self, esp):
261f7d24e3fSHanoh Haim        """
262f7d24e3fSHanoh Haim        Add the correct amount of padding so that the data to encrypt is
263f7d24e3fSHanoh Haim        exactly a multiple of the algorithm's block size.
264f7d24e3fSHanoh Haim
265f7d24e3fSHanoh Haim        Also, make sure that the total ESP packet length is a multiple of 4 or
266f7d24e3fSHanoh Haim        8 bytes with IP or IPv6 respectively.
267f7d24e3fSHanoh Haim
268f7d24e3fSHanoh Haim        @param esp:    an unencrypted _ESPPlain packet
269f7d24e3fSHanoh Haim        """
270f7d24e3fSHanoh Haim        # 2 extra bytes for padlen and nh
271f7d24e3fSHanoh Haim        data_len = len(esp.data) + 2
272f7d24e3fSHanoh Haim
273f7d24e3fSHanoh Haim        # according to the RFC4303, section 2.4. Padding (for Encryption)
274f7d24e3fSHanoh Haim        # the size of the ESP payload must be a multiple of 32 bits
275f7d24e3fSHanoh Haim        align = _lcm(self.block_size, 4)
276f7d24e3fSHanoh Haim
277f7d24e3fSHanoh Haim        # pad for block size
278f7d24e3fSHanoh Haim        esp.padlen = -data_len % align
279f7d24e3fSHanoh Haim
280f7d24e3fSHanoh Haim        # padding must be an array of bytes starting from 1 to padlen
281f7d24e3fSHanoh Haim        esp.padding = ''
282f7d24e3fSHanoh Haim        for b in range(1, esp.padlen + 1):
283f7d24e3fSHanoh Haim            esp.padding += chr(b)
284f7d24e3fSHanoh Haim
285f7d24e3fSHanoh Haim        # If the following test fails, it means that this algo does not comply
286f7d24e3fSHanoh Haim        # with the RFC
287f7d24e3fSHanoh Haim        payload_len = len(esp.iv) + len(esp.data) + len(esp.padding) + 2
288f7d24e3fSHanoh Haim        if payload_len % 4 != 0:
289f7d24e3fSHanoh Haim            raise ValueError('The size of the ESP data is not aligned to 32 bits after padding.')
290f7d24e3fSHanoh Haim
291f7d24e3fSHanoh Haim        return esp
292f7d24e3fSHanoh Haim
293f7d24e3fSHanoh Haim    def encrypt(self, esp, key):
294f7d24e3fSHanoh Haim        """
295f7d24e3fSHanoh Haim        Encrypt an ESP packet
296f7d24e3fSHanoh Haim
297f7d24e3fSHanoh Haim        @param esp:  an unencrypted _ESPPlain packet with valid padding
298f7d24e3fSHanoh Haim        @param key:  the secret key used for encryption
299f7d24e3fSHanoh Haim
300f7d24e3fSHanoh Haim        @return:    a valid ESP packet encrypted with this algorithm
301f7d24e3fSHanoh Haim        """
302f7d24e3fSHanoh Haim        data = esp.data_for_encryption()
303f7d24e3fSHanoh Haim
304f7d24e3fSHanoh Haim        if self.cipher:
305f7d24e3fSHanoh Haim            self.check_key(key)
306f7d24e3fSHanoh Haim            cipher = self.new_cipher(key, esp.iv)
307f7d24e3fSHanoh Haim            data = cipher.encrypt(data)
308f7d24e3fSHanoh Haim
309f7d24e3fSHanoh Haim        return ESP(spi=esp.spi, seq=esp.seq, data=esp.iv + data)
310f7d24e3fSHanoh Haim
311f7d24e3fSHanoh Haim    def decrypt(self, esp, key, icv_size=0):
312f7d24e3fSHanoh Haim        """
313f7d24e3fSHanoh Haim        Decrypt an ESP packet
314f7d24e3fSHanoh Haim
315f7d24e3fSHanoh Haim        @param esp:        an encrypted ESP packet
316f7d24e3fSHanoh Haim        @param key:        the secret key used for encryption
317f7d24e3fSHanoh Haim        @param icv_size:   the length of the icv used for integrity check
318f7d24e3fSHanoh Haim
319f7d24e3fSHanoh Haim        @return:    a valid ESP packet encrypted with this algorithm
320f7d24e3fSHanoh Haim        """
321f7d24e3fSHanoh Haim        self.check_key(key)
322f7d24e3fSHanoh Haim
323f7d24e3fSHanoh Haim        iv = esp.data[:self.iv_size]
324f7d24e3fSHanoh Haim        data = esp.data[self.iv_size:len(esp.data) - icv_size]
325f7d24e3fSHanoh Haim        icv = esp.data[len(esp.data) - icv_size:]
326f7d24e3fSHanoh Haim
327f7d24e3fSHanoh Haim        if self.cipher:
328f7d24e3fSHanoh Haim            cipher = self.new_cipher(key, iv)
329f7d24e3fSHanoh Haim            data = cipher.decrypt(data)
330f7d24e3fSHanoh Haim
331f7d24e3fSHanoh Haim        # extract padlen and nh
332f7d24e3fSHanoh Haim        padlen = ord(data[-2])
333f7d24e3fSHanoh Haim        nh = ord(data[-1])
334f7d24e3fSHanoh Haim
335f7d24e3fSHanoh Haim        # then use padlen to determine data and padding
336f7d24e3fSHanoh Haim        data = data[:len(data) - padlen - 2]
337f7d24e3fSHanoh Haim        padding = data[len(data) - padlen - 2: len(data) - 2]
338f7d24e3fSHanoh Haim
339f7d24e3fSHanoh Haim        return _ESPPlain(spi=esp.spi,
340f7d24e3fSHanoh Haim                        seq=esp.seq,
341f7d24e3fSHanoh Haim                        iv=iv,
342f7d24e3fSHanoh Haim                        data=data,
343f7d24e3fSHanoh Haim                        padding=padding,
344f7d24e3fSHanoh Haim                        padlen=padlen,
345f7d24e3fSHanoh Haim                        nh=nh,
346f7d24e3fSHanoh Haim                        icv=icv)
347f7d24e3fSHanoh Haim
348f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
349f7d24e3fSHanoh Haim# The names of the encryption algorithms are the same than in scapy.contrib.ikev2
350f7d24e3fSHanoh Haim# see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml
351f7d24e3fSHanoh Haim
352f7d24e3fSHanoh HaimCRYPT_ALGOS = {
353f7d24e3fSHanoh Haim    'NULL': CryptAlgo('NULL', cipher=None, mode=None, iv_size=0),
354f7d24e3fSHanoh Haim}
355f7d24e3fSHanoh Haim
356f7d24e3fSHanoh Haimif AES:
357f7d24e3fSHanoh Haim    CRYPT_ALGOS['AES-CBC'] = CryptAlgo('AES-CBC',
358f7d24e3fSHanoh Haim                                       cipher=AES,
359f7d24e3fSHanoh Haim                                       mode=AES.MODE_CBC)
360f7d24e3fSHanoh Haim    # specific case for counter mode:
361f7d24e3fSHanoh Haim    # the last 4 bytes of the key are used to carry the nonce of the counter
362f7d24e3fSHanoh Haim    CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR',
363f7d24e3fSHanoh Haim                                       cipher=AES,
364f7d24e3fSHanoh Haim                                       mode=AES.MODE_CTR,
365f7d24e3fSHanoh Haim                                       block_size=1,
366f7d24e3fSHanoh Haim                                       iv_size=8,
367f7d24e3fSHanoh Haim                                       key_size=(16 + 4, 24 + 4, 32 + 4))
368f7d24e3fSHanoh Haimif DES:
369f7d24e3fSHanoh Haim    CRYPT_ALGOS['DES'] = CryptAlgo('DES',
370f7d24e3fSHanoh Haim                                   cipher=DES,
371f7d24e3fSHanoh Haim                                   mode=DES.MODE_CBC)
372f7d24e3fSHanoh Haimif Blowfish:
373f7d24e3fSHanoh Haim    CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish',
374f7d24e3fSHanoh Haim                                        cipher=Blowfish,
375f7d24e3fSHanoh Haim                                        mode=Blowfish.MODE_CBC)
376f7d24e3fSHanoh Haimif DES3:
377f7d24e3fSHanoh Haim    CRYPT_ALGOS['3DES'] = CryptAlgo('3DES',
378f7d24e3fSHanoh Haim                                    cipher=DES3,
379f7d24e3fSHanoh Haim                                    mode=DES3.MODE_CBC)
380f7d24e3fSHanoh Haimif CAST:
381f7d24e3fSHanoh Haim    CRYPT_ALGOS['CAST'] = CryptAlgo('CAST',
382f7d24e3fSHanoh Haim                                    cipher=CAST,
383f7d24e3fSHanoh Haim                                    mode=CAST.MODE_CBC)
384f7d24e3fSHanoh Haim
385f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
386f7d24e3fSHanoh Haimtry:
387f7d24e3fSHanoh Haim    from Crypto.Hash import HMAC
388f7d24e3fSHanoh Haim    from Crypto.Hash import SHA
389f7d24e3fSHanoh Haim    from Crypto.Hash import MD5
390f7d24e3fSHanoh Haim    from Crypto.Hash import SHA256
391f7d24e3fSHanoh Haim    from Crypto.Hash import SHA384
392f7d24e3fSHanoh Haim    from Crypto.Hash import SHA512
393f7d24e3fSHanoh Haimexcept ImportError:
394f7d24e3fSHanoh Haim    # no error if pycrypto is not available but authentication won't be supported
395f7d24e3fSHanoh Haim    HMAC = None
396f7d24e3fSHanoh Haim    SHA = None
397f7d24e3fSHanoh Haim    MD5 = None
398f7d24e3fSHanoh Haim    SHA256 = None
399f7d24e3fSHanoh Haim    SHA384 = None
400f7d24e3fSHanoh Haimtry:
401f7d24e3fSHanoh Haim    from Crypto.Hash import XCBCMAC
402f7d24e3fSHanoh Haimexcept ImportError:
403f7d24e3fSHanoh Haim    XCBCMAC = None
404f7d24e3fSHanoh Haim
405f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
406f7d24e3fSHanoh Haimclass IPSecIntegrityError(Exception):
407f7d24e3fSHanoh Haim    """
408f7d24e3fSHanoh Haim    Error risen when the integrity check fails.
409f7d24e3fSHanoh Haim    """
410f7d24e3fSHanoh Haim    pass
411f7d24e3fSHanoh Haim
412f7d24e3fSHanoh Haimclass AuthAlgo(object):
413f7d24e3fSHanoh Haim    """
414f7d24e3fSHanoh Haim    IPSec integrity algorithm
415f7d24e3fSHanoh Haim    """
416f7d24e3fSHanoh Haim
417f7d24e3fSHanoh Haim    def __init__(self, name, mac, digestmod, icv_size, key_size=None):
418f7d24e3fSHanoh Haim        """
419f7d24e3fSHanoh Haim        @param name: the name of this integrity algorithm
420f7d24e3fSHanoh Haim        @param mac: a Message Authentication Code module
421f7d24e3fSHanoh Haim        @param digestmod: a Hash or Cipher module
422f7d24e3fSHanoh Haim        @param icv_size: the length of the integrity check value of this algo
423f7d24e3fSHanoh Haim        @param key_size: an integer or list/tuple of integers. If specified,
424f7d24e3fSHanoh Haim                         force the secret keys length to one of the values.
425f7d24e3fSHanoh Haim                         Defaults to the `key_size` of the cipher.
426f7d24e3fSHanoh Haim        """
427f7d24e3fSHanoh Haim        self.name = name
428f7d24e3fSHanoh Haim        self.mac = mac
429f7d24e3fSHanoh Haim        self.digestmod = digestmod
430f7d24e3fSHanoh Haim        self.icv_size = icv_size
431f7d24e3fSHanoh Haim        self.key_size = key_size
432f7d24e3fSHanoh Haim
433f7d24e3fSHanoh Haim    def check_key(self, key):
434f7d24e3fSHanoh Haim        """
435f7d24e3fSHanoh Haim        Check that the key length is valid.
436f7d24e3fSHanoh Haim
437f7d24e3fSHanoh Haim        @param key:    a byte string
438f7d24e3fSHanoh Haim        """
439f7d24e3fSHanoh Haim        if self.key_size and len(key) not in self.key_size:
440f7d24e3fSHanoh Haim            raise TypeError('invalid key size %s, must be one of %s' %
441f7d24e3fSHanoh Haim                            (len(key), self.key_size))
442f7d24e3fSHanoh Haim
443f7d24e3fSHanoh Haim    def new_mac(self, key):
444f7d24e3fSHanoh Haim        """
445f7d24e3fSHanoh Haim        @param key:    a byte string
446f7d24e3fSHanoh Haim        @return:       an initialized mac object for this algo
447f7d24e3fSHanoh Haim        """
448f7d24e3fSHanoh Haim        if self.mac is XCBCMAC:
449f7d24e3fSHanoh Haim            # specific case here, ciphermod instead of digestmod
450f7d24e3fSHanoh Haim            return self.mac.new(key, ciphermod=self.digestmod)
451f7d24e3fSHanoh Haim        else:
452f7d24e3fSHanoh Haim            return self.mac.new(key, digestmod=self.digestmod)
453f7d24e3fSHanoh Haim
454f7d24e3fSHanoh Haim    def sign(self, pkt, key):
455f7d24e3fSHanoh Haim        """
456f7d24e3fSHanoh Haim        Sign an IPSec (ESP or AH) packet with this algo.
457f7d24e3fSHanoh Haim
458f7d24e3fSHanoh Haim        @param pkt:    a packet that contains a valid encrypted ESP or AH layer
459f7d24e3fSHanoh Haim        @param key:    the authentication key, a byte string
460f7d24e3fSHanoh Haim
461f7d24e3fSHanoh Haim        @return: the signed packet
462f7d24e3fSHanoh Haim        """
463f7d24e3fSHanoh Haim        if not self.mac:
464f7d24e3fSHanoh Haim            return pkt
465f7d24e3fSHanoh Haim
466f7d24e3fSHanoh Haim        self.check_key(key)
467f7d24e3fSHanoh Haim
468f7d24e3fSHanoh Haim        mac = self.new_mac(key)
469f7d24e3fSHanoh Haim
470f7d24e3fSHanoh Haim        if pkt.haslayer(ESP):
471f7d24e3fSHanoh Haim            mac.update(str(pkt[ESP]))
472f7d24e3fSHanoh Haim            pkt[ESP].data += mac.digest()[:self.icv_size]
473f7d24e3fSHanoh Haim
474f7d24e3fSHanoh Haim        elif pkt.haslayer(AH):
475f7d24e3fSHanoh Haim            clone = zero_mutable_fields(pkt.copy(), sending=True)
476f7d24e3fSHanoh Haim            mac.update(str(clone))
477f7d24e3fSHanoh Haim            pkt[AH].icv = mac.digest()[:self.icv_size]
478f7d24e3fSHanoh Haim
479f7d24e3fSHanoh Haim        return pkt
480f7d24e3fSHanoh Haim
481f7d24e3fSHanoh Haim    def verify(self, pkt, key):
482f7d24e3fSHanoh Haim        """
483f7d24e3fSHanoh Haim        Check that the integrity check value (icv) of a packet is valid.
484f7d24e3fSHanoh Haim
485f7d24e3fSHanoh Haim        @param pkt:    a packet that contains a valid encrypted ESP or AH layer
486f7d24e3fSHanoh Haim        @param key:    the authentication key, a byte string
487f7d24e3fSHanoh Haim
488f7d24e3fSHanoh Haim        @raise IPSecIntegrityError: if the integrity check fails
489f7d24e3fSHanoh Haim        """
490f7d24e3fSHanoh Haim        if not self.mac or self.icv_size == 0:
491f7d24e3fSHanoh Haim            return
492f7d24e3fSHanoh Haim
493f7d24e3fSHanoh Haim        self.check_key(key)
494f7d24e3fSHanoh Haim
495f7d24e3fSHanoh Haim        mac = self.new_mac(key)
496f7d24e3fSHanoh Haim
497f7d24e3fSHanoh Haim        pkt_icv = 'not found'
498f7d24e3fSHanoh Haim        computed_icv = 'not computed'
499f7d24e3fSHanoh Haim
500f7d24e3fSHanoh Haim        if isinstance(pkt, ESP):
501f7d24e3fSHanoh Haim            pkt_icv = pkt.data[len(pkt.data) - self.icv_size:]
502f7d24e3fSHanoh Haim
503f7d24e3fSHanoh Haim            pkt = pkt.copy()
504f7d24e3fSHanoh Haim            pkt.data = pkt.data[:len(pkt.data) - self.icv_size]
505f7d24e3fSHanoh Haim            mac.update(str(pkt))
506f7d24e3fSHanoh Haim            computed_icv = mac.digest()[:self.icv_size]
507f7d24e3fSHanoh Haim
508f7d24e3fSHanoh Haim        elif pkt.haslayer(AH):
509f7d24e3fSHanoh Haim            pkt_icv = pkt[AH].icv[:self.icv_size]
510f7d24e3fSHanoh Haim
511f7d24e3fSHanoh Haim            clone = zero_mutable_fields(pkt.copy(), sending=False)
512f7d24e3fSHanoh Haim            mac.update(str(clone))
513f7d24e3fSHanoh Haim            computed_icv = mac.digest()[:self.icv_size]
514f7d24e3fSHanoh Haim
515f7d24e3fSHanoh Haim        if pkt_icv != computed_icv:
516f7d24e3fSHanoh Haim            raise IPSecIntegrityError('pkt_icv=%r, computed_icv=%r' %
517f7d24e3fSHanoh Haim                                      (pkt_icv, computed_icv))
518f7d24e3fSHanoh Haim
519f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
520f7d24e3fSHanoh Haim# The names of the integrity algorithms are the same than in scapy.contrib.ikev2
521f7d24e3fSHanoh Haim# see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml
522f7d24e3fSHanoh Haim
523f7d24e3fSHanoh HaimAUTH_ALGOS = {
524f7d24e3fSHanoh Haim    'NULL': AuthAlgo('NULL', mac=None, digestmod=None, icv_size=0),
525f7d24e3fSHanoh Haim}
526f7d24e3fSHanoh Haim
527f7d24e3fSHanoh Haimif HMAC:
528f7d24e3fSHanoh Haim    if SHA:
529f7d24e3fSHanoh Haim        AUTH_ALGOS['HMAC-SHA1-96'] = AuthAlgo('HMAC-SHA1-96',
530f7d24e3fSHanoh Haim                                              mac=HMAC,
531f7d24e3fSHanoh Haim                                              digestmod=SHA,
532f7d24e3fSHanoh Haim                                              icv_size=12)
533f7d24e3fSHanoh Haim    if SHA256:
534f7d24e3fSHanoh Haim        AUTH_ALGOS['SHA2-256-128'] = AuthAlgo('SHA2-256-128',
535f7d24e3fSHanoh Haim                                              mac=HMAC,
536f7d24e3fSHanoh Haim                                              digestmod=SHA256,
537f7d24e3fSHanoh Haim                                              icv_size=16)
538f7d24e3fSHanoh Haim    if SHA384:
539f7d24e3fSHanoh Haim        AUTH_ALGOS['SHA2-384-192'] = AuthAlgo('SHA2-384-192',
540f7d24e3fSHanoh Haim                                              mac=HMAC,
541f7d24e3fSHanoh Haim                                              digestmod=SHA384,
542f7d24e3fSHanoh Haim                                              icv_size=24)
543f7d24e3fSHanoh Haim    if SHA512:
544f7d24e3fSHanoh Haim        AUTH_ALGOS['SHA2-512-256'] = AuthAlgo('SHA2-512-256',
545f7d24e3fSHanoh Haim                                              mac=HMAC,
546f7d24e3fSHanoh Haim                                              digestmod=SHA512,
547f7d24e3fSHanoh Haim                                              icv_size=32)
548f7d24e3fSHanoh Haim    if MD5:
549f7d24e3fSHanoh Haim        AUTH_ALGOS['HMAC-MD5-96'] = AuthAlgo('HMAC-MD5-96',
550f7d24e3fSHanoh Haim                                             mac=HMAC,
551f7d24e3fSHanoh Haim                                             digestmod=MD5,
552f7d24e3fSHanoh Haim                                             icv_size=12)
553f7d24e3fSHanoh Haimif AES and XCBCMAC:
554f7d24e3fSHanoh Haim    AUTH_ALGOS['AES-XCBC-96'] = AuthAlgo('AES-XCBC-96',
555f7d24e3fSHanoh Haim                                         mac=XCBCMAC,
556f7d24e3fSHanoh Haim                                         digestmod=AES,
557f7d24e3fSHanoh Haim                                         icv_size=12,
558f7d24e3fSHanoh Haim                                         key_size=(16,))
559f7d24e3fSHanoh Haim
560f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
561f7d24e3fSHanoh Haim
562f7d24e3fSHanoh Haim
563f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
564f7d24e3fSHanoh Haimdef split_for_transport(orig_pkt, transport_proto):
565f7d24e3fSHanoh Haim    """
566f7d24e3fSHanoh Haim    Split an IP(v6) packet in the correct location to insert an ESP or AH
567f7d24e3fSHanoh Haim    header.
568f7d24e3fSHanoh Haim
569f7d24e3fSHanoh Haim    @param orig_pkt: the packet to split. Must be an IP or IPv6 packet
570f7d24e3fSHanoh Haim    @param transport_proto: the IPSec protocol number that will be inserted
571f7d24e3fSHanoh Haim                            at the split position.
572f7d24e3fSHanoh Haim    @return: a tuple (header, nh, payload) where nh is the protocol number of
573f7d24e3fSHanoh Haim             payload.
574f7d24e3fSHanoh Haim    """
575f7d24e3fSHanoh Haim    header = orig_pkt.copy()
576f7d24e3fSHanoh Haim    next_hdr = header.payload
577f7d24e3fSHanoh Haim    nh = None
578f7d24e3fSHanoh Haim
579f7d24e3fSHanoh Haim    if header.version == 4:
580f7d24e3fSHanoh Haim        nh = header.proto
581f7d24e3fSHanoh Haim        header.proto = transport_proto
582f7d24e3fSHanoh Haim        header.remove_payload()
583f7d24e3fSHanoh Haim        del header.chksum
584f7d24e3fSHanoh Haim        del header.len
585f7d24e3fSHanoh Haim
586f7d24e3fSHanoh Haim        return header, nh, next_hdr
587f7d24e3fSHanoh Haim    else:
588f7d24e3fSHanoh Haim        found_rt_hdr = False
589f7d24e3fSHanoh Haim        prev = header
590f7d24e3fSHanoh Haim
591f7d24e3fSHanoh Haim        # Since the RFC 4302 is vague about where the ESP/AH headers should be
592f7d24e3fSHanoh Haim        # inserted in IPv6, I chose to follow the linux implementation.
593f7d24e3fSHanoh Haim        while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)):
594f7d24e3fSHanoh Haim            if isinstance(next_hdr, IPv6ExtHdrHopByHop):
595f7d24e3fSHanoh Haim                pass
596f7d24e3fSHanoh Haim            if isinstance(next_hdr, IPv6ExtHdrRouting):
597f7d24e3fSHanoh Haim                found_rt_hdr = True
598f7d24e3fSHanoh Haim            elif isinstance(next_hdr, IPv6ExtHdrDestOpt) and found_rt_hdr:
599f7d24e3fSHanoh Haim                break
600f7d24e3fSHanoh Haim
601f7d24e3fSHanoh Haim            prev = next_hdr
602f7d24e3fSHanoh Haim            next_hdr = next_hdr.payload
603f7d24e3fSHanoh Haim
604f7d24e3fSHanoh Haim        nh = prev.nh
605f7d24e3fSHanoh Haim        prev.nh = transport_proto
606f7d24e3fSHanoh Haim        prev.remove_payload()
607f7d24e3fSHanoh Haim        del header.plen
608f7d24e3fSHanoh Haim
609f7d24e3fSHanoh Haim        return header, nh, next_hdr
610f7d24e3fSHanoh Haim
611f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
612f7d24e3fSHanoh Haim# see RFC 4302 - Appendix A. Mutability of IP Options/Extension Headers
613f7d24e3fSHanoh HaimIMMUTABLE_IPV4_OPTIONS = (
614f7d24e3fSHanoh Haim    0, # End Of List
615f7d24e3fSHanoh Haim    1, # No OPeration
616f7d24e3fSHanoh Haim    2, # Security
617f7d24e3fSHanoh Haim    5, # Extended Security
618f7d24e3fSHanoh Haim    6, # Commercial Security
619f7d24e3fSHanoh Haim    20, # Router Alert
620f7d24e3fSHanoh Haim    21, # Sender Directed Multi-Destination Delivery
621f7d24e3fSHanoh Haim)
622f7d24e3fSHanoh Haimdef zero_mutable_fields(pkt, sending=False):
623f7d24e3fSHanoh Haim    """
624f7d24e3fSHanoh Haim    When using AH, all "mutable" fields must be "zeroed" before calculating
625f7d24e3fSHanoh Haim    the ICV. See RFC 4302, Section Handling Mutable Fields.
626f7d24e3fSHanoh Haim
627f7d24e3fSHanoh Haim    @param pkt: an IP(v6) packet containing an AH layer.
628f7d24e3fSHanoh Haim                NOTE: The packet will be modified
629f7d24e3fSHanoh Haim    @param sending: if true, ipv6 routing headers will not be reordered
630f7d24e3fSHanoh Haim    """
631f7d24e3fSHanoh Haim
632f7d24e3fSHanoh Haim    if pkt.haslayer(AH):
633f7d24e3fSHanoh Haim        pkt[AH].icv = chr(0) * len(pkt[AH].icv)
634f7d24e3fSHanoh Haim    else:
635f7d24e3fSHanoh Haim        raise TypeError('no AH layer found')
636f7d24e3fSHanoh Haim
637f7d24e3fSHanoh Haim    if pkt.version == 4:
638f7d24e3fSHanoh Haim        # the tos field has been replaced by DSCP and ECN
639f7d24e3fSHanoh Haim        # Routers may rewrite the DS field as needed to provide a
640f7d24e3fSHanoh Haim        # desired local or end-to-end service
641f7d24e3fSHanoh Haim        pkt.tos = 0
642f7d24e3fSHanoh Haim        # an intermediate router might set the DF bit, even if the source
643f7d24e3fSHanoh Haim        # did not select it.
644f7d24e3fSHanoh Haim        pkt.flags = 0
645f7d24e3fSHanoh Haim        # changed en route as a normal course of processing by routers
646f7d24e3fSHanoh Haim        pkt.ttl = 0
647f7d24e3fSHanoh Haim        # will change if any of these other fields change
648f7d24e3fSHanoh Haim        pkt.chksum = 0
649f7d24e3fSHanoh Haim
650f7d24e3fSHanoh Haim        immutable_opts = []
651f7d24e3fSHanoh Haim        for opt in pkt.options:
652f7d24e3fSHanoh Haim            if opt.option in IMMUTABLE_IPV4_OPTIONS:
653f7d24e3fSHanoh Haim                immutable_opts.append(opt)
654f7d24e3fSHanoh Haim            else:
655f7d24e3fSHanoh Haim                immutable_opts.append(Raw(chr(0) * len(opt)))
656f7d24e3fSHanoh Haim        pkt.options = immutable_opts
657f7d24e3fSHanoh Haim
658f7d24e3fSHanoh Haim    else:
659f7d24e3fSHanoh Haim        # holds DSCP and ECN
660f7d24e3fSHanoh Haim        pkt.tc = 0
661f7d24e3fSHanoh Haim        # The flow label described in AHv1 was mutable, and in RFC 2460 [DH98]
662f7d24e3fSHanoh Haim        # was potentially mutable. To retain compatibility with existing AH
663f7d24e3fSHanoh Haim        # implementations, the flow label is not included in the ICV in AHv2.
664f7d24e3fSHanoh Haim        pkt.fl = 0
665f7d24e3fSHanoh Haim        # same as ttl
666f7d24e3fSHanoh Haim        pkt.hlim = 0
667f7d24e3fSHanoh Haim
668f7d24e3fSHanoh Haim        next_hdr = pkt.payload
669f7d24e3fSHanoh Haim
670f7d24e3fSHanoh Haim        while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)):
671f7d24e3fSHanoh Haim            if isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt)):
672f7d24e3fSHanoh Haim                for opt in next_hdr.options:
673f7d24e3fSHanoh Haim                    if opt.otype & 0x20:
674f7d24e3fSHanoh Haim                        # option data can change en-route and must be zeroed
675f7d24e3fSHanoh Haim                        opt.optdata = chr(0) * opt.optlen
676f7d24e3fSHanoh Haim            elif isinstance(next_hdr, IPv6ExtHdrRouting) and sending:
677f7d24e3fSHanoh Haim                # The sender must order the field so that it appears as it
678f7d24e3fSHanoh Haim                # will at the receiver, prior to performing the ICV computation.
679f7d24e3fSHanoh Haim                next_hdr.segleft = 0
680f7d24e3fSHanoh Haim                if next_hdr.addresses:
681f7d24e3fSHanoh Haim                    final = next_hdr.addresses.pop()
682f7d24e3fSHanoh Haim                    next_hdr.addresses.insert(0, pkt.dst)
683f7d24e3fSHanoh Haim                    pkt.dst = final
684f7d24e3fSHanoh Haim            else:
685f7d24e3fSHanoh Haim                break
686f7d24e3fSHanoh Haim
687f7d24e3fSHanoh Haim            next_hdr = next_hdr.payload
688f7d24e3fSHanoh Haim
689f7d24e3fSHanoh Haim    return pkt
690f7d24e3fSHanoh Haim
691f7d24e3fSHanoh Haim#------------------------------------------------------------------------------
692f7d24e3fSHanoh Haimclass SecurityAssociation(object):
693f7d24e3fSHanoh Haim    """
694f7d24e3fSHanoh Haim    This class is responsible of "encryption" and "decryption" of IPSec packets.
695f7d24e3fSHanoh Haim    """
696f7d24e3fSHanoh Haim
697f7d24e3fSHanoh Haim    SUPPORTED_PROTOS = (IP, IPv6)
698f7d24e3fSHanoh Haim
699f7d24e3fSHanoh Haim    def __init__(self, proto, spi, seq_num=1, crypt_algo=None, crypt_key=None,
700f7d24e3fSHanoh Haim                 auth_algo=None, auth_key=None, tunnel_header=None, nat_t_header=None):
701f7d24e3fSHanoh Haim        """
702f7d24e3fSHanoh Haim        @param proto: the IPSec proto to use (ESP or AH)
703f7d24e3fSHanoh Haim        @param spi: the Security Parameters Index of this SA
704f7d24e3fSHanoh Haim        @param seq_num: the initial value for the sequence number on encrypted
705f7d24e3fSHanoh Haim                        packets
706f7d24e3fSHanoh Haim        @param crypt_algo: the encryption algorithm name (only used with ESP)
707f7d24e3fSHanoh Haim        @param crypt_key: the encryption key (only used with ESP)
708f7d24e3fSHanoh Haim        @param auth_algo: the integrity algorithm name
709f7d24e3fSHanoh Haim        @param auth_key: the integrity key
710f7d24e3fSHanoh Haim        @param tunnel_header: an instance of a IP(v6) header that will be used
711f7d24e3fSHanoh Haim                              to encapsulate the encrypted packets.
712f7d24e3fSHanoh Haim        @param nat_t_header: an instance of a UDP header that will be used
713f7d24e3fSHanoh Haim                             for NAT-Traversal.
714f7d24e3fSHanoh Haim        """
715f7d24e3fSHanoh Haim
716f7d24e3fSHanoh Haim        if proto not in (ESP, AH, ESP.name, AH.name):
717f7d24e3fSHanoh Haim            raise ValueError("proto must be either ESP or AH")
718f7d24e3fSHanoh Haim        if isinstance(proto, basestring):
719f7d24e3fSHanoh Haim            self.proto = eval(proto)
720f7d24e3fSHanoh Haim        else:
721f7d24e3fSHanoh Haim            self.proto = proto
722f7d24e3fSHanoh Haim
723f7d24e3fSHanoh Haim        self.spi = spi
724f7d24e3fSHanoh Haim        self.seq_num = seq_num
725f7d24e3fSHanoh Haim
726f7d24e3fSHanoh Haim        if crypt_algo:
727f7d24e3fSHanoh Haim            if crypt_algo not in CRYPT_ALGOS:
728f7d24e3fSHanoh Haim                raise TypeError('unsupported encryption algo %r, try %r' %
729f7d24e3fSHanoh Haim                                (crypt_algo, CRYPT_ALGOS.keys()))
730f7d24e3fSHanoh Haim            self.crypt_algo = CRYPT_ALGOS[crypt_algo]
731f7d24e3fSHanoh Haim            self.crypt_algo.check_key(crypt_key)
732f7d24e3fSHanoh Haim            self.crypt_key = crypt_key
733f7d24e3fSHanoh Haim        else:
734f7d24e3fSHanoh Haim            self.crypt_algo = CRYPT_ALGOS['NULL']
735f7d24e3fSHanoh Haim            self.crypt_key = None
736f7d24e3fSHanoh Haim
737f7d24e3fSHanoh Haim        if auth_algo:
738f7d24e3fSHanoh Haim            if auth_algo not in AUTH_ALGOS:
739f7d24e3fSHanoh Haim                raise TypeError('unsupported integrity algo %r, try %r' %
740f7d24e3fSHanoh Haim                                (auth_algo, AUTH_ALGOS.keys()))
741f7d24e3fSHanoh Haim            self.auth_algo = AUTH_ALGOS[auth_algo]
742f7d24e3fSHanoh Haim            self.auth_algo.check_key(auth_key)
743f7d24e3fSHanoh Haim            self.auth_key = auth_key
744f7d24e3fSHanoh Haim        else:
745f7d24e3fSHanoh Haim            self.auth_algo = AUTH_ALGOS['NULL']
746f7d24e3fSHanoh Haim            self.auth_key = None
747f7d24e3fSHanoh Haim
748f7d24e3fSHanoh Haim        if tunnel_header and not isinstance(tunnel_header, (IP, IPv6)):
749f7d24e3fSHanoh Haim            raise TypeError('tunnel_header must be %s or %s' % (IP.name, IPv6.name))
750f7d24e3fSHanoh Haim        self.tunnel_header = tunnel_header
751f7d24e3fSHanoh Haim
752f7d24e3fSHanoh Haim        if nat_t_header:
753f7d24e3fSHanoh Haim            if proto is not ESP:
754f7d24e3fSHanoh Haim                raise TypeError('nat_t_header is only allowed with ESP')
755f7d24e3fSHanoh Haim            if not isinstance(nat_t_header, UDP):
756f7d24e3fSHanoh Haim                raise TypeError('nat_t_header must be %s' % UDP.name)
757f7d24e3fSHanoh Haim        self.nat_t_header = nat_t_header
758f7d24e3fSHanoh Haim
759f7d24e3fSHanoh Haim    def check_spi(self, pkt):
760f7d24e3fSHanoh Haim        if pkt.spi != self.spi:
761f7d24e3fSHanoh Haim            raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' %
762f7d24e3fSHanoh Haim                            (pkt.spi, self.spi))
763f7d24e3fSHanoh Haim
764f7d24e3fSHanoh Haim    def _encrypt_esp(self, pkt, seq_num=None, iv=None):
765f7d24e3fSHanoh Haim
766f7d24e3fSHanoh Haim        if iv is None:
767f7d24e3fSHanoh Haim            iv = self.crypt_algo.generate_iv()
768f7d24e3fSHanoh Haim        else:
769f7d24e3fSHanoh Haim            if len(iv) != self.crypt_algo.iv_size:
770f7d24e3fSHanoh Haim                raise TypeError('iv length must be %s' % self.crypt_algo.iv_size)
771f7d24e3fSHanoh Haim
772f7d24e3fSHanoh Haim        esp = _ESPPlain(spi=self.spi, seq=seq_num or self.seq_num, iv=iv)
773f7d24e3fSHanoh Haim
774f7d24e3fSHanoh Haim        if self.tunnel_header:
775f7d24e3fSHanoh Haim            tunnel = self.tunnel_header.copy()
776f7d24e3fSHanoh Haim
777f7d24e3fSHanoh Haim            if tunnel.version == 4:
778f7d24e3fSHanoh Haim                del tunnel.proto
779f7d24e3fSHanoh Haim                del tunnel.len
780f7d24e3fSHanoh Haim                del tunnel.chksum
781f7d24e3fSHanoh Haim            else:
782f7d24e3fSHanoh Haim                del tunnel.nh
783f7d24e3fSHanoh Haim                del tunnel.plen
784f7d24e3fSHanoh Haim
785f7d24e3fSHanoh Haim            pkt = tunnel.__class__(str(tunnel / pkt))
786f7d24e3fSHanoh Haim
787f7d24e3fSHanoh Haim        ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_ESP)
788f7d24e3fSHanoh Haim        esp.data = payload
789f7d24e3fSHanoh Haim        esp.nh = nh
790f7d24e3fSHanoh Haim
791f7d24e3fSHanoh Haim        esp = self.crypt_algo.pad(esp)
792f7d24e3fSHanoh Haim        esp = self.crypt_algo.encrypt(esp, self.crypt_key)
793f7d24e3fSHanoh Haim
794f7d24e3fSHanoh Haim        self.auth_algo.sign(esp, self.auth_key)
795f7d24e3fSHanoh Haim
796f7d24e3fSHanoh Haim        if self.nat_t_header:
797f7d24e3fSHanoh Haim            nat_t_header = self.nat_t_header.copy()
798f7d24e3fSHanoh Haim            nat_t_header.chksum = 0
799f7d24e3fSHanoh Haim            del nat_t_header.len
800f7d24e3fSHanoh Haim            if ip_header.version == 4:
801f7d24e3fSHanoh Haim                del ip_header.proto
802f7d24e3fSHanoh Haim            else:
803f7d24e3fSHanoh Haim                del ip_header.nh
804f7d24e3fSHanoh Haim            ip_header /= nat_t_header
805f7d24e3fSHanoh Haim
806f7d24e3fSHanoh Haim        if ip_header.version == 4:
807f7d24e3fSHanoh Haim            ip_header.len = len(ip_header) + len(esp)
808f7d24e3fSHanoh Haim            del ip_header.chksum
809f7d24e3fSHanoh Haim            ip_header = ip_header.__class__(str(ip_header))
810f7d24e3fSHanoh Haim        else:
811f7d24e3fSHanoh Haim            ip_header.plen = len(ip_header.payload) + len(esp)
812f7d24e3fSHanoh Haim
813f7d24e3fSHanoh Haim        # sequence number must always change, unless specified by the user
814f7d24e3fSHanoh Haim        if seq_num is None:
815f7d24e3fSHanoh Haim            self.seq_num += 1
816f7d24e3fSHanoh Haim
817f7d24e3fSHanoh Haim        return ip_header / esp
818f7d24e3fSHanoh Haim
819f7d24e3fSHanoh Haim    def _encrypt_ah(self, pkt, seq_num=None):
820f7d24e3fSHanoh Haim
821f7d24e3fSHanoh Haim        ah = AH(spi=self.spi, seq=seq_num or self.seq_num,
822f7d24e3fSHanoh Haim                icv=chr(0) * self.auth_algo.icv_size)
823f7d24e3fSHanoh Haim
824f7d24e3fSHanoh Haim        if self.tunnel_header:
825f7d24e3fSHanoh Haim            tunnel = self.tunnel_header.copy()
826f7d24e3fSHanoh Haim
827f7d24e3fSHanoh Haim            if tunnel.version == 4:
828f7d24e3fSHanoh Haim                del tunnel.proto
829f7d24e3fSHanoh Haim                del tunnel.len
830f7d24e3fSHanoh Haim                del tunnel.chksum
831f7d24e3fSHanoh Haim            else:
832f7d24e3fSHanoh Haim                del tunnel.nh
833f7d24e3fSHanoh Haim                del tunnel.plen
834f7d24e3fSHanoh Haim
835f7d24e3fSHanoh Haim            pkt = tunnel.__class__(str(tunnel / pkt))
836f7d24e3fSHanoh Haim
837f7d24e3fSHanoh Haim        ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_AH)
838f7d24e3fSHanoh Haim        ah.nh = nh
839f7d24e3fSHanoh Haim
840f7d24e3fSHanoh Haim        if ip_header.version == 6 and len(ah) % 8 != 0:
841f7d24e3fSHanoh Haim            # For IPv6, the total length of the header must be a multiple of
842f7d24e3fSHanoh Haim            # 8-octet units.
843f7d24e3fSHanoh Haim            ah.padding = chr(0) * (-len(ah) % 8)
844f7d24e3fSHanoh Haim        elif len(ah) % 4 != 0:
845f7d24e3fSHanoh Haim            # For IPv4, the total length of the header must be a multiple of
846f7d24e3fSHanoh Haim            # 4-octet units.
847f7d24e3fSHanoh Haim            ah.padding = chr(0) * (-len(ah) % 4)
848f7d24e3fSHanoh Haim
849f7d24e3fSHanoh Haim        # RFC 4302 - Section 2.2. Payload Length
850f7d24e3fSHanoh Haim        # This 8-bit field specifies the length of AH in 32-bit words (4-byte
851f7d24e3fSHanoh Haim        # units), minus "2".
852f7d24e3fSHanoh Haim        ah.payloadlen = len(ah) / 4 - 2
853f7d24e3fSHanoh Haim
854f7d24e3fSHanoh Haim        if ip_header.version == 4:
855f7d24e3fSHanoh Haim            ip_header.len = len(ip_header) + len(ah) + len(payload)
856f7d24e3fSHanoh Haim            del ip_header.chksum
857f7d24e3fSHanoh Haim            ip_header = ip_header.__class__(str(ip_header))
858f7d24e3fSHanoh Haim        else:
859f7d24e3fSHanoh Haim            ip_header.plen = len(ip_header.payload) + len(ah) + len(payload)
860f7d24e3fSHanoh Haim
861f7d24e3fSHanoh Haim        signed_pkt = self.auth_algo.sign(ip_header / ah / payload, self.auth_key)
862f7d24e3fSHanoh Haim
863f7d24e3fSHanoh Haim        # sequence number must always change, unless specified by the user
864f7d24e3fSHanoh Haim        if seq_num is None:
865f7d24e3fSHanoh Haim            self.seq_num += 1
866f7d24e3fSHanoh Haim
867f7d24e3fSHanoh Haim        return signed_pkt
868f7d24e3fSHanoh Haim
869f7d24e3fSHanoh Haim    def encrypt(self, pkt, seq_num=None, iv=None):
870f7d24e3fSHanoh Haim        """
871f7d24e3fSHanoh Haim        Encrypt (and encapsulate) an IP(v6) packet with ESP or AH according
872f7d24e3fSHanoh Haim        to this SecurityAssociation.
873f7d24e3fSHanoh Haim
874f7d24e3fSHanoh Haim        @param pkt:     the packet to encrypt
875f7d24e3fSHanoh Haim        @param seq_num: if specified, use this sequence number instead of the
876f7d24e3fSHanoh Haim                        generated one
877f7d24e3fSHanoh Haim        @param iv:      if specified, use this initialization vector for
878f7d24e3fSHanoh Haim                        encryption instead of a random one.
879f7d24e3fSHanoh Haim
880f7d24e3fSHanoh Haim        @return: the encrypted/encapsulated packet
881f7d24e3fSHanoh Haim        """
882f7d24e3fSHanoh Haim        if not isinstance(pkt, self.SUPPORTED_PROTOS):
883f7d24e3fSHanoh Haim            raise TypeError('cannot encrypt %s, supported protos are %s'
884f7d24e3fSHanoh Haim                            % (pkt.__class__, self.SUPPORTED_PROTOS))
885f7d24e3fSHanoh Haim        if self.proto is ESP:
886f7d24e3fSHanoh Haim            return self._encrypt_esp(pkt, seq_num=seq_num, iv=iv)
887f7d24e3fSHanoh Haim        else:
888f7d24e3fSHanoh Haim            return self._encrypt_ah(pkt, seq_num=seq_num)
889f7d24e3fSHanoh Haim
890f7d24e3fSHanoh Haim    def _decrypt_esp(self, pkt, verify=True):
891f7d24e3fSHanoh Haim
892f7d24e3fSHanoh Haim        encrypted = pkt[ESP]
893f7d24e3fSHanoh Haim
894f7d24e3fSHanoh Haim        if verify:
895f7d24e3fSHanoh Haim            self.check_spi(pkt)
896f7d24e3fSHanoh Haim            self.auth_algo.verify(encrypted, self.auth_key)
897f7d24e3fSHanoh Haim
898f7d24e3fSHanoh Haim        esp = self.crypt_algo.decrypt(encrypted, self.crypt_key,
899f7d24e3fSHanoh Haim                                      self.auth_algo.icv_size)
900f7d24e3fSHanoh Haim
901f7d24e3fSHanoh Haim        if self.tunnel_header:
902f7d24e3fSHanoh Haim            # drop the tunnel header and return the payload untouched
903f7d24e3fSHanoh Haim
904f7d24e3fSHanoh Haim            pkt.remove_payload()
905f7d24e3fSHanoh Haim            if pkt.version == 4:
906f7d24e3fSHanoh Haim                pkt.proto = esp.nh
907f7d24e3fSHanoh Haim            else:
908f7d24e3fSHanoh Haim                pkt.nh = esp.nh
909f7d24e3fSHanoh Haim            cls = pkt.guess_payload_class(esp.data)
910f7d24e3fSHanoh Haim
911f7d24e3fSHanoh Haim            return cls(esp.data)
912f7d24e3fSHanoh Haim        else:
913f7d24e3fSHanoh Haim            ip_header = pkt
914f7d24e3fSHanoh Haim
915f7d24e3fSHanoh Haim            if ip_header.version == 4:
916f7d24e3fSHanoh Haim                ip_header.proto = esp.nh
917f7d24e3fSHanoh Haim                del ip_header.chksum
918f7d24e3fSHanoh Haim                ip_header.remove_payload()
919f7d24e3fSHanoh Haim                ip_header.len = len(ip_header) + len(esp.data)
920f7d24e3fSHanoh Haim                # recompute checksum
921f7d24e3fSHanoh Haim                ip_header = ip_header.__class__(str(ip_header))
922f7d24e3fSHanoh Haim            else:
923f7d24e3fSHanoh Haim                encrypted.underlayer.nh = esp.nh
924f7d24e3fSHanoh Haim                encrypted.underlayer.remove_payload()
925f7d24e3fSHanoh Haim                ip_header.plen = len(ip_header.payload) + len(esp.data)
926f7d24e3fSHanoh Haim
927f7d24e3fSHanoh Haim            cls = ip_header.guess_payload_class(esp.data)
928f7d24e3fSHanoh Haim
929f7d24e3fSHanoh Haim            # reassemble the ip_header with the ESP payload
930f7d24e3fSHanoh Haim            return ip_header / cls(esp.data)
931f7d24e3fSHanoh Haim
932f7d24e3fSHanoh Haim    def _decrypt_ah(self, pkt, verify=True):
933f7d24e3fSHanoh Haim
934f7d24e3fSHanoh Haim        if verify:
935f7d24e3fSHanoh Haim            self.check_spi(pkt)
936f7d24e3fSHanoh Haim            self.auth_algo.verify(pkt, self.auth_key)
937f7d24e3fSHanoh Haim
938f7d24e3fSHanoh Haim        ah = pkt[AH]
939f7d24e3fSHanoh Haim        payload = ah.payload
940f7d24e3fSHanoh Haim        payload.remove_underlayer(None)  # useless argument...
941f7d24e3fSHanoh Haim
942f7d24e3fSHanoh Haim        if self.tunnel_header:
943f7d24e3fSHanoh Haim            return payload
944f7d24e3fSHanoh Haim        else:
945f7d24e3fSHanoh Haim            ip_header = pkt
946f7d24e3fSHanoh Haim
947f7d24e3fSHanoh Haim            if ip_header.version == 4:
948f7d24e3fSHanoh Haim                ip_header.proto = ah.nh
949f7d24e3fSHanoh Haim                del ip_header.chksum
950f7d24e3fSHanoh Haim                ip_header.remove_payload()
951f7d24e3fSHanoh Haim                ip_header.len = len(ip_header) + len(payload)
952f7d24e3fSHanoh Haim                # recompute checksum
953f7d24e3fSHanoh Haim                ip_header = ip_header.__class__(str(ip_header))
954f7d24e3fSHanoh Haim            else:
955f7d24e3fSHanoh Haim                ah.underlayer.nh = ah.nh
956f7d24e3fSHanoh Haim                ah.underlayer.remove_payload()
957f7d24e3fSHanoh Haim                ip_header.plen = len(ip_header.payload) + len(payload)
958f7d24e3fSHanoh Haim
959f7d24e3fSHanoh Haim            # reassemble the ip_header with the AH payload
960f7d24e3fSHanoh Haim            return ip_header / payload
961f7d24e3fSHanoh Haim
962f7d24e3fSHanoh Haim    def decrypt(self, pkt, verify=True):
963f7d24e3fSHanoh Haim        """
964f7d24e3fSHanoh Haim        Decrypt (and decapsulate) an IP(v6) packet containing ESP or AH.
965f7d24e3fSHanoh Haim
966f7d24e3fSHanoh Haim        @param pkt:     the packet to decrypt
967f7d24e3fSHanoh Haim        @param verify:  if False, do not perform the integrity check
968f7d24e3fSHanoh Haim
969f7d24e3fSHanoh Haim        @return: the decrypted/decapsulated packet
970f7d24e3fSHanoh Haim        @raise IPSecIntegrityError: if the integrity check fails
971f7d24e3fSHanoh Haim        """
972f7d24e3fSHanoh Haim        if not isinstance(pkt, self.SUPPORTED_PROTOS):
973f7d24e3fSHanoh Haim            raise TypeError('cannot decrypt %s, supported protos are %s'
974f7d24e3fSHanoh Haim                            % (pkt.__class__, self.SUPPORTED_PROTOS))
975f7d24e3fSHanoh Haim
976f7d24e3fSHanoh Haim        if self.proto is ESP and pkt.haslayer(ESP):
977f7d24e3fSHanoh Haim            return self._decrypt_esp(pkt, verify=verify)
978f7d24e3fSHanoh Haim        elif self.proto is AH and pkt.haslayer(AH):
979f7d24e3fSHanoh Haim            return self._decrypt_ah(pkt, verify=verify)
980f7d24e3fSHanoh Haim        else:
981f7d24e3fSHanoh Haim            raise TypeError('%s has no %s layer' % (pkt, self.proto.name))