diff --git a/.travis.yml b/.travis.yml
index 1ed6cb21a..7ce325581 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,6 +28,13 @@ matrix:
- TRAVIS_SUDO=sudo
- SCAPY_USE_PCAPDNET=yes
+ - os: linux
+ sudo: required
+ python: 2.7
+ env:
+ - TRAVIS_SUDO=sudo
+ - TEST_COMBINED_MODES=yes
+
- os: osx
language: generic
env:
diff --git a/.travis/install.sh b/.travis/install.sh
index fd0e68763..79f41dd7f 100644
--- a/.travis/install.sh
+++ b/.travis/install.sh
@@ -3,7 +3,18 @@ if [ -z $TRAVIS_SUDO ] && [ "$TRAVIS_OS_NAME" = "osx" ]
then
PIP_INSTALL_FLAGS="--user"
fi
-$TRAVIS_SUDO pip install $PIP_INSTALL_FLAGS pycrypto ecdsa mock
+$TRAVIS_SUDO pip install $PIP_INSTALL_FLAGS ecdsa mock
+
+# Pycrypto 2.7a1 isn't available on PyPi
+if [ "$TEST_COMBINED_MODES" = "yes" ]
+then
+ curl -sL https://github.com/dlitz/pycrypto/archive/v2.7a1.tar.gz | tar xz
+ cd pycrypto-2.7a1
+ python setup.py build
+ $TRAVIS_SUDO python setup.py install
+else
+ $TRAVIS_SUDO pip install $PIP_INSTALL_FLAGS pycrypto
+fi
# Install pcap & dnet
if [ ! -z $SCAPY_USE_PCAPDNET ]
diff --git a/.travis/test.sh b/.travis/test.sh
index fca107715..870559a87 100644
--- a/.travis/test.sh
+++ b/.travis/test.sh
@@ -4,7 +4,13 @@ python -c "from scapy.all import *; print conf"
# Don't run tests that requires root privileges
if [ -z $TRAVIS_SUDO ]
then
- UT_FLAGS="-K netaccess"
+ UT_FLAGS="-K netaccess "
+fi
+
+# Test AEAD modes in IPSec if available
+if [ "$TEST_COMBINED_MODES" != "yes" ]
+then
+ UT_FLAGS+="-K combined_modes "
fi
# Run unit tests
diff --git a/doc/scapy/installation.rst b/doc/scapy/installation.rst
index dde7a71c3..91fa69f0a 100644
--- a/doc/scapy/installation.rst
+++ b/doc/scapy/installation.rst
@@ -190,6 +190,18 @@ Here are the topics involved and some examples that you can use to try if your i
* VOIP. ``voip_play()`` needs `SoX `_.
+* IPSec Crypto Support. ``SecurityAssociation()`` needs `Pycrypto `_. Combined AEAD modes such as GCM and CCM require pycrypto2.7a1, which is only available from source (no pip or package).
+
+ .. code-block:: text
+
+ # pycrypto 2.6 install
+ sudo pip install pycrypto
+
+ # pycrypto 2.7a1 install
+ curl -sL https://github.com/dlitz/pycrypto/archive/v2.7a1.tar.gz | tar xz
+ cd pycrypto-2.7a1
+ python setup.py build
+ sudo python setup.py install
Platform-specific instructions
==============================
diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py
index b61861ec8..431bc20aa 100644
--- a/scapy/layers/ipsec.py
+++ b/scapy/layers/ipsec.py
@@ -41,6 +41,8 @@ True
import socket
import struct
+from scapy.error import warning
+
try:
from Crypto.Util.number import GCD as gcd
except ImportError:
@@ -152,6 +154,7 @@ try:
from Crypto import Random
except ImportError:
# no error if pycrypto is not available but encryption won't be supported
+ warning("IPSec encryption not supported (pycrypto required).")
AES = None
DES = None
DES3 = None
@@ -159,6 +162,12 @@ except ImportError:
Blowfish = None
Random = None
+try:
+ from Crypto.Cipher.AES import MODE_GCM
+ from Crypto.Cipher.AES import MODE_CCM
+except ImportError:
+ warning("Combined crypto modes not available for IPSec (pycrypto 2.7a1 required).")
+
#------------------------------------------------------------------------------
def _lcm(a, b):
"""
@@ -186,16 +195,20 @@ class CryptAlgo(object):
@param key_size: an integer or list/tuple of integers. If specified,
force the secret keys length to one of the values.
Defaults to the `key_size` of the cipher.
+ @param icv_size: the length of the integrity check value of this algo.
+ Only used in this class for AEAD algorithms.
"""
self.name = name
self.cipher = cipher
self.mode = mode
- self.icv_size = icv_size
self.is_aead = (hasattr(self.cipher, 'MODE_GCM') and
self.mode == self.cipher.MODE_GCM) or \
(hasattr(self.cipher, 'MODE_CCM') and
self.mode == self.cipher.MODE_CCM)
+ if icv_size is not None:
+ self.icv_size = icv_size
+
if block_size is not None:
self.block_size = block_size
elif cipher is not None:
@@ -389,19 +402,23 @@ if AES:
block_size=1,
iv_size=8,
key_size=(16 + 4, 24 + 4, 32 + 4))
+
+ # AEAD algorithms are only supported in pycrypto 2.7a1+
+ # they also have an additional field icv_size, which is usually
+ # populated by an auth algo when signing and verifying signatures.
if hasattr(AES, "MODE_GCM"):
CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM',
cipher=AES,
mode=AES.MODE_GCM,
iv_size=8,
- icv_size=8,
+ icv_size=16,
key_size=(16 + 4, 24 + 4, 32 + 4))
if hasattr(AES, "MODE_CCM"):
CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM',
cipher=AES,
mode=AES.MODE_CCM,
iv_size=8,
- icv_size=8,
+ icv_size=16,
key_size=(16 + 4, 24 + 4, 32 + 4))
if DES:
CRYPT_ALGOS['DES'] = CryptAlgo('DES',
diff --git a/test/ipsec.uts b/test/ipsec.uts
index bf13075ae..dfa8ce1c7 100644
--- a/test/ipsec.uts
+++ b/test/ipsec.uts
@@ -139,6 +139,76 @@ d
* after decryption original packet should be preserved
assert(d == p)
+#######################################
+= IPv4 / ESP - Tunnel - AES-GCM - NULL
+~ combined_modes
+
+p = IP(src='1.1.1.1', dst='2.2.2.2')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IP(str(p))
+p
+
+sa = SecurityAssociation(ESP, spi=0x222,
+ crypt_algo='AES-GCM', crypt_key='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)
+e
+
+assert(isinstance(e, IP))
+* after encryption packet should be encapsulated with the given ip tunnel header
+assert(e.src == '11.11.11.11' and e.dst == '22.22.22.22')
+assert(e.chksum != p.chksum)
+assert(e.proto == socket.IPPROTO_ESP)
+assert(e.haslayer(ESP))
+assert(not e.haslayer(TCP))
+assert(e[ESP].spi == sa.spi)
+* after encryption the original packet payload should NOT be readable
+assert('testdata' not in e[ESP].data)
+
+d = sa.decrypt(e)
+d
+
+* after decryption original packet should be preserved
+assert(d == p)
+
+#######################################
+= IPv4 / ESP - Tunnel - AES-CCM - NULL
+~ combined_modes
+
+p = IP(src='1.1.1.1', dst='2.2.2.2')
+p /= TCP(sport=45012, dport=80)
+p /= Raw('testdata')
+p = IP(str(p))
+p
+
+sa = SecurityAssociation(ESP, spi=0x222,
+ crypt_algo='AES-CCM', crypt_key='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)
+e
+
+assert(isinstance(e, IP))
+* after encryption packet should be encapsulated with the given ip tunnel header
+assert(e.src == '11.11.11.11' and e.dst == '22.22.22.22')
+assert(e.chksum != p.chksum)
+assert(e.proto == socket.IPPROTO_ESP)
+assert(e.haslayer(ESP))
+assert(not e.haslayer(TCP))
+assert(e[ESP].spi == sa.spi)
+* after encryption the original packet payload should NOT be readable
+assert('testdata' not in e[ESP].data)
+
+d = sa.decrypt(e)
+d
+
+* after decryption original packet should be preserved
+assert(d == p)
+
#######################################
= IPv4 / ESP - Tunnel - NULL - SHA2-256-128