Scapy extensions

 


IPv6 (Scapy6) [merged upstream]

In 2006, Guillaume Valadon and I started working on IPv6 support for Scapy. This resulted on a parallel IPv6-enabled version of Scapy named Scapy6.

Support for most of the protocols associated with IPv6 (ICMPv6, Neighbor Discovery, MLD, MRD, DHCPv6, MIPv6 ...) was progressively added to the extension.

In August 2008, Phil started merging our work in version 2.0 of Scapy and quickly completed that merge.

As a conclusion, Scapy6 is now deprecated and you can benefit from IPv6 protocol suite support directly in Scapy.

$ hg clone http://hg.secdev.org/scapy
destination directory: scapy
requesting all changes
adding changesets
adding manifests
adding file changes
added 1195 changesets with 1641 changes to 145 files
updating working directory
129 files updated, 0 files merged, 0 files removed, 0 files unresolved

$ cd scapy/

$ sudo ./run_scapy
Welcome to Scapy (2.1.0-dev)
>>> traceroute6("www.google.com", maxttl=15)
Begin emission:
.....*..*..*..*..*....*******.*.***Finished to send 30 packets.
**......*.***...*..**.......................................................
Received 158 packets, got 10 answers, remaining 5 packets
   2a00:1450:8002:0000:0000:0000:0000:0068   :tcpwww 
1  2001:7a8:8:8::8                            3      
2  2001:7a8:0:c951::1                         3      
3  2001:7a8:1:131::1                          3      
4  2001:7a8:1:30::2                           3      
5  2001:860:0:6:0:1:5169:1                    3      
6  2001:4860:0:1::25                          3      
7  2a00:1450:8002::68                         SA     
8  2a00:1450:8002::68                         SA     
9  2a00:1450:8002::68                         SA     
10 2a00:1450:8002::68                         SA     
(<Traceroute: TCP:4 UDP:0 ICMP:0 Other:6>, <Unanswered: TCP:5 UDP:0 ICMP:0 Other:0>)

 


X.509 Cert, CRL and Key [merged upstream]

A while ago (I was still using CVS at that time), I was working on extensions which required support for X.509 Certificates, Keys and CRL. The idea was to be able to import X.509 certificates, CRL and keys as objects in Python scripts (and Scapy obviously) and benefit from useful methods to sign, verify, encrypt, decrypt and do a lot more. I started writing a standalone pure Python module for that purpose: cert.py

You might be wondering why I did not simply reused existing frameworks to do that (python-crypto, M2crypto, pyOpenSSL, ...). They were limited on some aspects and most importantly I wanted to spend time on the protocols (PKCS#1, RFC 3447, RFC 5280 ...) and come with a pure Python implementation of the useful methods in order to be able to modify them at will.

As I did not want to deal with the ASN.1 parsing directly (ASN.1 support was not yet in Scapy and I also did not want the module to depend on Scapy at that time anyway), I decided to implement a temporary (scurvy) hack for X.509 certificates, CRL and keys import: depend on openssl binary and parse the output of its subcommands for that purpose. Import could be improved later without breaking the API provided by the module. The benefit is that you can drop cert.py on a system with OpenSSL installed and start using it directly.

Anyway, if you want to use the module:

The 3 main classes are Cert, Key and CRL. The code is commented and IMHO easy to read. All main methods have associated docstrings so it is pretty easy to understand what can be done. If you need examples, you can clone the repository (hg clone http://hg.natisbad.org/cert) and take a look at cert.uts file. Don't hesitate to bug me by email or post a message on Scapy Mailing List if you have additional questions.

Here is a small overview of what the module can be used for:

arno@small:/tmp/$ hg clone http://hg.secdev.org/scapy          # clone Scapy
destination directory: scapy
requesting all changes
adding changesets
adding manifests
adding file changes
added 1195 changesets with 1641 changes to 145 files
updating to branch default
129 files updated, 0 files merged, 0 files removed, 0 files unresolved

arno@small:/tmp/scapy$ openssl genrsa 4096 >  key.pem          # generate a RSA key pair
Generating RSA private key, 4096 bit long modulus
....................................................................................++
..........................................................++
unable to write 'random state'
e is 65537 (0x10001)

                                                               # Generate a self signed cert
arno@small:/tmp/scapy$ openssl req -new -x509 -nodes -sha1 -days 365 -key key.pem > cert.pem
Country Name (2 letter code) [AU]:FR
State or Province Name (full name) [Some-State]:France
Locality Name (eg, city) []:Paris
Organization Name (eg, company) [Internet Widgits Pty Ltd]:natisbad.org
Organizational Unit Name (eg, section) []:PKI Services Unit
Common Name (eg, YOUR name) []:Arnaud Ebalard
Email Address []:arno@natisbad.org

arno@small:/tmp/scapy$ ./run_scapy                             # launch scapy
Welcome to Scapy (2.1.0-dev)

>>> from scapy.crypto.cert import *                            # import cert module

>>> c = Cert("cert.pem")                                       # import our self signed cert as c

>>> c.show()
Serial: 827EA38F7A0E025A
Issuer: C=FR, ST=France, L=Paris, ... , CN=Arnaud Ebalard/emailAddress=arno@natisbad.org
Subject: C=FR, ST=France, L=Paris, ... , CN=Arnaud Ebalard/emailAddress=arno@natisbad.org
Validity: 01/01/10 to 01/01/11

>>> k=Key("key.pem")                                           # import associated key pair as k

>>> help(k.sign)                                               # How does .sign() work?
Help on method sign in module scapy.crypto.cert:

sign(self, M, t=None, h=None, mgf=None, sLen=None) method of scapy.crypto.cert.Key instance
    Sign message 'M' using 't' signature scheme where 't' can be:
    
    - None: the message 'M' is directly applied the RSASP1 signature
            primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect
            5.2.1. Simply put, the message undergo a modular exponentiation
            using the private key. Additionnal method parameters are just
            ignored.
    
    - 'pkcs': the message 'M' is applied RSASSA-PKCS1-v1_5-SIGN signature
            scheme as described in Sect. 8.2.1 of RFC 3447. In that context,
            the hash function name is passed using 'h'. Possible values are
            "md2", "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384"
            and "sha512". If none is provided, sha1 is used. Other additionnal 
            parameters are ignored.
    
    - 'pss' : the message 'M' is applied RSASSA-PSS-SIGN signature scheme as
            described in Sect. 8.1.1. of RFC 3447. In that context,
    
            o 'h' parameter provides the name of the hash method to use.
               Possible values are "md2", "md4", "md5", "sha1", "tls", "sha224",
               "sha256", "sha384" and "sha512". if none is provided, sha1
               is used. 
    
            o 'mgf' is the mask generation function. By default, mgf
               is derived from the provided hash function using the
               generic MGF1 (see pkcs_mgf1() for details).
    
            o 'sLen' is the length in octet of the salt. You can overload the
              default value (the octet length of the hash value for provided
              algorithm) by providing another one with that parameter.

>>> M = "message to be signed"

>>> k.sign(M, t="pss")                                         # sign M (pss w/ SHA1)
"\xb6[\x820\xff\xf4V\xc1\x130\xd8\x80 ... \xf4rYaq\xb1\xbd\xdc'\xcaZoo\xf4"

>>> S=_

>>> c.verify(M, S, t="pss")                                    # verify signature
True

>>> M = "message to be encrypted"
>>> c.encrypt(M, t='oaep')                                     # (public) encrypt M
'r\x08~\x9b\xeb\xcd\x80\xc2\xbb\x0e_V ... \xff\xbf\xa4H%\xffr(\x83\xa5\x80'
>>> C=_
>>> k.decrypt(C, t='oaep')                                     # (private) decrypt C
'message to be encrypted'

                                                               # Overview of Cert methods
>>> c.           # TAB TAB (i.e. hit tabulation twice for completion)
c.__doc__                    c.dercert                    c.output
c.__init__                   c.encrypt                    c.pemcert
c.__module__                 c.exponent                   c.possible_fields
c.__repr__                   c.export                     c.possible_fields_count
c.__str__                    c.extKeyUsage                c.pubKeyAlg
c._apply_ossl_cmd            c.format                     c.rawcert
c._rsaep                     c.isIssuerCert               c.remainingDays
c._rsaes_oaep_encrypt        c.isSelfSigned               c.serial
c._rsaes_pkcs1_v1_5_encrypt  c.is_revoked                 c.show
c._rsassa_pkcs1_v1_5_verify  c.issuer                     c.sig
c._rsassa_pss_verify         c.key                        c.sigAlg
c._rsavp1                    c.keyHash                    c.sigLen
c.asn1parsecert              c.keyUsage                   c.subject
c.authorityInfoAccess        c.modulus                    c.subjectKeyID
c.authorityKeyID             c.modulusLen                 c.tbsCertificate
c.authorityKeyID_dirname     c.modulus_hexdump            c.textcert
c.authorityKeyID_keyid       c.notAfter                   c.verify
c.authorityKeyID_serial      c.notAfter_str               c.verifychain
c.basicConstraints           c.notAfter_str_simple        c.verifychain_from_cafile
c.basicConstraintsCritical   c.notBefore                  c.verifychain_from_capath
c.cRLDistributionPoints      c.notBefore_str              c.version
c.certpath                   c.notBefore_str_simple       
c.chain                      c.osslcmdbase                

>>> c.isSelfSigned()
True
>>> c.notAfter
(2011, 1, 1, 17, 40, 59, 5, 1, 0)
>>> c.notAfter_str
'Jan  1 17:40:59 2011 GMT'
>>> c.remainingDays()
364.91947916666669
>>> c.version
3

                                                                    # Overview of Key methods
>>> k.           # TAB TAB (i.e. hit tabulation twice for completion)
k.__doc__                    k._rsassa_pss_sign           k.modulusLen
k.__init__                   k._rsassa_pss_verify         k.osslcmdbase
k.__module__                 k._rsavp1                    k.pemkey
k.__str__                    k.asn1parsekey               k.possible_fields
k._apply_ossl_cmd            k.coefficient                k.possible_fields_count
k._rsadp                     k.decrypt                    k.prime1
k._rsaep                     k.derkey                     k.prime2
k._rsaes_oaep_decrypt        k.encrypt                    k.privExp
k._rsaes_oaep_encrypt        k.exponent1                  k.pubExp
k._rsaes_pkcs1_v1_5_decrypt  k.exponent2                  k.rawkey
k._rsaes_pkcs1_v1_5_encrypt  k.format                     k.sign
k._rsasp1                    k.key                        k.textkey
k._rsassa_pkcs1_v1_5_sign    k.keypath                    k.verify
k._rsassa_pkcs1_v1_5_verify  k.modulus                    

                                                                    # CRL is left as exercise
                                                                    # for curious reader

One (IMHO) interesting direction that I have currently no time to follow would be to progressively remove the dependency of cert.py on openssl binary in Scapy in order to replace it by the now available internal ASN.1 support. This would allow modifying (e.g. fuzzing) certificates and CRL structure for instance: if you have the time, I bet this is almost guaranteed to allow you to find bugs in various crypto frameworks.

Disclaimer: the module has been developed as a convenience for testing purposes only. Do not rely on it for real security needs (e.g. production apps). You have been warned.

 


PF_KEY

While performing MIPv6 and IPsec related work under Linux, I often had the need to monitor what was going on at PF_KEY level. Even if ip xfrm monitor or setkey -x are of some help on that aspect, they are also very limited when you are used to tcpdump, wireshark or Scapy. They also do not allow injecting PF_KEY messages.

So, I started adding support for PF_KEY to Scapy (more precisely to Scapy6 because IPv6 support had not been merged upstream at that time). I used RFC 2367, KAME extensions for policy management and Linux kernel code as a basis for that work.

One of the direct advantage of having PF_KEY support in Scapy is that you not only have the ability to monitor but also the ability to interact (i.e. inject PF_KEY traffic).

So, if you have a need, my old mercurial repo is here. If you find the extension useful and you think it would be interesting to have it available upstream in Scapy, then drop me an email and I'll see what I can do to update it for current version of Scapy. At the moment, it fits my needs.

Short example of how you can start playing with it:

arno@small:/tmp$ hg clone http://hg.natisbad.org/scapy6-pfkey
destination directory: scapy6-pfkey
requesting all changes
adding changesets
adding manifests
adding file changes
added 918 changesets with 947 changes to 24 files
updating to branch default
18 files updated, 0 files merged, 0 files removed, 0 files unresolved

arno@small:/tmp/scapy6-pfkey$ sudo ./scapy6-pfkey.py
Welcome to Scapy (1.2.0.2)
Scapy6 - PF_KEYv2 add-on (Using PID 3467)
>>> l=pfkey_sniff()                                            # Ctrl-C to stop the capture
>>> l.show()
...
0044 SADB_X_MIGRATE / SADB_EXT_X_KMADDRESS / SADB_EXT_ADDRESS_SRC / ... / SADB_EXT_X_POLICY
...
0054 SADB_ACQUIRE: ESP 2001:db8:1::1/128 0/any -> 2001:db8:2::2/128 0/any ...
...
>>> l[44].show()
###[ PF_KEY SADB_X_MIGRATE message ]###
  version= 2
  type= SADB_X_MIGRATE
  errno= 0
  satype= SADB_SATYPE_ESP
  len= 40
  res= 0
  seq= 0
  pid= 0
###[ PF_KEY Key Manager Addresses extension ]###
     len= 8
     type= SADB_EXT_X_KMADDRESS
     res= 0
     \local\
      |###[ sockaddr_in6 structure - Linux version ]###
      |  sin6_family= AF_INET6
      |  sin6_port= 0
      |  sin6_flowinfo= 0
      |  sin6_addr= 2001:db8:3::3
      |  sin6_scope_id= 0
     \remote\
      |###[ sockaddr_in6 structure - Linux version ]###
      |  sin6_family= AF_INET6
      |  sin6_port= 0
      |  sin6_flowinfo= 0
      |  sin6_addr= 2001:db8:2::1
      |  sin6_scope_id= 0
###[ PF_KEY IPv4/IPv6 source address extension ]###
        len= 5
        type= SADB_EXT_ADDRESS_SRC
        proto= Mobility Header
        plen= 128
        res= 0
        \addr\
         |###[ sockaddr_in6 structure - Linux version ]###
         |  sin6_family= AF_INET6
         |  sin6_port= 0
         |  sin6_flowinfo= 0
         |  sin6_addr= 2001:db8:2::2
         |  sin6_scope_id= 0
###[ PF_KEY IPv4/IPv6 destination address extension ]###
           len= 5
           type= SADB_EXT_ADDRESS_DST
           proto= Mobility Header
           plen= 128
           res= 0
           \addr\
            |###[ sockaddr_in6 structure - Linux version ]###
            |  sin6_family= AF_INET6
            |  sin6_port= 0
            |  sin6_flowinfo= 0
            |  sin6_addr= 2001:db8:2::1
            |  sin6_scope_id= 0
###[ PF_KEY POLICY extension ]###
              len= 20
              type= SADB_EXT_X_POLICY
              poltype= IPsec
              dir= OUT
              res= 0
              id= 0
              prio= 0
              \reqs\
               |###[ IPsec Request (sadb_x_ipsecrequest) ]###
               |  len= 72
               |  proto= ESP
               |  mode= Transport
               |  level= Require
               |  res1= 0
               |  id= 0
               |  res2= 0
               |  \sockaddr1\
               |   |###[ sockaddr_in6 structure - Linux version ]###
               |   |  sin6_family= AF_INET6
               |   |  sin6_port= 0
               |   |  sin6_flowinfo= 0
               |   |  sin6_addr= ::
               |   |  sin6_scope_id= 0
               |  \sockaddr2\
               |   |###[ sockaddr_in6 structure - Linux version ]###
               |   |  sin6_family= AF_INET6
               |   |  sin6_port= 0
               |   |  sin6_flowinfo= 0
               |   |  sin6_addr= ::
               |   |  sin6_scope_id= 0
               |###[ IPsec Request (sadb_x_ipsecrequest) ]###
               |  len= 72
               |  proto= ESP
               |  mode= Transport
               |  level= Require
               |  res1= 0
               |  id= 0
               |  res2= 0
               |  \sockaddr1\
               |   |###[ sockaddr_in6 structure - Linux version ]###
               |   |  sin6_family= AF_INET6
               |   |  sin6_port= 0
               |   |  sin6_flowinfo= 0
               |   |  sin6_addr= 2001:db8:3::3
               |   |  sin6_scope_id= 0
               |  \sockaddr2\
               |   |###[ sockaddr_in6 structure - Linux version ]###
               |   |  sin6_family= AF_INET6
               |   |  sin6_port= 0
               |   |  sin6_flowinfo= 0
               |   |  sin6_addr= 2001:db8:2::1
               |   |  sin6_scope_id= 0
>>>