diff --git a/.travis/install.sh b/.travis/install.sh index 20bc9b411aa73dfc9c37547d336d8d5865c37edb..d2936ab9aa997b4450b912a637397e4bb7796450 100644 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -10,9 +10,9 @@ fi if python --version 2>&1 | grep -q PyPy; then # cryptography requires PyPy >= 2.6, Travis CI uses 2.5.0 - $SCAPY_SUDO pip install $PIP_INSTALL_FLAGS ecdsa mock + $SCAPY_SUDO pip install $PIP_INSTALL_FLAGS mock else - $SCAPY_SUDO pip install $PIP_INSTALL_FLAGS cryptography ecdsa mock + $SCAPY_SUDO pip install $PIP_INSTALL_FLAGS cryptography mock fi # Install coverage @@ -26,7 +26,7 @@ if [ ! -z $SCAPY_USE_PCAPDNET ] then if [ "$TRAVIS_OS_NAME" = "linux" ] then - $SCAPY_SUDO apt-get install python-libpcap python-dumbnet + $SCAPY_SUDO apt-get install python-libpcap python-dumbnet openssl elif [ "$TRAVIS_OS_NAME" = "osx" ] then mkdir -p /Users/travis/Library/Python/2.7/lib/python/site-packages diff --git a/.travis/test.sh b/.travis/test.sh index 9edadf7e6b57be98be8e676949fc59052d431b5b..64ba5b1fad648301e00673297a4fc27b346afe06 100644 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -75,3 +75,10 @@ for f in ../scapy/contrib/*.uts do $SCAPY_SUDO ./run_tests -f text -t $f $UT_FLAGS -P "load_contrib('$(basename ${f/.uts})')" || exit $? done + +# Run unit tests with openssl if we have root privileges +if [ "$TRAVIS_OS_NAME" = "linux" ] && [ ! -z $SCAPY_USE_PCAPDNET ] && [ ! -z $SCAPY_SUDO ] +then + $SCAPY_SUDO tls/run_tests_tls_netaccess || exit $? +fi + diff --git a/scapy/all.py b/scapy/all.py index 9d6e5b5d3f80e7ab619869b98242d468bcc23b18..9ad308de32d09f13ca8f795e02ddbd9c6f82b19d 100644 --- a/scapy/all.py +++ b/scapy/all.py @@ -38,6 +38,11 @@ from scapy.autorun import * from scapy.main import * from scapy.layers.all import * +if "tls" in conf.load_layers: + try: + from scapy.layers.tls.all import * + except ImportError: + pass from scapy.asn1.asn1 import * from scapy.asn1.ber import * diff --git a/scapy/asn1/mib.py b/scapy/asn1/mib.py index 5506c0f99f479292a800ae67467ba4887ce5e184..59dc5b9617e728b881f39d9ddb25f00ad73c07ec 100644 --- a/scapy/asn1/mib.py +++ b/scapy/asn1/mib.py @@ -169,6 +169,12 @@ pkcs1_oids = { "sha224WithRSAEncryption" : "1.2.840.113549.1.1.14" } +####### secsig oiw ####### + +secsig_oids = { + "sha1" : "1.3.14.3.2.26" + } + ####### pkcs9 ####### pkcs9_oids = { @@ -428,7 +434,8 @@ certPkixAd_oids = { "id-ad-caRepository" : "1.3.6.1.5.5.7.48.5", "id-pkix-ocsp-archive-cutoff" : "1.3.6.1.5.5.7.48.6", "id-pkix-ocsp-service-locator" : "1.3.6.1.5.5.7.48.7", - "id-ad-cmc" : "1.3.6.1.5.5.7.48.12" + "id-ad-cmc" : "1.3.6.1.5.5.7.48.12", + "basic-response" : "1.3.6.1.5.5.7.48.1.1" } ####### ansi-x962 ####### @@ -556,6 +563,7 @@ evPolicy_oids = { x509_oids_sets = [ pkcs1_oids, + secsig_oids, pkcs9_oids, attributeType_oids, certificateExtension_oids, diff --git a/scapy/config.py b/scapy/config.py index 6f1f9787569350b1842f3d77f3d1f650d0d034da..e46ffeadc2f0dc97eaaf05426445cc471bae8839 100755 --- a/scapy/config.py +++ b/scapy/config.py @@ -310,7 +310,7 @@ def _prompt_changer(attr,val): class Conf(ConfClass): """This object contains the configuration of Scapy. session : filename where the session will be saved -interactive_shell : If set to "ipython", use IPython as shell. Default: Python +interactive_shell : If set to "ipython", use IPython as shell. Default: Python stealth : if 1, prevents any unwanted packet to go out (ARP, DNS, ...) checkIPID: if 0, doesn't check that IPID matches between IP sent and ICMP IP citation received if 1, checks that they either are equal or byte swapped equals (bug in some IP stacks) @@ -332,11 +332,12 @@ route : holds the Scapy routing table and provides methods to manipulate it warning_threshold : how much time between warnings from the same place ASN1_default_codec: Codec used by default for ASN1 objects mib : holds MIB direct access dictionary -resolve : holds list of fields for which resolution should be done -noenum : holds list of enum fields for which conversion to string should NOT be done +resolve : holds list of fields for which resolution should be done +noenum : holds list of enum fields for which conversion to string should NOT be done AS_resolver: choose the AS resolver class to use extensions_paths: path or list of paths where extensions are to be looked for -contribs: a dict which can be used by contrib layers to store local configuration +contribs : a dict which can be used by contrib layers to store local configuration +debug_tls:When 1, print some TLS session secrets when they are computed. """ version = VERSION session = "" @@ -373,6 +374,7 @@ contribs: a dict which can be used by contrib layers to store local configuratio padding = 1 except_filter = "" debug_match = 0 + debug_tls = 0 wepkey = "" route = None # Filed by route.py route6 = None # Filed by route6.py diff --git a/scapy/fields.py b/scapy/fields.py index 53f84727ca737ee33fc52d11438303d2aefc7554..8ec15ef9148e91e096fc39143f26aca00fb905cc 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -622,7 +622,11 @@ class FieldListField(Field): else: return map(lambda e, pkt=pkt: self.field.any2i(pkt, e), x) def i2repr(self, pkt, x): - return map(lambda e, pkt=pkt: self.field.i2repr(pkt,e), x) + res = [] + for v in x: + r = self.field.i2repr(pkt, v) + res.append(r) + return "[%s]" % ", ".join(res) def addfield(self, pkt, s, val): val = self.i2m(pkt, val) for v in val: diff --git a/scapy/layers/all.py b/scapy/layers/all.py index 1774049421bfa2731d5a261a06dda00a54e15f9b..f23fae1c3d0a0bcc286b438bf076ba8647e9d4c8 100644 --- a/scapy/layers/all.py +++ b/scapy/layers/all.py @@ -31,6 +31,8 @@ def _import_star(m): for _l in conf.load_layers: log_loading.debug("Loading layer %s" % _l) try: - _import_star(_l) + if _l != "tls": + _import_star(_l) except Exception,e: log.warning("can't import layer %s: %s" % (_l,e)) + diff --git a/scapy/layers/tls/__init__.py b/scapy/layers/tls/__init__.py index 2f20630887846cc99eb5259ebc8ed45c5d589353..2c6159194a86c9b68690c2a697c9eed1fd214fef 100644 --- a/scapy/layers/tls/__init__.py +++ b/scapy/layers/tls/__init__.py @@ -1,10 +1,114 @@ ## This file is part of Scapy -## See http://www.secdev.org/projects/scapy for more informations -## Copyright (C) Arnaud Ebalard, Maxence Tury +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard <arno@natisbad.com> +## 2015, 2016 Maxence Tury <maxence.tury@ssi.gouv.fr> ## This program is published under a GPLv2 license """ Tools for handling TLS sessions and digital certificates. + +Prerequisites: + + - You may need to 'pip install cryptography' for the module to be loaded. + + +Main features: + + - X.509 certificates parsing/building. + + - RSA & ECDSA keys sign/verify methods. + + - TLS records and sublayers (handshake...) parsing/building. Works with + versions SSLv3, TLS 1.0, 1.1 and 1.2. This may be enhanced by a TLS + context. For instance, if scapy reads a ServerHello with version TLS 1.2 + and a cipher suite using AES, it will assume the presence of IVs + prepending the data. See test/tls.uts for real examples. + + - TLS encryption/decryption capabilities with the usual ciphersuites. Once + again, the TLS context enables scapy to transparently send/receive + protected data if it learnt the session secrets. Note that if scapy acts + as one side of the handshake (e.g. reads all server-related packets and + builds all client-related packets), it will indeed compute the session + secrets. + + - TLS client & server basic automatons, provided for testing and tweaking + purposes. These make for a very primitive TLS stack. + + - Additionally, a basic test PKI (key + certificate for a CA, a client and + a server) is provided in tls/examples/pki_test. + + +Unit tests: + + - Various cryptography checks. + + - Reading a TLS handshake between a Firefox client and a GitHub server. + + - Test our TLS server against s_client with different cipher suites. + + - Test our TLS client against our TLS server (s_server is unscriptable). + + +TODO list (may it be carved away by good souls): + + - Features to add (or wait for) in the cryptography library: + + - no limitation on FFDH generator size; + (remove line 88 in cryptography/hazmat/primitives/asymmetric/dh.py) + + - CCM and CHACHA20-POLY1305 ciphers; + + - ECDH curves (x25519 and x448) from RFC 7748; + + - FFDH groups from RFC 7919; + + - the so-called 'tls' hash used with SSLv3 and TLS 1.0; + + - the simple DES algorithm; + + - the compressed EC point format. + + + - About the automatons: + + - Enrich the automatons. The client should be able to receive data at + any time, and to send as much data as wanted from stdin (for now, + only one predefined data message may be sent following the + handshake). The server should stay online even after the first client + leaves. Then we could look at more complicated behaviours like + renegotiation and resumption. We might get some help from + tintinweb/scapy-ssl_tls. + + - Add some examples which illustrate how the automatons could be used. + Typically, we could showcase this with Heartbleed. + + - Split up parts of the automaton, e.g. when our server builds the + ServerHello, Certificate, ServerKeyExchange and ServerHelloDone in + the same should_REPLY_TO_CH method. + + - Make the automatons tests more robust and less consuming. + + - Allow the server to store both one RSA key and one ECDSA key, and + select the right one to use according to the ClientHello suites. + + - Find a way to shutdown the automatons sockets properly without + simultaneously breaking the unit tests. + + + - Miscellaneous: + + - Implement TLS 1.3 structures and automatons. :D + + - Implement SSLv2 structures and automatons. xD + + - Mostly unused features : DSS, fixed DH, SRP, IDEA, char2 curves... + + - Check FFDH and ECDH parameters at SKE/CKE reception. + + - Go through the kx_algs and see what may be commented out. + + - Define several Certificate Transparency objects. + + - Enhance PSK and session ticket support. """ from scapy.config import conf @@ -12,13 +116,6 @@ from scapy.config import conf if not conf.crypto_valid: import logging log_loading = logging.getLogger("scapy.loading") - log_loading.info("Can't import python-cryptography v1.7+. Disabled PKCS #1 signing/verifying.") + log_loading.info("Can't import python-cryptography v1.7+. " + "Disabled PKI & TLS crypto-related features.") -try: - import ecdsa -except ImportError: - import logging - log_loading = logging.getLogger("scapy.loading") - log_loading.info("Can't import python ecdsa lib. Disabled certificate manipulation tools") -else: - from scapy.layers.tls.cert import * diff --git a/scapy/layers/tls/all.py b/scapy/layers/tls/all.py new file mode 100644 index 0000000000000000000000000000000000000000..3409b88c4f3af36de1798da23e2922c71cec3b74 --- /dev/null +++ b/scapy/layers/tls/all.py @@ -0,0 +1,19 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Aggregate top level objects from all TLS modules. +""" + +from scapy.layers.tls.cert import * + +from scapy.layers.tls.automaton import * +from scapy.layers.tls.handshake import * +from scapy.layers.tls.keyexchange import * +from scapy.layers.tls.record import * +from scapy.layers.tls.session import * + +from scapy.layers.tls.crypto.all import * + diff --git a/scapy/layers/tls/automaton.py b/scapy/layers/tls/automaton.py new file mode 100644 index 0000000000000000000000000000000000000000..f31c1f1b03c7358ba8671823a1e71d74acc21caf --- /dev/null +++ b/scapy/layers/tls/automaton.py @@ -0,0 +1,1078 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS automatons. This makes for a primitive TLS stack. +SSLv3 is not guaranteed, and SSLv2 is not supported. +Obviously you need rights for network access. + + +Launch a server on tcp/4433: + +from scapy.all import * +t = TLSServerAutomaton(mycert='<cert.pem>', mykey='<key.pem>') +t.run() + + +Launch a client to tcp/50000 with one cipher suite of your choice: + +from scapy.all import * +ch = TLSClientHello(ciphers=<int code of the cipher suite>) +t = TLSClientAutomaton(dport=50000, client_hello=ch) +t.run() +""" + +import socket +import struct + +from scapy.error import warning +from scapy.automaton import Automaton, ATMT +from scapy.layers.tls.cert import Cert, PrivKey, PrivKeyRSA, PrivKeyECDSA +from scapy.layers.tls.basefields import _tls_version +from scapy.layers.tls.session import tlsSession +from scapy.layers.tls.handshake import * +from scapy.layers.tls.record import (TLS, TLSAlert, TLSChangeCipherSpec, + TLSApplicationData) +from scapy.layers.tls.crypto.suites import (_tls_cipher_suites_cls, + _tls_cipher_suites, + get_usable_ciphersuites) + + +############################################################################### +### Client automaton ### +############################################################################### + +class TLSClientAutomaton(Automaton): + """ + The TLS client automaton. + + - server : default value is '127.0.0.1'; + - dport : default value is 4433; + - server_name : default value is None; + - mycert : optional when there is no client authentication; + - mykey : optional when there is no client authentication; + - client_hello : optional definition of the ClientHello to be sent to the + server, this is faster than automaton overloading and enables quick + cipher suite choice (make sure it is usable, though); + - data : optional application_data to be sent after the handshake, if this + is not defined we send a simple GET request. + """ + + def parse_args(self, server="127.0.0.1", dport=4433, + server_name=None, mycert=None, mykey=None, + client_hello=None, data=None, **kargs): + Automaton.parse_args(self, **kargs) + + tmp = socket.getaddrinfo(server, dport) + self.remote_name = None + try: + if ':' in server: + socket.inet_pton(socket.AF_INET6, server) + else: + socket.inet_pton(socket.AF_INET, server) + except: + self.remote_name = socket.getfqdn(server) + if self.remote_name != server: + tmp = socket.getaddrinfo(self.remote_name, dport) + + if server_name: + self.remote_name = server_name + self.remote_family = tmp[0][0] + self.remote_ip = tmp[0][4][0] + self.remote_port = dport + self.local_ip = None + self.local_port = None + + self.cur_pkt = None + self.cur_session = None + self.msg_list = [] + + self.remain = "" + + self.socket = None + + self.cert_req = None + + self.client_hello = client_hello + self.data = data + + if mycert and mykey: + self.mycert = Cert(mycert) + self.mykey = PrivKey(mykey) + else: + self.mycert = None + self.mykey = None + + + def get_next_msg(self, socket_timeout=5, retry=5): + """ + The purpose of the function is to make next message(s) available in + self.msg_list. If the list is not empty, nothing is done. If not, in + order to fill it, the function uses the data already available in + self.remain from a previous call and waits till there are enough to + dissect a TLS packet (expected length is in the 5 first bytes of the + packet). Once dissected, the content of the TLS packet (carried + messages) is appended to self.msg_list. + + We have to grab enough data to dissect a TLS packet, i.e. at least + 5 bytes in order to access the expected length of the TLS packet. + """ + + if self.msg_list: # a message is already available + return + + self.socket.settimeout(socket_timeout) + grablen = 5 + while retry and (grablen == 5 or len(self.remain) < grablen): + if grablen == 5 and len(self.remain) >= 5: + grablen = struct.unpack('!H', self.remain[3:5])[0] + 5 + + if grablen == len(self.remain): + break + + try: + tmp = self.socket.recv(grablen - len(self.remain)) + if not tmp: + retry -= 1 + else: + self.remain += tmp + except: + retry -= 1 + + if self.remain < 5 or len(self.remain) != grablen: + # Remote peer is not willing to respond + return + + # Instantiate the TLS packet (record header only, at this point) + p = TLS(self.remain, tls_session=self.cur_session) + self.cur_session = p.tls_session + self.remain = "" + self.msg_list += p.msg + + while p.payload: + if isinstance(p.payload, Raw): + self.remain += p.payload.load + p = p.payload + elif isinstance(p.payload, TLS): + p = p.payload + self.msg_list += p.msg + + + @ATMT.state(initial=True) + def INITIAL(self): + raise self.INIT_TLS_SESSION() + + @ATMT.state() + def INIT_TLS_SESSION(self): + self.cur_session = tlsSession() + self.cur_session.client_certs = self.mycert + self.cur_session.client_key = self.mykey + raise self.CONNECT() + + @ATMT.state() + def CONNECT(self): + s = socket.socket(self.remote_family, socket.SOCK_STREAM) + s.connect((self.remote_ip, self.remote_port)) + self.socket = s + self.local_ip, self.local_port = self.socket.getsockname()[:2] + raise self.PREPARE_FIRST_PKT() + + @ATMT.state() + def PREPARE_FIRST_PKT(self): + self.cur_pkt = TLS(tls_session=self.cur_session) + + @ATMT.condition(PREPARE_FIRST_PKT) + def should_add_ClientHello(self): + raise self.ADDED_ClientHello() + + @ATMT.action(should_add_ClientHello, prio=1) + def add_ClientHello(self): + """ + Default TLSClientHello() offers only TLS_DHE_RSA_WITH_AES_128_CBC_SHA. + + For fast server testing, typical alternatives (DHE only, RSAkx with CBC + only, ECDHE with appropriate extensions) may be found in tls.uts, + and then brought here through the client_hello argument. + """ + p = self.client_hello or TLSClientHello() + self.cur_pkt.msg.append(p) + + @ATMT.state() + def ADDED_ClientHello(self): + pass + + @ATMT.condition(ADDED_ClientHello) + def should_send_ClientHello(self): + raise self.SENT_ClientHello() + + @ATMT.action(should_send_ClientHello, prio=1) + def send_ClientHello(self): + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + + @ATMT.state() + def SENT_ClientHello(self): + raise self.WAITING_FOR_ServerHello() + + @ATMT.state() + def WAITING_FOR_ServerHello(self): + self.get_next_msg() + raise self.PREPROCESS_ServerHello() + + @ATMT.state() + def PREPROCESS_ServerHello(self): + pass + + @ATMT.condition(PREPROCESS_ServerHello, prio=1) + def should_HANDLE_ServerHello(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSServerHello)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_SH() + + @ATMT.state() + def HANDLE_SH(self): + pass + + @ATMT.condition(PREPROCESS_ServerHello, prio=2) + def missing_server_hello(self): + raise self.MISSING_SH() + + @ATMT.state(final=True) + def MISSING_SH(self): + print "Missing TLS Server Hello message" + + @ATMT.condition(HANDLE_SH, prio=1) + def should_HANDLE_CERT(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSCertificate)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_CERT() + + @ATMT.state() + def HANDLE_CERT(self): + pass + + @ATMT.condition(HANDLE_SH, prio=2) + def missing_certificate(self): + raise self.MISSING_CERT() + + @ATMT.state(final=True) + def MISSING_CERT(self): + print "Missing TLS Certificate message" + + @ATMT.state() + def HANDLE_CERT_REQ(self): + pass + + @ATMT.condition(HANDLE_CERT, prio=1) + def should_HANDLE_SKE_from_CERT(self): + """ + XXX We should check the ServerKeyExchange attributes for discrepancies + with our own ClientHello, along with the ServerHello and Certificate. + """ + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSServerKeyExchange)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_SKE() + + @ATMT.state(final=True) + def MISSING_SKE(self): + pass + + @ATMT.condition(HANDLE_CERT, prio=2) + def expected_server_key_exchange(self): + if self.cur_session.prcs.key_exchange.server_kx_msg_cls: + # Should have received a SKE + raise self.MISSING_SKE() + + @ATMT.state() + def HANDLE_SKE(self): + # XXX Move that refill code somewhere else + self.get_next_msg() + + @ATMT.condition(HANDLE_SKE, prio=2) + def should_HANDLE_CERT_REQ_from_SKE(self): + self.get_next_msg() + """ + XXX We should check the CertificateRequest attributes for discrepancies + with the cipher suite, etc. + """ + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSCertificateRequest)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + self.cert_req = p + raise self.HANDLE_CERT_REQ() + + @ATMT.condition(HANDLE_CERT, prio=3) + def should_HANDLE_CERT_REQ(self): + """ + XXX We should check the CertificateRequest attributes for discrepancies + with the cipher suite, etc. + """ + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSCertificateRequest)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + self.cert_req = p + raise self.HANDLE_CERT_REQ() + + @ATMT.condition(HANDLE_SKE, prio=1) + def should_HANDLE_SHD(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSServerHelloDone)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_SHD() + + @ATMT.condition(HANDLE_CERT_REQ, prio=4) + def should_HANDLE_SHD_from_CERT_REQ(self): + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSServerHelloDone)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_SHD() + + @ATMT.condition(HANDLE_CERT) + def should_HANDLE_SHD_from_CERT(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSServerHelloDone)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_SHD() + + @ATMT.state() + def HANDLE_SHD(self): + raise self.PREPARE_PKT2() + + # Second packet sent by us + @ATMT.state() + def PREPARE_PKT2(self): + pass + + @ATMT.condition(PREPARE_PKT2, prio=1) + def should_ADD_CLIENT_CERT(self): + """ + If the server sent a CertificateRequest, we send a Certificate message. + If no certificate is available, an empty Certificate message is sent: + - this is a SHOULD in RFC 4346 (Section 7.4.6) + - this is a MUST in RFC 5246 (Section 7.4.6) + + XXX We may want to add a complete chain. + """ + if not self.cert_req: + return + certs = [] + if self.mycert: + certs = [self.mycert] + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + p = TLSCertificate(certs=certs) + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CLIENT_CERT() + + @ATMT.state() + def ADD_CLIENT_CERT(self): + pass + + @ATMT.condition(PREPARE_PKT2, prio=2) + def should_ADD_CKE_from_PREPARE_PKT2(self): + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + p = TLSClientKeyExchange() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CKE() + + @ATMT.condition(ADD_CLIENT_CERT, prio=2) + def should_ADD_CKE_from_ADD_CLIENT_CERT(self): + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + p = TLSClientKeyExchange() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CKE() + + @ATMT.state() + def ADD_CKE(self): + pass + + @ATMT.condition(ADD_CKE, prio=1) + def should_ADD_CV_from_ADD_CKE(self): + """ + XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify + message is only sent following a client certificate that has signing + capability (i.e. not those containing fixed DH params). + We should verify that before adding the message. We should also handle + the case when the Certificate message was empty. + """ + if (not self.cert_req or + self.mycert is None or + self.mykey is None): + return + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + p = TLSCertificateVerify() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CV() + + @ATMT.state() + def ADD_CV(self): + pass + + @ATMT.condition(ADD_CV) + def should_ADD_CCS_from_ADD_CV(self): + self.cur_pkt = TLS(type=20, tls_session=self.cur_session, msg=[]) + p = TLSChangeCipherSpec() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CCS() + + @ATMT.condition(ADD_CKE, prio=2) + def should_ADD_CCS_from_ADD_CKE(self): + self.cur_pkt = TLS(type=20, tls_session=self.cur_session, msg=[]) + p = TLSChangeCipherSpec() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_CCS() + + @ATMT.state() + def ADD_CCS(self): + pass + + @ATMT.condition(ADD_CCS) + def should_ADD_FINISHED(self): + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + p = TLSFinished() + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.ADD_FINISHED() + + @ATMT.state() + def ADD_FINISHED(self): + pass + + @ATMT.condition(ADD_FINISHED) + def should_SEND_SECOND_PKT(self): + raise self.SEND_SECOND_PKT() + + @ATMT.state() + def SEND_SECOND_PKT(self): + raise self.WAIT_FOR_RESP2() + + @ATMT.state() + def WAIT_FOR_RESP2(self): + self.socket.settimeout(10) + s = self.socket.recv(100000) + p = TLS(s, tls_session=self.cur_session) + self.msg_list = p.msg + while p.payload: + if isinstance(p.payload, Raw): + self.remain += p.payload.load + p = p.payload + elif isinstance(p.payload, TLS): + p = p.payload + self.msg_list += p.msg + raise self.PREPROCESS_RESP2() + + # Second response from the server + @ATMT.state() + def PREPROCESS_RESP2(self): + pass + + @ATMT.condition(PREPROCESS_RESP2) + def should_HANDLE_CCS(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSChangeCipherSpec)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_CCS() + + @ATMT.state() + def HANDLE_CCS(self): + pass + + @ATMT.condition(HANDLE_CCS) + def should_HANDLE_FINISHED(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSFinished)): + return + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_FINISHED() + + @ATMT.state() + def HANDLE_FINISHED(self): + pass + + @ATMT.condition(HANDLE_FINISHED) + def should_test_connection(self): + raise self.TESTED_CONNECTION() + + @ATMT.action(should_test_connection, prio=1) + def send_recv_data(self): + """ + XXX No live input from the user ; one unique send for now. + XXX We might want not to send any ApplicationData message. + XXX We do not wait very long for server answer. + """ + txt = self.data or "GET /\r\n\r\n" # GET HTTP/1.1\r\n\r\n" + p = TLS(type=23, tls_session=self.cur_session, msg=[Raw(load=txt)]) + self.socket.send(str(p)) + print "Sent to server: \n%r" % txt + + self.get_next_msg(1, 0) + if self.msg_list: + p = self.msg_list[0] + self.msg_list = self.msg_list[1:] + if isinstance(p, Raw): + print "Received from server: \n%s" % p.load + else: + print "Received from server: \n%s" % p + + @ATMT.state() + def TESTED_CONNECTION(self): + pass + + @ATMT.condition(TESTED_CONNECTION) + def should_close_session(self): + raise self.CLOSED_TLS_SESSION() + + @ATMT.action(should_close_session, prio=1) + def close_session(self): + """ + We end the session properly after 2 seconds, + with a TLS Alert (warning, close_notify). + """ + time.sleep(2) + self.cur_pkt = TLS(type=21, msg=[], tls_session=self.cur_session) + p = TLSAlert(level=1, descr=0) + self.cur_pkt.msg.append(p) + try: + self.socket.send(str(self.cur_pkt)) + except: + print "Could not send termination Alert (maybe the server stopped)" + self.cur_pkt = None + + @ATMT.state() + def CLOSED_TLS_SESSION(self): + raise self.FINAL() + + @ATMT.state(final=True) + def FINAL(self): + """ + We might call shutdown, but it may happen that the server + did not wait for us to shutdown after answering our data query. + #self.socket.shutdown(1) + """ + self.socket.close() + + +############################################################################### +### Server automaton ### +############################################################################### + +class TLSServerAutomaton(Automaton): + """ + The TLS client automaton. + + - server : default value is '127.0.0.1'; + - sport : default value is 4433; + - mycert : optional when there is no client authentication; + - mykey : optional when there is no client authentication; + - preferred_ciphersuite : optional cipher suite to be selected should the + client offer it through its ClientHello. + """ + + def parse_args(self, server="127.0.0.1", sport=4433, + mycert=None, mykey=None, + preferred_ciphersuite=None, **kargs): + Automaton.parse_args(self, **kargs) + + self.mycert = Cert(mycert) + self.mykey = PrivKey(mykey) + + try: + if ':' in server: + socket.inet_pton(socket.AF_INET6, server) + else: + socket.inet_pton(socket.AF_INET, server) + tmp = socket.getaddrinfo(server, sport) + except: + tmp = socket.getaddrinfo(socket.getfqdn(server), sport) + + self.ip_family = tmp[0][0] + self.local_ip = tmp[0][4][0] + self.local_port = sport + self.remote_ip = None + self.remote_port = None + + self.cur_pkt = None + self.cur_session = None + self.msg_list = [] + + self.remain = "" + + self.socket = None + + self.cert_req = None + + self.preferred_ciphersuite = preferred_ciphersuite + + + def get_next_msg(self): + """ + The purpose of the function is to make next message(s) available + in self.msg_list. If the list is not empty, nothing is done. If + not, in order to fill it, the function uses the data already + available in self.remain from a previous call and waits till there + are enough to dissect a TLS packet (expected length is in the 5 + first bytes of the packet). Once dissected, the content of the + TLS packet (carried messages) is appended to self.msg_list. + + We have to grab enough data to dissect a TLS packet, i.e. at least + 5 bytes in order to access the expected length of the TLS packet. + """ + + if self.msg_list: # a message is already available + return + + self.socket.settimeout(5) + retry = 5 + grablen = 5 + while retry and (grablen == 5 or len(self.remain) < grablen): + if grablen == 5 and len(self.remain) >= 5: + grablen = struct.unpack('!H', self.remain[3:5])[0] + 5 + if grablen == len(self.remain): + break + + try: + tmp = self.socket.recv(grablen - len(self.remain)) + if not tmp: + retry -= 1 + else: + self.remain += tmp + except: + retry -= 1 + + if self.remain < 5 or len(self.remain) != grablen: + # Remote peer is not willing to respond + return + + # Instantiate TLS packet (record header only, at this point) + p = TLS(self.remain, tls_session=self.cur_session) + self.cur_session = p.tls_session + self.remain = "" + self.msg_list += p.msg + + while p.payload: + if isinstance(p.payload, Raw): + self.remain += p.payload.load + p = p.payload + elif isinstance(p.payload, TLS): + p = p.payload + self.msg_list += p.msg + + @ATMT.state(initial=True) + def INITIAL(self): + raise self.INIT_TLS_SESSION() + + @ATMT.state() + def INIT_TLS_SESSION(self): + """ + XXX We should offer the right key according to the client's suites. For + now server_rsa_key is only used for RSAkx, but we should try to replace + every server_key with both server_rsa_key and server_ecdsa_key. + """ + self.cur_session = tlsSession(connection_end="server") + self.cur_session.server_certs = [self.mycert] + self.cur_session.server_key = self.mykey + if isinstance(self.mykey, PrivKeyRSA): + self.cur_session.server_rsa_key = self.mykey + #elif isinstance(self.mykey, PrivKeyECDSA): + # self.cur_session.server_ecdsa_key = self.mykey + raise self.BIND_AND_WAIT() + + @ATMT.state() + def BIND_AND_WAIT(self): + s = socket.socket(self.ip_family, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind((self.local_ip, self.local_port)) + s.listen(1) + except: + print "Unable to bind on address %s and port %d" % (self.local_ip, + self.local_port) + return + self.socket, addr = s.accept() + self.remote_ip, self.remote_port = addr + + raise self.WAITING_FOR_ClientHello() + + @ATMT.state() + def WAITING_FOR_ClientHello(self): + self.get_next_msg() + + raise self.PREPROCESS_ClientHello() + + @ATMT.state() + def PREPROCESS_ClientHello(self): + pass + + @ATMT.condition(PREPROCESS_ClientHello, prio=1) + def should_HANDLE_ClientHello(self): + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSClientHello)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_CH() + + @ATMT.state() + def HANDLE_CH(self): + pass + + @ATMT.condition(HANDLE_CH, prio=1) + def should_NO_USABLE_CIPHERSUITE(self): + """ + We extract cipher suites candidates from the client's proposition. + """ + l = self.cur_pkt.ciphers + + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + l = get_usable_ciphersuites(l, kx) + + if l: + return + + raise self.NO_USABLE_CIPHERSUITE() + + @ATMT.state(final=True) + def NO_USABLE_CIPHERSUITE(self): + """ + If there is no available cipher suite, close the session with an Alert. + """ + print "No usable cipher suite, closing connection" + self.cur_pkt = TLS(type=21, msg=[], tls_session=self.cur_session) + p = TLSAlert(level=1, descr=0) + self.cur_pkt.msg.append(p) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + + @ATMT.condition(PREPROCESS_ClientHello, prio=2) + def missing_client_hello(self): + raise self.MISSING_CH() + + @ATMT.state(final=True) + def MISSING_CH(self): + print "Missing TLS Client Hello message" + + @ATMT.condition(HANDLE_CH, prio=2) + def should_REPLY_TO_CH(self): + """ + XXX Several enhancements needed here. + + Selecting a cipher suite should be no trouble as we already caught the + None case previously. However, regarding the protocol version, we + might want to try resending a ClientHello when the advertised + version is not deemed satisfying. + + Then, the sending of ServerHello, Certificate, ServerKeyExchange and + ServerHelloDone should be split into multiple states, in order for the + user to overload only the ones he's interested in. + + Also, we do not manage extensions at all. + """ + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + if self.preferred_ciphersuite in usable_suites: + c = self.preferred_ciphersuite + + comp = 0 + if self.cur_pkt.comp and 1 in self.cur_pkt.comp: + comp = 1 + + self.cur_session.advertised_tls_version = self.cur_pkt.version + self.cur_session.tls_version = self.cur_pkt.version + #XXX there should be some checks on this version from the ClientHello + v = self.cur_session.tls_version + print "\nVersion: " + _tls_version[v] + print "Cipher suite: " + _tls_cipher_suites[c] + + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[]) + + p = TLSServerHello(cipher=c, comp=[comp]) + self.cur_pkt.msg.append(p) + + p = TLSCertificate(certs=self.cur_session.server_certs) + self.cur_pkt.msg.append(p) + + if not _tls_cipher_suites_cls[c].kx_alg.no_ske: + p = TLSServerKeyExchange() + self.cur_pkt.msg.append(p) + + p = TLSServerHelloDone() + self.cur_pkt.msg.append(p) + + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.SENT_SH() + + @ATMT.state() + def SENT_SH(self): + pass + + @ATMT.condition(SENT_SH, prio=1) + def should_HANDLE_CKE(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSClientKeyExchange)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_CKE() + + @ATMT.state() + def HANDLE_CKE(self): + pass + + @ATMT.condition(SENT_SH, prio=2) + def should_HANDLE_ALERT_INSTEAD_OF_CKE(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSAlert)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_ALERT_INSTEAD_OF_CKE() + + @ATMT.state() + def HANDLE_ALERT_INSTEAD_OF_CKE(self): + print "Received Alert message instead of CKE" + + @ATMT.condition(SENT_SH, prio=3) + def should_HANDLE_MISSING_CKE(self): + raise self.HANDLE_MISSING_CKE() + + @ATMT.state() + def HANDLE_MISSING_CKE(self): + print "Missing CKE in client's reply" + + + @ATMT.condition(HANDLE_CKE, prio=1) + def should_HANDLE_CCS(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSChangeCipherSpec)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_CCS() + + @ATMT.state() + def HANDLE_CCS(self): + pass + + @ATMT.condition(HANDLE_CKE, prio=2) + def should_HANDLE_ALERT_INSTEAD_OF_CCS(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSAlert)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + + raise self.HANDLE_ALERT_INSTEAD_OF_CCS() + + @ATMT.state() + def HANDLE_ALERT_INSTEAD_OF_CCS(self): + print "Received Alert message instead of CCS" + + @ATMT.condition(HANDLE_CKE, prio=3) + def should_HANDLE_MISSING_CCS(self): + raise self.HANDLE_MISSING_CCS() + + @ATMT.state() + def HANDLE_MISSING_CCS(self): + print "Missing CCS in client's reply" + + @ATMT.condition(HANDLE_CCS, prio=1) + def should_HANDLE_Finished(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSFinished)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_FINISHED() + + @ATMT.state() + def HANDLE_FINISHED(self): + pass + + @ATMT.condition(HANDLE_CCS, prio=2) + def should_HANDLE_ALERT_INSTEAD_OF_Finished(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSAlert)): + return + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + raise self.HANDLE_ALERT_INSTEAD_OF_FINISHED() + + @ATMT.state() + def HANDLE_ALERT_INSTEAD_OF_FINISHED(self): + print "Received Alert message instead of Finished" + + @ATMT.condition(HANDLE_CCS, prio=3) + def should_HANDLE_MISSING_FINISHED(self): + raise self.HANDLE_MISSING_FINISHED() + + @ATMT.state() + def HANDLE_MISSING_FINISHED(self): + print "Missing Finished in client's reply" + + @ATMT.condition(HANDLE_FINISHED, prio=1) + def should_SEND_CCS(self): + ccs = TLSChangeCipherSpec() + self.cur_pkt = TLS(type=20, msg=[ccs], tls_session=self.cur_session) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.SEND_CCS() + + @ATMT.state() + def SEND_CCS(self): + pass + + @ATMT.condition(SEND_CCS, prio=2) + def should_SEND_FINISHED(self): + p = TLSFinished() + self.cur_pkt = TLS(tls_session=self.cur_session, msg=[p]) + self.socket.send(str(self.cur_pkt)) + self.cur_pkt = None + raise self.FINISHED_SENT() + + @ATMT.state() + def FINISHED_SENT(self): + pass + + @ATMT.condition(FINISHED_SENT, prio=0) + def should_HANDLE_NO_CLIENT(self): + self.get_next_msg() + if self.msg_list: + return + print "Client left. Closing connection..." + raise self.FINAL() + + @ATMT.condition(FINISHED_SENT, prio=1) + def should_HANDLE_ALERT_FROM_FINISHED(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSAlert)): + return + raise self.HANDLE_ALERT_FROM_FINISHED_SENT() + + @ATMT.state() + def HANDLE_ALERT_FROM_FINISHED_SENT(self): + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + print "Received Alert Message after sending Finished" + print "Closing connection" + #XXX no support for new connections, for now + raise self.FINAL() + + @ATMT.condition(FINISHED_SENT, prio=2) + def should_WAIT_DATA(self): + self.get_next_msg() + if self.msg_list: + return + # Client did not send anything, let's wait + raise self.FINISHED_SENT() + + @ATMT.condition(FINISHED_SENT, prio=3) + def should_PROCESS_DATA(self): + self.get_next_msg() + if (not self.msg_list or + not isinstance(self.msg_list[0], TLSApplicationData)): + return + raise self.PROCESS_DATA() + + @ATMT.state() + def PROCESS_DATA(self): + """ + In the beginning, we return a small page with useful information. + Then, we act as an echo server. + """ + self.cur_pkt = self.msg_list[0] + self.msg_list = self.msg_list[1:] + + recv_data = self.cur_pkt.data + print "Received %s" % repr(recv_data) + + if recv_data.startswith("GET / HTTP/1."): + header = "HTTP/1.1 200 OK\r\n" + header += "Server: Scapy TLS Extension\r\n" + header += "Content-type: text/html\r\n" + header += "Content-length: %d\r\n\r\n" + s = "Information on current TLS session:\n\n" + s += "Local end : %s:%d\n" % (self.local_ip, self.local_port) + s += "Remote end : %s:%d\n" % (self.remote_ip, self.remote_port) + v = self.cur_session.advertised_tls_version + v = "%s (0x%04x)" % (_tls_version[v], v) + s += "TLS version : %s\n" % v + s += repr(self.cur_session.wcs) + body = "<html><body><pre>%s</pre></body></html>\r\n\r\n" % s + page = (header+body) % len(body) + else: + page = recv_data + + p = Raw(load=page) + self.cur_pkt = TLS(type=23, msg=[p], tls_session=self.cur_session) + self.socket.send(str(self.cur_pkt)) + raise self.FINISHED_SENT() + + @ATMT.state(final=True) + def FINAL(self): + """ + We might call shutdown, but unit tests with s_client fail with this. + #self.socket.shutdown(1) + """ + self.socket.close() + diff --git a/scapy/layers/tls/basefields.py b/scapy/layers/tls/basefields.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa98d6e832cccfaf93e7197898fca4d0bc5b1fa --- /dev/null +++ b/scapy/layers/tls/basefields.py @@ -0,0 +1,180 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS base fields, used for record parsing/building. As several operations depend +upon the TLS version or ciphersuite, the packet has to provide a TLS context. +""" + +from scapy.fields import * + + +_tls_type = { 20: "change_cipher_spec", + 21: "alert", + 22: "handshake", + 23: "application_data" } + +_tls_version = { 0x0200: "SSLv2", + 0x0300: "SSLv3", + 0x0301: "TLS 1.0", + 0x0302: "TLS 1.1", + 0x0303: "TLS 1.2" } + + +class _TLSVersionField(ShortEnumField): + """ + Behavior: if the user does not provide a value, we use the version provided + by tls_version parameter in packet's session, only if it is defined. In + that case, this is the version selected by the server. Otherwise, we use + the value provided by advertised_tls_version parameter in packet's session. + In that latter case, this is simply the version provided by the client. + """ + def i2h(self, pkt, x): + if x is None: + if pkt.tls_session.tls_version: + return pkt.tls_session.tls_version + return pkt.tls_session.advertised_tls_version + return x + + def i2m(self, pkt, x): + if x is None: + if pkt.tls_session.tls_version: + return pkt.tls_session.tls_version + return pkt.tls_session.advertised_tls_version + return x + + +class _TLSClientVersionField(ShortEnumField): + """ + Unlike _TLSVersionField, we use advertised_tls_version preferentially, + and then tls_version if there was none advertised. + """ + def i2h(self, pkt, x): + if x is None: + if pkt.tls_session.advertised_tls_version: + return pkt.tls_session.advertised_tls_version + return pkt.tls_session.tls_version + return x + + def i2m(self, pkt, x): + if x is None: + if pkt.tls_session.advertised_tls_version: + return pkt.tls_session.advertised_tls_version + return pkt.tls_session.tls_version + return x + + +class _TLSLengthField(ShortField): + pass + + +class _TLSIVField(StrField): + """ + As stated in Section 6.2.3.2. RFC 4346, TLS 1.1 implements an explicit IV + mechanism. For that reason, the behavior of the field is dependent on the + TLS version found in the packet if available or otherwise (on build, if + not overloaded, it is provided by the session). The size of the IV and + its value are obviously provided by the session. As a side note, for the + first packets exchanged by peers, NULL being the default enc alg, it is + empty (except if forced to a specific value). Also note that the field is + kept empty (unless forced to a specific value) when the cipher is a stream + cipher (and NULL is considered a stream cipher). + """ + def i2len(self, pkt, i): + if i is not None: + return len(i) + l = 0 + cipher_type = pkt.tls_session.rcs.cipher.type + if cipher_type == "block": + if pkt.tls_session.tls_version >= 0x0302: + l = pkt.tls_session.rcs.cipher.block_size + elif cipher_type == "aead": + l = pkt.tls_session.rcs.cipher.nonce_explicit_len + return l + + def i2m(self, pkt, x): + return x or "" + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + l = 0 + cipher_type = pkt.tls_session.rcs.cipher.type + if cipher_type == "block": + if pkt.tls_session.tls_version >= 0x0302: + l = pkt.tls_session.rcs.cipher.block_size + elif cipher_type == "aead": + l = pkt.tls_session.rcs.cipher.nonce_explicit_len + return s[l:], self.m2i(pkt, s[:l]) + + def i2repr(self, pkt, x): + return repr(self.i2m(pkt, x)) + + +class _TLSMACField(StrField): + def i2len(self, pkt, i): + if i is not None: + return len(i) + return pkt.tls_session.wcs.mac_len + + def i2m(self, pkt, x): + if x is None: + return "" + return x + + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + l = pkt.tls_session.rcs.mac_len + return s[l:], self.m2i(pkt, s[:l]) + + def i2repr(self, pkt, x): + #XXX Provide status when dissection has been performed successfully? + return repr(self.i2m(pkt, x)) + + +class _TLSPadField(StrField): + def i2len(self, pkt, i): + if i is not None: + return len(i) + return 0 + + def i2m(self, pkt, x): + if x is None: + return "" + return x + + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + if pkt.tls_session.consider_read_padding(): + # We get the length from the last byte of s which + # is either the first byte of padding or the padding + # length field itself is padding length is 0. + # This should work with SSLv3 and also TLS versions. + l = ord(s[-1]) + return s[l:], self.m2i(pkt, s[:l]) + return s, None + + def i2repr(self, pkt, x): + #XXX Provide status when dissection has been performed successfully? + return repr(self.i2m(pkt, x)) + + +class _TLSPadLenField(ByteField): + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + if pkt.tls_session.consider_read_padding(): + return ByteField.getfield(self, pkt, s) + return s, None + diff --git a/scapy/layers/tls/cert.py b/scapy/layers/tls/cert.py index 8d81097e0668b2d7938dfb7e5816061966d65b41..8f816267a8d938e4612f3cafdde842d600f4d2e5 100644 --- a/scapy/layers/tls/cert.py +++ b/scapy/layers/tls/cert.py @@ -7,49 +7,47 @@ """ High-level methods for PKI objects (X.509 certificates, CRLs, asymmetric keys). Supports both RSA and ECDSA objects. + +The classes below are wrappers for the ASN.1 objects defined in x509.py. +By collecting their attributes, we bypass the ASN.1 structure, hence +there is no direct method for exporting a new full DER-encoded version +of a Cert instance after its serial has been modified (for example). +If you need to modify an import, just use the corresponding ASN1_Packet. + +For instance, here is what you could do in order to modify the serial of +'cert' and then resign it with whatever 'key': + f = open('cert.der') + c = X509_Cert(f.read()) + c.tbsCertificate.serialNumber = 0x4B1D + k = PrivKey('key.pem') + new_x509_cert = k.resignCert(c) +No need for obnoxious openssl tweaking anymore. :) """ -## This module relies on python-crypto and python-ecdsa. -## -## The classes below are wrappers for the ASN.1 objects defined in x509.py. -## By collecting their attributes, we bypass the ASN.1 structure, hence -## there is no direct method for exporting a new full DER-encoded version -## of a Cert instance after its serial has been modified (for example). -## If you need to modify an import, just use the corresponding ASN1_Packet. -## -## For instance, here is what you could do in order to modify the serial of -## 'cert' and then resign it with whatever 'key': -## f = open('cert.der') -## c = X509_Cert(f.read()) -## c.tbsCertificate.serialNumber = 0x4B1D -## k = PrivKey('key.pem') -## new_x509_cert = k.resignCert(c) -## No need for obnoxious openssl tweaking anymore. :) - -import base64, os, time - -import ecdsa +import base64 +import os +import time from scapy.config import conf, crypto_validator if conf.crypto_valid: from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa else: - default_backend = rsa = None - -from scapy.layers.tls.crypto.curves import import_curve -from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip, pkcs_i2osp, mapHashFunc -from scapy.layers.tls.crypto.pkcs1 import _EncryptAndVerifyRSA -from scapy.layers.tls.crypto.pkcs1 import _DecryptAndSignRSA + default_backend = rsa = serialization = None +from scapy.error import warning +from scapy.utils import binrepr from scapy.asn1.asn1 import ASN1_BIT_STRING from scapy.asn1.mib import hash_by_oid -from scapy.layers.x509 import X509_SubjectPublicKeyInfo -from scapy.layers.x509 import RSAPublicKey, RSAPrivateKey -from scapy.layers.x509 import ECDSAPublicKey, ECDSAPrivateKey -from scapy.layers.x509 import RSAPrivateKey_OpenSSL, ECDSAPrivateKey_OpenSSL -from scapy.layers.x509 import X509_Cert, X509_CRL -from scapy.utils import binrepr +from scapy.layers.x509 import (X509_SubjectPublicKeyInfo, + RSAPublicKey, RSAPrivateKey, + ECDSAPublicKey, ECDSAPrivateKey, + RSAPrivateKey_OpenSSL, ECDSAPrivateKey_OpenSSL, + X509_Cert, X509_CRL) +from scapy.layers.tls.crypto.pkcs1 import (pkcs_os2ip, pkcs_i2osp, mapHashFunc, + _EncryptAndVerifyRSA, + _DecryptAndSignRSA) # Maximum allowed size in bytes for a certificate file, to avoid # loading huge file when importing a cert @@ -62,10 +60,10 @@ MAX_CRL_SIZE = 10*1024*1024 # some are that big # Some helpers ##################################################################### +@conf.commands.register def der2pem(der_string, obj="UNKNOWN"): - """ - Encode a byte string in PEM format. Header advertizes <obj> type. - """ + """Convert DER octet string to PEM format (with optional header)""" + # Encode a byte string in PEM format. Header advertizes <obj> type. pem_string = "-----BEGIN %s-----\n" % obj base64_string = base64.b64encode(der_string) chunks = [base64_string[i:i+64] for i in range(0, len(base64_string), 64)] @@ -73,10 +71,10 @@ def der2pem(der_string, obj="UNKNOWN"): pem_string += "\n-----END %s-----\n" % obj return pem_string +@conf.commands.register def pem2der(pem_string): - """ - Encode every line between the first '-----\n' and the 2nd-to-last '-----'. - """ + """Convert PEM string to DER format""" + # Encode all lines between the first '-----\n' and the 2nd-to-last '-----'. pem_string = pem_string.replace("\r", "") first_idx = pem_string.find("-----\n") + 6 if pem_string.find("-----BEGIN", first_idx) != -1: @@ -107,6 +105,7 @@ class _PKIObj(object): def __init__(self, frmt, der, pem): # Note that changing attributes of the _PKIObj does not update these # values (e.g. modifying k.modulus does not change k.der). + #XXX use __setattr__ for this self.frmt = frmt self.der = der self.pem = pem @@ -174,18 +173,25 @@ class _PubKeyFactory(_PKIObjMaker): """ Metaclass for PubKey creation. It casts the appropriate class on the fly, then fills in - the appropriate attributes with updateWith() submethod. + the appropriate attributes with import_from_asn1pkt() submethod. """ - def __call__(cls, key_path): + def __call__(cls, key_path=None): + + if key_path is None: + obj = type.__call__(cls) + if cls is PubKey: + cls = PubKeyRSA + obj.__class__ = cls + obj.frmt = "original" + obj.fill_and_store() + return obj - # First, we deal with the exceptional RSA KEA call. + # This deals with the rare RSA 'kx export' call. if type(key_path) is tuple: - e, m, mLen = key_path obj = type.__call__(cls) + obj.__class__ = PubKeyRSA obj.frmt = "tuple" - obj.modulus = m - obj.modulusLen = mLen - obj.pubExp = e + obj.import_from_tuple(key_path) return obj # Now for the usual calls, key_path may be the path to either: @@ -198,10 +204,13 @@ class _PubKeyFactory(_PKIObjMaker): pubkey = spki.subjectPublicKey if isinstance(pubkey, RSAPublicKey): obj.__class__ = PubKeyRSA - obj.updateWith(pubkey) + obj.import_from_asn1pkt(pubkey) elif isinstance(pubkey, ECDSAPublicKey): obj.__class__ = PubKeyECDSA - obj.updateWith(spki) + try: + obj.import_from_der(obj.der) + except ImportError: + pass else: raise marker = "PUBLIC KEY" @@ -209,7 +218,7 @@ class _PubKeyFactory(_PKIObjMaker): try: pubkey = RSAPublicKey(obj.der) obj.__class__ = PubKeyRSA - obj.updateWith(pubkey) + obj.import_from_asn1pkt(pubkey) marker = "RSA PUBLIC KEY" except: # We cannot import an ECDSA public key without curve knowledge @@ -228,74 +237,96 @@ class PubKey(object): __metaclass__ = _PubKeyFactory def verifyCert(self, cert): - """ - Verifies either a Cert or an X509_Cert. - """ + """ Verifies either a Cert or an X509_Cert. """ tbsCert = cert.tbsCertificate sigAlg = tbsCert.signature h = hash_by_oid[sigAlg.algorithm.val] sigVal = str(cert.signatureValue) - return self.verify(str(tbsCert), sigVal, h=h, - t='pkcs', - sigdecode=ecdsa.util.sigdecode_der) + return self.verify(str(tbsCert), sigVal, h=h, t='pkcs') -class PubKeyRSA(_PKIObj, PubKey, _EncryptAndVerifyRSA): +class PubKeyRSA(PubKey, _EncryptAndVerifyRSA): """ Wrapper for RSA keys based on _EncryptAndVerifyRSA from crypto/pkcs1.py Use the 'key' attribute to access original object. """ @crypto_validator - def updateWith(self, pubkey): - self.modulus = pubkey.modulus.val - self.modulusLen = len(binrepr(pubkey.modulus.val)) - self.pubExp = pubkey.publicExponent.val - self.key = rsa.RSAPublicNumbers( - n=self.modulus, - e=self.pubExp - ).public_key(default_backend()) + def fill_and_store(self, modulus=None, modulusLen=None, pubExp=None): + pubExp = pubExp or 65537 + if modulus is None: + real_modulusLen = modulusLen or 2048 + private_key = rsa.generate_private_key(public_exponent=pubExp, + key_size=real_modulusLen, + backend=default_backend()) + self.pubkey = private_key.public_key() + else: + real_modulusLen = len(binrepr(modulus)) + if real_modulusLen != modulusLen: + warning("modulus and modulusLen do not match!") + pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) + self.pubkey = pubNum.public_key(default_backend()) + #XXX lines below should be removed once pkcs1.py is cleaned of legacy + pubNum = self.pubkey.public_numbers() + self._modulusLen = real_modulusLen + self._modulus = pubNum.n + self._pubExp = pubNum.e + + @crypto_validator + def import_from_tuple(self, tup): + # this is rarely used + e, m, mLen = tup + if isinstance(m, str): + m = pkcs_os2ip(m) + if isinstance(e, str): + e = pkcs_os2ip(e) + self.fill_and_store(modulus=m, pubExp=e) + self.pem = self.pubkey.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + self.der = pem2der(self.pem) + + def import_from_asn1pkt(self, pubkey): + modulus = pubkey.modulus.val + pubExp = pubkey.publicExponent.val + self.fill_and_store(modulus=modulus, pubExp=pubExp) + def encrypt(self, msg, t=None, h=None, mgf=None, L=None): # no ECDSA encryption support, hence no ECDSA specific keywords here return _EncryptAndVerifyRSA.encrypt(self, msg, t=t, h=h, mgf=mgf, L=L) + def verify(self, msg, sig, h=None, - t=None, mgf=None, sLen=None, - sigdecode=None): + t=None, mgf=None, sLen=None): return _EncryptAndVerifyRSA.verify(self, msg, sig, h=h, t=t, mgf=mgf, sLen=sLen) - -class PubKeyECDSA(_PKIObj, PubKey): +class PubKeyECDSA(PubKey): """ - Wrapper for ECDSA keys based on VerifyingKey from ecdsa library. + Wrapper for ECDSA keys based on the cryptography library. Use the 'key' attribute to access original object. """ - def updateWith(self, spki): - # For now we use from_der() or from_string() methods, - # which do not offer support for compressed points. - #XXX Try using the from_public_point() method. - try: - self.key = ecdsa.VerifyingKey.from_der(str(spki)) - # from_der() raises an exception on explicit curves - except: - s = spki.subjectPublicKey.val_readable[1:] - p = spki.signatureAlgorithm.parameters - c = import_curve(p.fieldID.prime.val, - p.curve.a.val, - p.curve.b.val, - p.base.val, - p.order.val) - self.key = ecdsa.VerifyingKey.from_string(s, c) - def encrypt(self, msg, t=None, h=None, mgf=None, L=None): - # python-ecdsa does not support encryption + @crypto_validator + def fill_and_store(self, curve=None): + curve = curve or ec.SECP256R1 + private_key = ec.generate_private_key(curve(), default_backend()) + self.pubkey = private_key.public_key() + + @crypto_validator + def import_from_der(self, pubkey): + # XXX does the cryptography lib support explicit curves? + # check also for compressed points + self.pubkey = serialization.load_der_public_key(pubkey, + backend=default_backend()) + + def encrypt(self, msg, h=None, **kwargs): + # cryptography lib does not support ECDSA encryption raise Exception("No ECDSA encryption support") - def verify(self, msg, sig, h=None, - t=None, mgf=None, sLen=None, - sigdecode=ecdsa.util.sigdecode_string): - try: - return self.key.verify(sig, msg, hashfunc=mapHashFunc(h), - sigdecode=sigdecode) - except ecdsa.keys.BadSignatureError: - return False + + @crypto_validator + def verify(self, msg, sig, h=None, **kwargs): + # 'sig' should be a DER-encoded signature, as per RFC 3279 + verifier = self.pubkey.verifier(sig, ec.ECDSA(mapHashFunc(h))) + verifier.update(msg) + return verifier.verify() ################ @@ -306,9 +337,9 @@ class _PrivKeyFactory(_PKIObjMaker): """ Metaclass for PrivKey creation. It casts the appropriate class on the fly, then fills in - the appropriate attributes with updateWith() submethod. + the appropriate attributes with import_from_asn1pkt() submethod. """ - def __call__(cls, key_path): + def __call__(cls, key_path=None): """ key_path may be the path to either: _an RSAPrivateKey_OpenSSL (as generated by openssl); @@ -316,6 +347,15 @@ class _PrivKeyFactory(_PKIObjMaker): _an RSAPrivateKey; _an ECDSAPrivateKey. """ + if key_path is None: + obj = type.__call__(cls) + if cls is PrivKey: + cls = PrivKeyECDSA + obj.__class__ = cls + obj.frmt = "original" + obj.fill_and_store() + return obj + obj = _PKIObjMaker.__call__(cls, key_path, MAX_KEY_SIZE) multiPEM = False try: @@ -342,7 +382,10 @@ class _PrivKeyFactory(_PKIObjMaker): marker = "EC PRIVATE KEY" except: raise Exception("Unable to import private key") - obj.updateWith(privkey) + try: + obj.import_from_asn1pkt(privkey) + except ImportError: + pass if obj.frmt == "DER": if multiPEM: @@ -372,14 +415,11 @@ class PrivKey(object): to both PrivKeyRSA and PrivKeyECDSA, the sign() methods of the subclasses accept any argument, be it from the RSA or ECDSA world, and then they keep the ones they're interested in. - Here, t will be passed eventually to pkcs1._DecryptAndSignRSA.sign(), - while sigencode will be passed to ecdsa.keys.SigningKey.sign(). + Here, t will be passed eventually to pkcs1._DecryptAndSignRSA.sign(). """ sigAlg = tbsCert.signature h = h or hash_by_oid[sigAlg.algorithm.val] - sigVal = self.sign(str(tbsCert), h=h, - t='pkcs', - sigencode=ecdsa.util.sigencode_der) + sigVal = self.sign(str(tbsCert), h=h, t='pkcs') c = X509_Cert() c.tbsCertificate = tbsCert c.signatureAlgorithm = sigAlg @@ -387,63 +427,110 @@ class PrivKey(object): return c def resignCert(self, cert): - # works with both Cert and X509_Cert types + """ Rewrite the signature of either a Cert or an X509_Cert. """ return self.signTBSCert(cert.tbsCertificate) + def verifyCert(self, cert): + """ Verifies either a Cert or an X509_Cert. """ + tbsCert = cert.tbsCertificate + sigAlg = tbsCert.signature + h = hash_by_oid[sigAlg.algorithm.val] + sigVal = str(cert.signatureValue) + return self.verify(str(tbsCert), sigVal, h=h, t='pkcs') + -class PrivKeyRSA(_PKIObj, PrivKey, _EncryptAndVerifyRSA, _DecryptAndSignRSA): +class PrivKeyRSA(PrivKey, _EncryptAndVerifyRSA, _DecryptAndSignRSA): """ Wrapper for RSA keys based on _DecryptAndSignRSA from crypto/pkcs1.py Use the 'key' attribute to access original object. """ @crypto_validator - def updateWith(self, privkey): - self.modulus = privkey.modulus.val - self.modulusLen = len(binrepr(privkey.modulus.val)) - self.pubExp = privkey.publicExponent.val - self.privExp = privkey.privateExponent.val - self.prime1 = privkey.prime1.val - self.prime2 = privkey.prime2.val - self.exponent1 = privkey.exponent1.val - self.exponent2 = privkey.exponent2.val - self.coefficient = privkey.coefficient.val - self.key = rsa.RSAPrivateNumbers( - p=self.prime1, q=self.prime2, d=self.privExp, dmp1=self.exponent1, - dmq1=self.exponent2, iqmp=self.coefficient, - public_numbers=rsa.RSAPublicNumbers(n=self.modulus, e=self.pubExp), - ).private_key(default_backend()) + def fill_and_store(self, modulus=None, modulusLen=None, pubExp=None, + prime1=None, prime2=None, coefficient=None, + exponent1=None, exponent2=None, privExp=None): + pubExp = pubExp or 65537 + if None in [modulus, prime1, prime2, coefficient, privExp, + exponent1, exponent2]: + # note that the library requires every parameter + # in order to call RSAPrivateNumbers(...) + # if one of these is missing, we generate a whole new key + real_modulusLen = modulusLen or 2048 + self.key = rsa.generate_private_key(public_exponent=pubExp, + key_size=real_modulusLen, + backend=default_backend()) + self.pubkey = self.key.public_key() + else: + real_modulusLen = len(binrepr(modulus)) + if real_modulusLen != modulusLen: + warning("modulus and modulusLen do not match!") + pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) + privNum = rsa.RSAPrivateNumbers(p=prime1, q=prime2, + dmp1=exponent1, dmq1=exponent2, + iqmp=coefficient, d=privExp, + public_numbers=pubNum) + self.key = privNum.private_key(default_backend()) + self.pubkey = self.key.public_key() + #XXX lines below should be removed once pkcs1.py is cleaned of legacy + pubNum = self.pubkey.public_numbers() + self._modulusLen = real_modulusLen + self._modulus = pubNum.n + self._pubExp = pubNum.e + + def import_from_asn1pkt(self, privkey): + modulus = privkey.modulus.val + pubExp = privkey.publicExponent.val + privExp = privkey.privateExponent.val + prime1 = privkey.prime1.val + prime2 = privkey.prime2.val + exponent1 = privkey.exponent1.val + exponent2 = privkey.exponent2.val + coefficient = privkey.coefficient.val + self.fill_and_store(modulus=modulus, pubExp=pubExp, + privExp=privExp, prime1=prime1, prime2=prime2, + exponent1=exponent1, exponent2=exponent2, + coefficient=coefficient) + def verify(self, msg, sig, h=None, - t=None, mgf=None, sLen=None, - sigdecode=None): + t=None, mgf=None, sLen=None): # Let's copy this from PubKeyRSA instead of adding another baseclass :) return _EncryptAndVerifyRSA.verify(self, msg, sig, h=h, t=t, mgf=mgf, sLen=sLen) + def sign(self, data, h=None, - t=None, mgf=None, sLen=None, - k=None, entropy=None, sigencode=None): + t=None, mgf=None, sLen=None): return _DecryptAndSignRSA.sign(self, data, h=h, t=t, mgf=mgf, sLen=sLen) -class PrivKeyECDSA(_PKIObj, PrivKey): +class PrivKeyECDSA(PrivKey): """ Wrapper for ECDSA keys based on SigningKey from ecdsa library. Use the 'key' attribute to access original object. """ - def updateWith(self, privkey): - self.privKey = pkcs_os2ip(privkey.privateKey.val) - self.key = ecdsa.SigningKey.from_der(str(privkey)) - self.vkey = self.key.get_verifying_key() - def verify(self, msg, sig, h=None, - t=None, mgf=None, sLen=None, - sigdecode=None): - return self.vkey.verify(sig, msg, hashfunc=mapHashFunc(h), - sigdecode=sigdecode) - def sign(self, data, h=None, - t=None, mgf=None, sLen=None, - k=None, entropy=None, sigencode=ecdsa.util.sigencode_string): - return self.key.sign(data, hashfunc=mapHashFunc(h), - k=k, entropy=entropy, sigencode=sigencode) + @crypto_validator + def fill_and_store(self, curve=None): + curve = curve or ec.SECP256R1 + self.key = ec.generate_private_key(curve(), default_backend()) + self.pubkey = self.key.public_key() + + @crypto_validator + def import_from_asn1pkt(self, privkey): + self.key = serialization.load_der_private_key(str(privkey), None, + backend=default_backend()) + self.pubkey = self.key.public_key() + + @crypto_validator + def verify(self, msg, sig, h=None, **kwargs): + # 'sig' should be a DER-encoded signature, as per RFC 3279 + verifier = self.pubkey.verifier(sig, ec.ECDSA(mapHashFunc(h))) + verifier.update(msg) + return verifier.verify() + + @crypto_validator + def sign(self, data, h=None, **kwargs): + signer = self.key.signer(ec.ECDSA(mapHashFunc(h))) + signer.update(data) + return signer.finalize() ################ @@ -463,18 +550,18 @@ class _CertMaker(_PKIObjMaker): cert = X509_Cert(obj.der) except: raise Exception("Unable to import certificate") - obj.updateWith(cert) + obj.import_from_asn1pkt(cert) return obj -class Cert(_PKIObj): +class Cert(object): """ Wrapper for the X509_Cert from layers/x509.py. Use the 'x509Cert' attribute to access original object. """ __metaclass__ = _CertMaker - def updateWith(self, cert): + def import_from_asn1pkt(self, cert): error_msg = "Unable to import certificate" self.x509Cert = cert @@ -558,11 +645,9 @@ class Cert(_PKIObj): return self.pubKey.encrypt(msg, t=t, h=h, mgf=mgf, L=L) def verify(self, msg, sig, h=None, - t=None, mgf=None, sLen=None, - sigdecode=None): + t=None, mgf=None, sLen=None): return self.pubKey.verify(msg, sig, h=h, - t=t, mgf=mgf, sLen=sLen, - sigdecode=sigdecode) + t=t, mgf=mgf, sLen=sLen) def remainingDays(self, now=None): """ @@ -593,7 +678,7 @@ class Cert(_PKIObj): else: now = time.strptime(now, '%b %d %H:%M:%S %Y %Z') except: - print "Bad time string provided, will use localtime() instead." + warning("Bad time string provided, will use localtime() instead.") now = time.localtime() now = time.mktime(now) @@ -665,18 +750,18 @@ class _CRLMaker(_PKIObjMaker): crl = X509_CRL(obj.der) except: raise Exception("Unable to import CRL") - obj.updateWith(crl) + obj.import_from_asn1pkt(crl) return obj -class CRL(_PKIObj): +class CRL(object): """ Wrapper for the X509_CRL from layers/x509.py. Use the 'x509CRL' attribute to access original object. """ __metaclass__ = _CRLMaker - def updateWith(self, crl): + def import_from_asn1pkt(self, crl): error_msg = "Unable to import CRL" self.x509CRL = crl diff --git a/scapy/layers/tls/crypto/__init__.py b/scapy/layers/tls/crypto/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12affb4e0d1d1a5a80cf589d0c5cff1e2c94cb39 100644 --- a/scapy/layers/tls/crypto/__init__.py +++ b/scapy/layers/tls/crypto/__init__.py @@ -0,0 +1,9 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +Cryptographic capabilities for TLS. +""" + diff --git a/scapy/layers/tls/crypto/all.py b/scapy/layers/tls/crypto/all.py new file mode 100644 index 0000000000000000000000000000000000000000..e8192a55dcec9a67aa4c0b027f6fa16a5b40c2c7 --- /dev/null +++ b/scapy/layers/tls/crypto/all.py @@ -0,0 +1,15 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Aggregate some TLS crypto objects. +""" + +# XXX This line should be removed once standard FFDH groups have been +# registered in the cryptography library. +from scapy.layers.tls.crypto.ffdh import * + +from scapy.layers.tls.crypto.suites import * + diff --git a/scapy/layers/tls/crypto/cipher_aead.py b/scapy/layers/tls/crypto/cipher_aead.py new file mode 100644 index 0000000000000000000000000000000000000000..3b9d84a095bce3b6b1a25cc5d10af8f1e0d78ee6 --- /dev/null +++ b/scapy/layers/tls/crypto/cipher_aead.py @@ -0,0 +1,194 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Authenticated Encryption with Associated Data ciphers. + +RFC 5288 introduces new ciphersuites for TLS 1.2 which are based on AES in +Galois/Counter Mode (GCM). RFC 6655 in turn introduces AES_CCM ciphersuites. +The related AEAD algorithms are defined in RFC 5116. + +For now the cryptography library only supports GCM mode. +Their interface might (and should) be changed in the future. +""" + +import struct + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend +from cryptography.exceptions import InvalidTag + +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +from scapy.layers.tls.crypto.ciphers import CipherError + + +tls_aead_cipher_algs = {} + +class _AEADCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if ciph_name != "_AEADCipher": + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_AEADCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if ciph_name != "_AEADCipher": + tls_aead_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class AEADTagError(Exception): + """ + Raised when MAC verification fails. Hopefully you can access to the + deciphered (but unathenticated) plaintext as e.args. + """ + pass + +class _AEADCipher(object): + __metaclass__ = _AEADCipherMetaclass + type = "aead" + + def __init__(self, key=None, salt=None, nonce_explicit=None): + """ + 'key' and 'salt' are to be provided as strings, whereas the internal + 'nonce_explicit' is an integer (it is simpler for incrementation). + """ + self.ready = {"key":True, "salt":True, "nonce_explicit":True} + if key is None: + self.ready["key"] = False + key = "\0" * self.key_len + if salt is None: + self.ready["salt"] = False + salt = "\0" * self.salt_len + if nonce_explicit is None: + self.ready["nonce_explicit"] = False + nonce_explicit = 0 + + if type(nonce_explicit) is str: + nonce_explicit = pkcs_os2ip(nonce_explicit) + + # we use super() in order to avoid any deadlock with __setattr__ + super(_AEADCipher, self).__setattr__("key", key) + super(_AEADCipher, self).__setattr__("salt", salt) + super(_AEADCipher, self).__setattr__("nonce_explicit", nonce_explicit) + + iv = salt + pkcs_i2osp(nonce_explicit, self.nonce_explicit_len) + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(iv), + backend=default_backend()) + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + self._cipher.algorithm.key = val + self.ready["key"] = True + elif name == "salt": + iv = val + pkcs_i2osp(self.nonce_explicit, self.nonce_explicit_len) + if self._cipher is not None: + self._cipher.mode._initialization_vector = iv + self.ready["salt"] = True + elif name == "nonce_explicit": + if type(val) is str: + val = pkcs_os2ip(val) + iv = self.salt + pkcs_i2osp(val, self.nonce_explicit_len) + if self._cipher is not None: + self._cipher.mode._initialization_vector = iv + self.ready["nonce_explicit"] = True + super(_AEADCipher, self).__setattr__(name, val) + + def _update_nonce(self): + """ + Increment the explicit nonce while avoiding any overflow. + """ + ne = self.nonce_explicit + 1 + self.nonce_explicit = ne % 2**(self.nonce_explicit_len*8) + + def auth_encrypt(self, P, A): + """ + Encrypt the data, prepend the explicit part of the nonce, + and append the computed authentication code. + Additional data may be authenticated without encryption (as A). + + Note that the cipher's authentication tag must be None when encrypting. + """ + if False in self.ready.itervalues(): + raise CipherError, (P, A) + self._cipher.mode._tag = None + encryptor = self._cipher.encryptor() + encryptor.authenticate_additional_data(A) + res = encryptor.update(P) + encryptor.finalize() + res += encryptor.tag + + nonce_explicit = pkcs_i2osp(self.nonce_explicit, + self.nonce_explicit_len) + self._update_nonce() + return nonce_explicit + res + + def auth_decrypt(self, A, C, add_length=True): + """ + Decrypt the data and verify the authentication code (in this order). + When additional data was authenticated, it has to be passed (as A). + If the verification fails, an AEADTagError is raised. It is the user's + responsibility to catch it if deemed useful. If we lack the key, we + raise a CipherError which contains the encrypted input. + + Note that we add the TLSCiphertext length to A although we're supposed + to add the TLSCompressed length. Fortunately, they are the same, + but the specifications actually messed up here. :'( + + The 'add_length' switch should always be True for TLS, but we provide + it anyway (mostly for test cases, hum). + """ + nonce_explicit_str, C, mac = (C[:self.nonce_explicit_len], + C[self.nonce_explicit_len:-self.tag_len], + C[-self.tag_len:]) + + if False in self.ready.itervalues(): + raise CipherError, (nonce_explicit_str, C, mac) + + self.nonce_explicit = pkcs_os2ip(nonce_explicit_str) + self._cipher.mode._tag = mac + + decryptor = self._cipher.decryptor() + if add_length: + A += struct.pack("!H", len(C)) + decryptor.authenticate_additional_data(A) + + P = decryptor.update(C) + try: + decryptor.finalize() + except InvalidTag: + raise AEADTagError, (nonce_explicit_str, P, mac) + return nonce_explicit_str, P, mac + + +class Cipher_AES_128_GCM(_AEADCipher): + pc_cls = algorithms.AES + pc_cls_mode = modes.GCM + block_size = 16 + key_len = 16 + salt_len = 4 + nonce_explicit_len = 8 + tag_len = 16 + +class Cipher_AES_256_GCM(Cipher_AES_128_GCM): + key_len = 32 + + +# no support for now in the cryptography library +#class Cipher_AES_128_CCM(_AEADCipher): +# pc_cls_mode = modes.CCM +# +#class Cipher_AES_256_CCM(Cipher_AES_128_CCM): +# key_len = 32 +# +#class Cipher_AES_128_CCM_8(Cipher_AES_128_CCM): +# tag_len = 8 +# +#class Cipher_AES_256_CCM_8(Cipher_AES_128_CCM_8): +# key_len = 32 + diff --git a/scapy/layers/tls/crypto/cipher_block.py b/scapy/layers/tls/crypto/cipher_block.py new file mode 100644 index 0000000000000000000000000000000000000000..de750438863f0adb8f3f4f8129c20a9a70351fa9 --- /dev/null +++ b/scapy/layers/tls/crypto/cipher_block.py @@ -0,0 +1,157 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Block ciphers. +""" + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend + +from scapy.utils import strxor +from scapy.layers.tls.crypto.ciphers import CipherError + + +tls_block_cipher_algs = {} + +class _BlockCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if ciph_name != "_BlockCipher": + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_BlockCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if ciph_name != "_BlockCipher": + tls_block_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class _BlockCipher(object): + __metaclass__ = _BlockCipherMetaclass + type = "block" + + def __init__(self, key=None, iv=None): + self.ready = {"key":True, "iv":True} + if key is None: + self.ready["key"] = False + key = "\0" * self.key_len + if iv is None or iv == "": + self.ready["iv"] = False + iv = "\0" * self.block_size + + # we use super() in order to avoid any deadlock with __setattr__ + super(_BlockCipher, self).__setattr__("key", key) + super(_BlockCipher, self).__setattr__("iv", iv) + + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(iv), + backend=default_backend()) + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + self._cipher.algorithm.key = val + self.ready["key"] = True + elif name == "iv": + if self._cipher is not None: + self._cipher.mode._initialization_vector = val + self.ready["iv"] = True + super(_BlockCipher, self).__setattr__(name, val) + + + def encrypt(self, data): + """ + Encrypt the data. Also, update the cipher iv. This is needed for SSLv3 + and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.post_build(). + """ + if False in self.ready.itervalues(): + raise CipherError, data + encryptor = self._cipher.encryptor() + tmp = encryptor.update(data) + encryptor.finalize() + self.iv = tmp[-self.block_size:] + return tmp + + def decrypt(self, data): + """ + Decrypt the data. Also, update the cipher iv. This is needed for SSLv3 + and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.pre_dissect(). + If we lack the key, we raise a CipherError which contains the input. + """ + if False in self.ready.itervalues(): + raise CipherError, data + decryptor = self._cipher.decryptor() + tmp = decryptor.update(data) + decryptor.finalize() + self.iv = data[-self.block_size:] + return tmp + + +class Cipher_AES_128_CBC(_BlockCipher): + pc_cls = algorithms.AES + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + +class Cipher_AES_256_CBC(Cipher_AES_128_CBC): + key_len = 32 + + +class Cipher_CAMELLIA_128_CBC(_BlockCipher): + pc_cls = algorithms.Camellia + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + +class Cipher_CAMELLIA_256_CBC(Cipher_CAMELLIA_128_CBC): + key_len = 32 + + +### Mostly deprecated ciphers + +class Cipher_3DES_EDE_CBC(_BlockCipher): + pc_cls = algorithms.TripleDES + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 24 + +class Cipher_IDEA_CBC(_BlockCipher): + pc_cls = algorithms.IDEA + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 16 + +class Cipher_SEED_CBC(_BlockCipher): + pc_cls = algorithms.SEED + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + +#class Cipher_DES_CBC(_BlockCipher): +# pc_cls = algorithms.DES # no support in the cryptography library +# pc_cls_mode = modes.CBC +# block_size = 8 +# key_len = 8 + +#class Cipher_DES40_CBC(Cipher_DES_CBC): +# """ +# This is an export cipher example. The key length has been weakened to 5 +# random bytes (i.e. 5 bytes will be extracted from the master_secret). +# Yet, we still need to know the original length which will actually be +# fed into the encryption algorithm. This is what expanded_key_len +# is for, and it gets used in PRF.postprocess_key_for_export(). +# We never define this attribute with non-export ciphers. +# """ +# key_len = 5 +# expanded_key_len = 8 + +#class Cipher_RC2_CBC_40(_BlockCipher): # RFC 2268 +# pc_cls = ARC2 # no support in the cryptography library +# pc_cls_mode = modes.CBC +# block_size = 8 +# key_len = 5 +# expanded_key_len = 16 + diff --git a/scapy/layers/tls/crypto/cipher_stream.py b/scapy/layers/tls/crypto/cipher_stream.py new file mode 100644 index 0000000000000000000000000000000000000000..7a403f3cad91da287bf25d508c16249533e125cc --- /dev/null +++ b/scapy/layers/tls/crypto/cipher_stream.py @@ -0,0 +1,98 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Stream ciphers. +""" + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms +from cryptography.hazmat.backends import default_backend + +from scapy.layers.tls.crypto.ciphers import CipherError + + +tls_stream_cipher_algs = {} + +class _StreamCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if ciph_name != "_StreamCipher": + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_StreamCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if ciph_name != "_StreamCipher": + tls_stream_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class _StreamCipher(object): + __metaclass__ = _StreamCipherMetaclass + type = "stream" + + def __init__(self, key=None): + """ + Note that we have to keep the encryption/decryption state in unique + encryptor and decryptor objects. This differs from _BlockCipher. + """ + self.ready = {"key":True} + if key is None: + self.ready["key"] = False + key = "\0" * self.key_len + + # we use super() in order to avoid any deadlock with __setattr__ + super(_StreamCipher, self).__setattr__("key", key) + + self._cipher = Cipher(self.pc_cls(key), + mode=None, + backend=default_backend()) + self.encryptor = self._cipher.encryptor() + self.decryptor = self._cipher.decryptor() + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + self._cipher.algorithm.key = val + self.ready["key"] = True + super(_StreamCipher, self).__setattr__(name, val) + + def encrypt(self, data): + if False in self.ready.itervalues(): + raise CipherError, data + return self.encryptor.update(data) + + def decrypt(self, data): + if False in self.ready.itervalues(): + raise CipherError, data + return self.decryptor.update(data) + + +class Cipher_RC4_40(_StreamCipher): + pc_cls = algorithms.ARC4 + key_len = 5 + expanded_key_len = 16 + +class Cipher_RC4_128(Cipher_RC4_40): + key_len = 16 + + +class Cipher_NULL(_StreamCipher): + key_len = 0 + expanded_key_len = 0 + + def __init__(self, key=None): + self.ready = {"key":True} + # we use super() in order to avoid any deadlock with __setattr__ + super(_StreamCipher, self).__setattr__("key", key) + self._cipher = None + + def encrypt(self, data): + return data + + def decrypt(self, data): + return data + diff --git a/scapy/layers/tls/crypto/ciphers.py b/scapy/layers/tls/crypto/ciphers.py new file mode 100644 index 0000000000000000000000000000000000000000..331546e2885fe28204cef693a747596fd4aef1b2 --- /dev/null +++ b/scapy/layers/tls/crypto/ciphers.py @@ -0,0 +1,28 @@ + +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS ciphers. +""" + +class CipherError(Exception): + """ + Raised when .decrypt() or .auth_decrypt() fails. + """ + pass + + +# We have to keep these imports below CipherError definition +# in order to avoid circular dependencies. +from scapy.layers.tls.crypto.cipher_aead import tls_aead_cipher_algs +from scapy.layers.tls.crypto.cipher_block import tls_block_cipher_algs +from scapy.layers.tls.crypto.cipher_stream import tls_stream_cipher_algs + +tls_cipher_algs = {} +tls_cipher_algs.update(tls_block_cipher_algs) +tls_cipher_algs.update(tls_stream_cipher_algs) +tls_cipher_algs.update(tls_aead_cipher_algs) + diff --git a/scapy/layers/tls/crypto/compression.py b/scapy/layers/tls/crypto/compression.py new file mode 100644 index 0000000000000000000000000000000000000000..52d60e73a199122f6a272c768135100c3a54420e --- /dev/null +++ b/scapy/layers/tls/crypto/compression.py @@ -0,0 +1,84 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS compression. +""" + +import zlib + +from scapy.error import warning + + +_tls_compression_algs = {} +_tls_compression_algs_cls = {} + +class _GenericCompMetaclass(type): + """ + Compression classes are automatically registered through this metaclass. + """ + def __new__(cls, name, bases, dct): + the_class = super(_GenericCompMetaclass, cls).__new__(cls, name, + bases, dct) + comp_name = dct.get("name") + val = dct.get("val") + if comp_name: + _tls_compression_algs[val] = comp_name + _tls_compression_algs_cls[val] = the_class + return the_class + + +class _GenericComp(object): + __metaclass__ = _GenericCompMetaclass + + +class Comp_NULL(_GenericComp): + """ + The default and advised compression method for TLS: doing nothing. + """ + name = "null" + val = 0 + + def compress(self, s): + return s + + def decompress(self, s): + return s + +class Comp_Deflate(_GenericComp): + """ + DEFLATE algorithm, specified for TLS by RFC 3749. + """ + name = "deflate" + val = 1 + + def compress(self, s): + tmp = self.compress_state.compress(s) + tmp += self.compress_state.flush(zlib.Z_FULL_FLUSH) + return tmp + + def decompress(self, s): + return self.decompress_state.decompress(s) + + def __init__(self): + self.compress_state = zlib.compressobj() + self.decompress_state = zlib.decompressobj() + +class Comp_LZS(_GenericComp): + """ + Lempel-Zic-Stac (LZS) algorithm, specified for TLS by RFC 3943. + XXX No support for now. + """ + name = "LZS" + val = 64 + + def compress(self, s): + warning("LZS Compression algorithm is not implemented yet") + return s + + def decompress(self, s): + warning("LZS Compression algorithm is not implemented yet") + return s + diff --git a/scapy/layers/tls/crypto/curves.py b/scapy/layers/tls/crypto/curves.py index 0512766136a7b547056ed67d231c0d9585529501..9a1d875e6930631e36693050da797891d431424b 100644 --- a/scapy/layers/tls/crypto/curves.py +++ b/scapy/layers/tls/crypto/curves.py @@ -5,402 +5,265 @@ """ Implicit elliptic curves. -""" - -# Recommended curve parameters from www.secg.org/SEC2-Ver-1.0.pdf -# and www.ecc-brainpool.org/download/Domain-parameters.pdf -# Note that this module will overwrite curves from python-ecdsa. - -import math -from ecdsa.ellipticcurve import CurveFp, Point -from ecdsa.curves import Curve -from ecdsa.numbertheory import square_root_mod_prime - -from scapy.utils import long_converter, binrepr -from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip - - -############################################################## -# Some helpers -############################################################## - -def encode_point(point, point_format=0): - """ - Return a string representation of the Point p, according to point_format. - """ - pLen = len(binrepr(point.curve().p())) - x = pkcs_i2osp(point.x(), math.ceil(pLen/8)) - y = pkcs_i2osp(point.y(), math.ceil(pLen/8)) - if point_format == 0: - frmt = '\x04' - elif point_format == 1: - frmt = chr(2 + y%2) - y = '' - else: - raise Exception("No support for point_format %d" % point_format) - return frmt + x + y - -def extract_coordinates(g, curve): - """ - Return the coordinates x and y as integers, - regardless of the point format of string g. - Second expected parameter is a CurveFp. - """ - p = curve.p() - point_format = g[0] - point = g[1:] - if point_format == '\x04': - point_len = len(point) - if point_len % 2 != 0: - raise Exception("Point length is not even.") - x_bytes = point[:point_len>>1] - x = pkcs_os2ip(x_bytes) % p - y_bytes = point[point_len>>1:] - y = pkcs_os2ip(y_bytes) % p - elif point_format in ['\x02', '\x03']: - x_bytes = point - x = pkcs_os2ip(x_bytes) % p - # perform the y coordinate computation with self.tls_ec - y_square = (x*x*x + curve.a()*x + curve.b()) % p - y = square_root_mod_prime(y_square, p) - y_parity = ord(point_format) % 2 # \x02 means even, \x03 means odd - if y % 2 != y_parity: - y = -y % p - else: - raise Exception("Point starts with %s. This encoding " - "is not recognized." % repr(point_format)) - if not curve.contains_point(x, y): - raise Exception("The point we extracted does not belong on the curve!") - return x, y -def import_curve(p, a, b, g, r, name="dummyName", oid=(1, 3, 132, 0, 0xff)): - """ - Create an ecdsa.curves.Curve from the usual parameters. - Arguments may be either octet strings or integers, - except g which we expect to be an octet string. - """ - if isinstance(p, str): - p = pkcs_os2ip(p) - if isinstance(a, str): - a = pkcs_os2ip(a) - if isinstance(b, str): - b = pkcs_os2ip(b) - if isinstance(r, str): - r = pkcs_os2ip(r) - curve = CurveFp(p, a, b) - x, y = extract_coordinates(g, curve) - generator = Point(curve, x, y, r) - return Curve(name, curve, generator, oid) +Recommended curve parameters from www.secg.org/SEC2-Ver-1.0.pdf +and www.ecc-brainpool.org/download/Domain-parameters.pdf +This is a ghost file since the shift from 'ecdsa' to 'cryptography' lib. +Part of the code has been kept, but commented out, in case anyone would like +to improve ECC support in 'cryptography' (namely for the compressed point +format and additional curves). +""" -############################################################## -# Named curves -############################################################## - -# We always provide _a as a positive integer. - -_p = long_converter(""" - ffffffff ffffffff ffffffff fffffffe ffffac73""") -_a = 0 -_b = 7 -_Gx = long_converter(""" - 3b4c382c e37aa192 a4019e76 3036f4f5 dd4d7ebb""") -_Gy = long_converter(""" - 938cf935 318fdced 6bc28286 531733c3 f03c4fee""") -_r = long_converter("""01 - 00000000 00000000 0001b8fa 16dfab9a ca16b6b3""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP160k1 = Curve("SECP160k1", curve, generator, - (1, 3, 132, 0, 9), "secp160k1") - -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff 7fffffff""") -_a = -3 % _p -_b = long_converter(""" - 1c97befc 54bd7a8b 65acf89f 81d4d4ad c565fa45""") -_Gx = long_converter(""" - 4a96b568 8ef57328 46646989 68c38bb9 13cbfc82""") -_Gy = long_converter(""" - 23a62855 3168947d 59dcc912 04235137 7ac5fb32""") -_r = long_converter("""01 - 00000000 00000000 0001f4c8 f927aed3 ca752257""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP160r1 = Curve("SECP160r1", curve, generator, - (1, 3, 132, 0, 8), "secp160r1") - -_p = long_converter(""" - ffffffff ffffffff ffffffff fffffffe ffffac73""") -_a = -3 % _p -_b = long_converter(""" - b4e134d3 fb59eb8b ab572749 04664d5a f50388ba""") -_Gx = long_converter(""" - 52dcb034 293a117e 1f4ff11b 30f7199d 3144ce6d""") -_Gy = long_converter(""" - feaffef2 e331f296 e071fa0d f9982cfe a7d43f2e""") -_r = long_converter("""01 - 00000000 00000000 0000351e e786a818 f3a1a16b""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP160r2 = Curve("SECP160r2", curve, generator, - (1, 3, 132, 0, 30), "secp160r2") - -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff fffffffe ffffee37""") -_a = 0 -_b = 3 -_Gx = long_converter(""" - db4ff10e c057e9ae 26b07d02 80b7f434 1da5d1b1 eae06c7d""") -_Gy = long_converter(""" - 9b2f2f6d 9c5628a7 844163d0 15be8634 4082aa88 d95e2f9d""") -_r = long_converter(""" - ffffffff ffffffff fffffffe 26f2fc17 0f69466a 74defd8d""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP192k1 = Curve("SECP192k1", curve, generator, - (1, 3, 132, 0, 31), "secp192k1") - -_p = long_converter(""" - ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff""") -_a = -3 % _p -_b = long_converter(""" - 64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1""") -_Gx = long_converter(""" - 188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012""") -_Gy = long_converter(""" - 07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811""") -_r = long_converter(""" - ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP192r1 = Curve("SECP192r1", curve, generator, - (1, 2, 840, 10045, 3, 1, 1), "prime192v1") - -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe - ffffe56d""") -_a = 0 -_b = 5 -_Gx = long_converter(""" - a1455b33 4df099df 30fc28a1 69a467e9 e47075a9 0f7e650e - b6b7a45c""") -_Gy = long_converter(""" - 7e089fed 7fba3442 82cafbd6 f7e319f7 c0b0bd59 e2ca4bdb - 556d61a5""") -_r = long_converter("""01 - 00000000 00000000 00000000 0001dce8 d2ec6184 caf0a971 - 769fb1f7""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP224k1 = Curve("SECP224k1", curve, generator, - (1, 3, 132, 0, 32), "secp224k1") +#import math +# +#from scapy.utils import long_converter, binrepr +#from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +# +# +#def encode_point(point, point_format=0): +# """ +# Return a string representation of the Point p, according to point_format. +# """ +# pLen = len(binrepr(point.curve().p())) +# x = pkcs_i2osp(point.x(), math.ceil(pLen/8)) +# y = pkcs_i2osp(point.y(), math.ceil(pLen/8)) +# if point_format == 0: +# frmt = '\x04' +# elif point_format == 1: +# frmt = chr(2 + y%2) +# y = '' +# else: +# raise Exception("No support for point_format %d" % point_format) +# return frmt + x + y +# +# +#try: +# import ecdsa +# ecdsa_support = True +#except ImportError: +# import logging +# log_loading = logging.getLogger("scapy.loading") +# log_loading.info("Can't import python ecdsa lib. No curves.") +# +# +#if ecdsa_support: +# +# from ecdsa.ellipticcurve import CurveFp, Point +# from ecdsa.curves import Curve +# from ecdsa.numbertheory import square_root_mod_prime +# +# +# def extract_coordinates(g, curve): +# """ +# Return the coordinates x and y as integers, +# regardless of the point format of string g. +# Second expected parameter is a CurveFp. +# """ +# p = curve.p() +# point_format = g[0] +# point = g[1:] +# if point_format == '\x04': +# point_len = len(point) +# if point_len % 2 != 0: +# raise Exception("Point length is not even.") +# x_bytes = point[:point_len>>1] +# x = pkcs_os2ip(x_bytes) % p +# y_bytes = point[point_len>>1:] +# y = pkcs_os2ip(y_bytes) % p +# elif point_format in ['\x02', '\x03']: +# x_bytes = point +# x = pkcs_os2ip(x_bytes) % p +# # perform the y coordinate computation with self.tls_ec +# y_square = (x*x*x + curve.a()*x + curve.b()) % p +# y = square_root_mod_prime(y_square, p) +# y_parity = ord(point_format) % 2 # \x02 means even, \x03 means odd +# if y % 2 != y_parity: +# y = -y % p +# else: +# raise Exception("Point starts with %s. This encoding " +# "is not recognized." % repr(point_format)) +# if not curve.contains_point(x, y): +# raise Exception("The point we extracted does not belong on the curve!") +# return x, y +# +# def import_curve(p, a, b, g, r, name="dummyName", oid=(1, 3, 132, 0, 0xff)): +# """ +# Create an ecdsa.curves.Curve from the usual parameters. +# Arguments may be either octet strings or integers, +# except g which we expect to be an octet string. +# """ +# if isinstance(p, str): +# p = pkcs_os2ip(p) +# if isinstance(a, str): +# a = pkcs_os2ip(a) +# if isinstance(b, str): +# b = pkcs_os2ip(b) +# if isinstance(r, str): +# r = pkcs_os2ip(r) +# curve = CurveFp(p, a, b) +# x, y = extract_coordinates(g, curve) +# generator = Point(curve, x, y, r) +# return Curve(name, curve, generator, oid) -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff 00000000 00000000 - 00000001""") -_a = -3 % _p -_b = long_converter(""" - b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 - 2355ffb4""") -_Gx = long_converter(""" - b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 - 115c1d21""") -_Gy = long_converter(""" - bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 - 85007e34""") -_r = long_converter(""" - ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 - 5c5c2a3d""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP224r1 = Curve("SECP224r1", curve, generator, - (1, 3, 132, 0, 33), "secp224r1") -# (already defined as SECP256k1 by python-ecdsa) -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff - fffffffe fffffc2f""") -_a = 0 -_b = 7 -_Gx = long_converter(""" - 79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 - 59f2815b 16f81798""") -_Gy = long_converter(""" - 483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 - 9c47d08f fb10d4b8""") -_r = long_converter(""" - ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b - bfd25e8c d0364141""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP256k1 = Curve("SECP256k1", curve, generator, - (1, 3, 132, 0, 10), "secp256k1") + ### Named curves -_p = long_converter(""" - ffffffff 00000001 00000000 00000000 00000000 ffffffff - ffffffff ffffffff""") -_a = -3 % _p -_b = long_converter(""" - 5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 - 3bce3c3e 27d2604b""") -_Gx = long_converter(""" - 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 - f4a13945 d898c296""") -_Gy = long_converter(""" - 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece - cbb64068 37bf51f5""") -_r = long_converter(""" - ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 - f3b9cac2 fc632551""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP256r1 = Curve("SECP256r1", curve, generator, - (1, 2, 840, 10045, 3, 1, 7), "prime256v1") + # We always provide _a as a positive integer. -_p = long_converter(""" - ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff - ffffffff fffffffe ffffffff 00000000 00000000 ffffffff""") -_a = -3 % _p -_b = long_converter(""" - b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 - 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef""") -_Gx = long_converter(""" - aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 - 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7""") -_Gy = long_converter(""" - 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c - e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f""") -_r = long_converter(""" - ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff - c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP384r1 = Curve("SECP384r1", curve, generator, - (1, 3, 132, 0, 34), "secp384r1") +# _p = long_converter(""" +# ffffffff ffffffff ffffffff fffffffe ffffac73""") +# _a = 0 +# _b = 7 +# _Gx = long_converter(""" +# 3b4c382c e37aa192 a4019e76 3036f4f5 dd4d7ebb""") +# _Gy = long_converter(""" +# 938cf935 318fdced 6bc28286 531733c3 f03c4fee""") +# _r = long_converter("""01 +# 00000000 00000000 0001b8fa 16dfab9a ca16b6b3""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160k1 = Curve("SECP160k1", curve, generator, +# (1, 3, 132, 0, 9), "secp160k1") -_p = long_converter(""" - 01ff ffffffff ffffffff ffffffff ffffffff ffffffff - ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff - ffffffff ffffffff ffffffff ffffffff ffffffff""") -_a = -3 % _p -_b = long_converter(""" - 0051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b - 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd - 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00""") -_Gx = long_converter(""" - 00c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 - 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 - a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66""") -_Gy = long_converter(""" - 0118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 - 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 - 3fad0761 353c7086 a272c240 88be9476 9fd16650""") -_r = long_converter(""" - 01ff ffffffff ffffffff ffffffff ffffffff ffffffff - ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 - f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -SECP521r1 = Curve("SECP521r1", curve, generator, - (1, 3, 132, 0, 35), "secp521r1") +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff 7fffffff""") +# _a = -3 % _p +# _b = long_converter(""" +# 1c97befc 54bd7a8b 65acf89f 81d4d4ad c565fa45""") +# _Gx = long_converter(""" +# 4a96b568 8ef57328 46646989 68c38bb9 13cbfc82""") +# _Gy = long_converter(""" +# 23a62855 3168947d 59dcc912 04235137 7ac5fb32""") +# _r = long_converter("""01 +# 00000000 00000000 0001f4c8 f927aed3 ca752257""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160r1 = Curve("SECP160r1", curve, generator, +# (1, 3, 132, 0, 8), "secp160r1") -_p = long_converter(""" - A9FB57DB A1EEA9BC 3E660A90 9D838D72 6E3BF623 D5262028 - 2013481D 1F6E5377""") -_a = long_converter(""" - 7D5A0975 FC2C3057 EEF67530 417AFFE7 FB8055C1 26DC5C6C - E94A4B44 F330B5D9""") -_b = long_converter(""" - 26DC5C6C E94A4B44 F330B5D9 BBD77CBF 95841629 5CF7E1CE - 6BCCDC18 FF8C07B6""") -_Gx = long_converter(""" - 8BD2AEB9 CB7E57CB 2C4B482F FC81B7AF B9DE27E1 E3BD23C2 - 3A4453BD 9ACE3262""") -_Gy = long_converter(""" - 547EF835 C3DAC4FD 97F8461A 14611DC9 C2774513 2DED8E54 - 5C1D54C7 2F046997""") -_r = long_converter(""" - A9FB57DB A1EEA9BC 3E660A90 9D838D71 8C397AA3 B561A6F7 - 901E0E82 974856A7""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -BRNP256r1 = Curve("BRNP256r1", curve, generator, - (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") +# _p = long_converter(""" +# ffffffff ffffffff ffffffff fffffffe ffffac73""") +# _a = -3 % _p +# _b = long_converter(""" +# b4e134d3 fb59eb8b ab572749 04664d5a f50388ba""") +# _Gx = long_converter(""" +# 52dcb034 293a117e 1f4ff11b 30f7199d 3144ce6d""") +# _Gy = long_converter(""" +# feaffef2 e331f296 e071fa0d f9982cfe a7d43f2e""") +# _r = long_converter("""01 +# 00000000 00000000 0000351e e786a818 f3a1a16b""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160r2 = Curve("SECP160r2", curve, generator, +# (1, 3, 132, 0, 30), "secp160r2") -_p = long_converter(""" - 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B4 - 12B1DA19 7FB71123 ACD3A729 901D1A71 87470013 3107EC53""") -_a = long_converter(""" - 7BC382C6 3D8C150C 3C72080A CE05AFA0 C2BEA28E 4FB22787 - 139165EF BA91F90F 8AA5814A 503AD4EB 04A8C7DD 22CE2826""") -_b = long_converter(""" - 04A8C7DD 22CE2826 8B39B554 16F0447C 2FB77DE1 07DCD2A6 - 2E880EA5 3EEB62D5 7CB43902 95DBC994 3AB78696 FA504C11""") -_Gx = long_converter(""" - 1D1C64F0 68CF45FF A2A63A81 B7C13F6B 8847A3E7 7EF14FE3 - DB7FCAFE 0CBD10E8 E826E034 36D646AA EF87B2E2 47D4AF1E""") -_Gy = long_converter(""" - 8ABE1D75 20F9C2A4 5CB1EB8E 95CFD552 62B70B29 FEEC5864 - E19C054F F9912928 0E464621 77918111 42820341 263C5315""") -_r = long_converter(""" - 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B3 - 1F166E6C AC0425A7 CF3AB6AF 6B7FC310 3B883202 E9046565""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -BRNP384r1 = Curve("BRNP384r1", curve, generator, - (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff fffffffe ffffee37""") +# _a = 0 +# _b = 3 +# _Gx = long_converter(""" +# db4ff10e c057e9ae 26b07d02 80b7f434 1da5d1b1 eae06c7d""") +# _Gy = long_converter(""" +# 9b2f2f6d 9c5628a7 844163d0 15be8634 4082aa88 d95e2f9d""") +# _r = long_converter(""" +# ffffffff ffffffff fffffffe 26f2fc17 0f69466a 74defd8d""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP192k1 = Curve("SECP192k1", curve, generator, +# (1, 3, 132, 0, 31), "secp192k1") -_p = long_converter(""" - AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E - D6639CCA 70330871 7D4D9B00 9BC66842 AECDA12A E6A380E6 - 2881FF2F 2D82C685 28AA6056 583A48F3""") -_a = long_converter(""" - 7830A331 8B603B89 E2327145 AC234CC5 94CBDD8D 3DF91610 - A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 8B9AC8B5 - 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA""") -_b = long_converter(""" - 3DF91610 A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 - 8B9AC8B5 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA DC083E67 - 984050B7 5EBAE5DD 2809BD63 8016F723""") -_Gx = long_converter(""" - 81AEE4BD D82ED964 5A21322E 9C4C6A93 85ED9F70 B5D916C1 - B43B62EE F4D0098E FF3B1F78 E2D0D48D 50D1687B 93B97D5F - 7C6D5047 406A5E68 8B352209 BCB9F822""") -_Gy = long_converter(""" - 7DDE385D 566332EC C0EABFA9 CF7822FD F209F700 24A57B1A - A000C55B 881F8111 B2DCDE49 4A5F485E 5BCA4BD8 8A2763AE - D1CA2B2F A8F05406 78CD1E0F 3AD80892""") -_r = long_converter(""" - AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E - D6639CCA 70330870 553E5C41 4CA92619 41866119 7FAC1047 - 1DB1D381 085DDADD B5879682 9CA90069""") -curve = CurveFp(_p, _a, _b) -generator = Point(curve, _Gx, _Gy, _r) -BRNP512r1 = Curve("BRNP512r1", curve, generator, - (1, 3, 36, 3, 3, 2, 8, 1, 1, 13), "brainpoolP512r1") +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe +# ffffe56d""") +# _a = 0 +# _b = 5 +# _Gx = long_converter(""" +# a1455b33 4df099df 30fc28a1 69a467e9 e47075a9 0f7e650e +# b6b7a45c""") +# _Gy = long_converter(""" +# 7e089fed 7fba3442 82cafbd6 f7e319f7 c0b0bd59 e2ca4bdb +# 556d61a5""") +# _r = long_converter("""01 +# 00000000 00000000 00000000 0001dce8 d2ec6184 caf0a971 +# 769fb1f7""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP224k1 = Curve("SECP224k1", curve, generator, +# (1, 3, 132, 0, 32), "secp224k1") -# we use IANA identifiers below -named_curves = { 15: SECP160k1, - 16: SECP160r1, - 17: SECP160r2, - 18: SECP192k1, - 19: SECP192r1, - 20: SECP224k1, - 21: SECP224r1, - 22: SECP256k1, - 23: SECP256r1, - 24: SECP384r1, - 25: SECP521r1, - 26: BRNP256r1, - 27: BRNP384r1, - 28: BRNP512r1 - } +# _p = long_converter(""" +# A9FB57DB A1EEA9BC 3E660A90 9D838D72 6E3BF623 D5262028 +# 2013481D 1F6E5377""") +# _a = long_converter(""" +# 7D5A0975 FC2C3057 EEF67530 417AFFE7 FB8055C1 26DC5C6C +# E94A4B44 F330B5D9""") +# _b = long_converter(""" +# 26DC5C6C E94A4B44 F330B5D9 BBD77CBF 95841629 5CF7E1CE +# 6BCCDC18 FF8C07B6""") +# _Gx = long_converter(""" +# 8BD2AEB9 CB7E57CB 2C4B482F FC81B7AF B9DE27E1 E3BD23C2 +# 3A4453BD 9ACE3262""") +# _Gy = long_converter(""" +# 547EF835 C3DAC4FD 97F8461A 14611DC9 C2774513 2DED8E54 +# 5C1D54C7 2F046997""") +# _r = long_converter(""" +# A9FB57DB A1EEA9BC 3E660A90 9D838D71 8C397AA3 B561A6F7 +# 901E0E82 974856A7""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP256r1 = Curve("BRNP256r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") -for cid, c in named_curves.iteritems(): - c.curve_id = cid +# _p = long_converter(""" +# 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B4 +# 12B1DA19 7FB71123 ACD3A729 901D1A71 87470013 3107EC53""") +# _a = long_converter(""" +# 7BC382C6 3D8C150C 3C72080A CE05AFA0 C2BEA28E 4FB22787 +# 139165EF BA91F90F 8AA5814A 503AD4EB 04A8C7DD 22CE2826""") +# _b = long_converter(""" +# 04A8C7DD 22CE2826 8B39B554 16F0447C 2FB77DE1 07DCD2A6 +# 2E880EA5 3EEB62D5 7CB43902 95DBC994 3AB78696 FA504C11""") +# _Gx = long_converter(""" +# 1D1C64F0 68CF45FF A2A63A81 B7C13F6B 8847A3E7 7EF14FE3 +# DB7FCAFE 0CBD10E8 E826E034 36D646AA EF87B2E2 47D4AF1E""") +# _Gy = long_converter(""" +# 8ABE1D75 20F9C2A4 5CB1EB8E 95CFD552 62B70B29 FEEC5864 +# E19C054F F9912928 0E464621 77918111 42820341 263C5315""") +# _r = long_converter(""" +# 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B3 +# 1F166E6C AC0425A7 CF3AB6AF 6B7FC310 3B883202 E9046565""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP384r1 = Curve("BRNP384r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") -# replace/fill previous named curves -import ecdsa.curves -ecdsa.curves.curves = named_curves.values() +# _p = long_converter(""" +# AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E +# D6639CCA 70330871 7D4D9B00 9BC66842 AECDA12A E6A380E6 +# 2881FF2F 2D82C685 28AA6056 583A48F3""") +# _a = long_converter(""" +# 7830A331 8B603B89 E2327145 AC234CC5 94CBDD8D 3DF91610 +# A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 8B9AC8B5 +# 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA""") +# _b = long_converter(""" +# 3DF91610 A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 +# 8B9AC8B5 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA DC083E67 +# 984050B7 5EBAE5DD 2809BD63 8016F723""") +# _Gx = long_converter(""" +# 81AEE4BD D82ED964 5A21322E 9C4C6A93 85ED9F70 B5D916C1 +# B43B62EE F4D0098E FF3B1F78 E2D0D48D 50D1687B 93B97D5F +# 7C6D5047 406A5E68 8B352209 BCB9F822""") +# _Gy = long_converter(""" +# 7DDE385D 566332EC C0EABFA9 CF7822FD F209F700 24A57B1A +# A000C55B 881F8111 B2DCDE49 4A5F485E 5BCA4BD8 8A2763AE +# D1CA2B2F A8F05406 78CD1E0F 3AD80892""") +# _r = long_converter(""" +# AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E +# D6639CCA 70330870 553E5C41 4CA92619 41866119 7FAC1047 +# 1DB1D381 085DDADD B5879682 9CA90069""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP512r1 = Curve("BRNP512r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 13), "brainpoolP512r1") diff --git a/scapy/layers/tls/crypto/ffdh.py b/scapy/layers/tls/crypto/ffdh.py new file mode 100644 index 0000000000000000000000000000000000000000..7451c96e52788284fc101ecf1b149038a66d9657 --- /dev/null +++ b/scapy/layers/tls/crypto/ffdh.py @@ -0,0 +1,299 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +This is a register for DH groups from RFC 3526 and RFC 4306. +XXX These groups (and the ones from RFC 7919) should be registered to +the cryptography library. And this file should eventually be removed. +""" + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import dh + +from scapy.utils import long_converter + + +class modp768: # From RFC 4306 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A63A3620 FFFFFFFF FFFFFFFF""") + mLen = 768 + +class modp1024: # From RFC 4306 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 + 49286651 ECE65381 FFFFFFFF FFFFFFFF""") + mLen = 1024 + +class modp1536: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF""") + mLen = 1536 + +class modp2048: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF""") + mLen = 2048 + +class modp3072: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF""") + mLen = 3072 + +class modp4096: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 + 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA + 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 + 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED + 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 + 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199 + FFFFFFFF FFFFFFFF""") + mLen = 4096 + +class modp6144: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 + 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 + FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C + 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 + 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D + 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D + B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 + 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC + E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 + 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB + 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 + 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 + D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 + 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406 + AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918 + DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151 + 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03 + F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F + BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA + CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B + B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632 + 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E + 6DCC4024 FFFFFFFF FFFFFFFF""") + mLen = 6144 + +class modp8192: # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 + 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA + 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 + 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED + 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 + 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 + 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD + F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831 + 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B + DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF + 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6 + D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3 + 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA + CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 + 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C + DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE + 12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4 + 38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300 + 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568 + 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9 + 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B + 4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A + 062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36 + 4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1 + B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92 + 4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47 + 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71 + 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF""") + mLen = 8192 + +_ffdh_raw_params = { 'modp768' : modp768, + 'modp1024': modp1024, + 'modp1536': modp1536, + 'modp2048': modp2048, + 'modp3072': modp3072, + 'modp4096': modp4096, + 'modp6144': modp6144, + 'modp8192': modp8192 } + +FFDH_GROUPS = {} +for name, group in _ffdh_raw_params.iteritems(): + pn = dh.DHParameterNumbers(group.m, group.g) + params = pn.parameters(default_backend()) + FFDH_GROUPS[name] = [params, group.mLen] + + +#from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip, pkcs_i2osp +# +# +#class FFDHParams(object): +# """ +# Finite-Field Diffie-Hellman parameters. +# self.priv is an integer. Its value may remain unknown. +# self.pub, self.other_pub, and finally self.secret, are also integers. +# Default group parameters relate to the 2048-bit group from RFC 3526. +# """ +# def __init__(self, g=ffdh_params[2048].g, +# m=ffdh_params[2048].m, +# mLen=ffdh_params[2048].mLen): +# """ +# g: group (2, 5, ...). Can be provided as a string or long. +# m: prime modulus. Can be provided as a string or long. +# mLen: prime modulus length in bits. +# """ +# if type(g) is str: +# g = pkcs_os2ip(g) +# if type(m) is str: +# m = pkcs_os2ip(m) +# +# self.g = long(g) +# self.m = long(m) +# self.mLen = mLen +# +# self.priv = None +# self.pub = None +# self.other_pub = None +# self.secret = None +# +# def gen_public_params(self): +# """ +# Generate FFDH public parameter, by choosing a random private +# value in ] 0, p-1 [ and then exponentiating the generator of +# the group with the private value. The public parameter is +# returned as an octet string. The private parameter is internally +# available for further secret generation (using .gen_secret()). +# +# Note that 'secret' and 'other_pub' attribute of the instance +# are reset by the call. +# """ +# self.other_pub = None +# self.secret = None +# +# # Private key generation : 0 < x < p-1 +# x = random.randint(1, self.m-2) +# self.priv = x +# +# # Exponentiation +# y = pow(self.g, self.priv, self.m) +# self.pub = y +# +# # Integer-to-octet-string conversion +# y = pkcs_i2osp(y, self.mLen/8) +# +# return y +# +# def gen_secret(self, other_pub): +# """ +# Given the peer's public value 'other_pub' provided as an octet string, +# the shared secret is computed by exponentiating the value using +# internally stored private value (self.priv, generated during +# public_parameter generation using .gen_public_params()). +# +# Computed secret is returned as a bitstring and stored internally. +# +# No specific check is done on 'other_pub' before exponentiation. +# """ +# if type(other_pub) is str: +# other_pub = pkcs_os2ip(other_pub) +# +# # Octet-string-to-integer conversion +# self.other_pub = other_pub +# +# # Exponentiation +# z = pow(other_pub, self.priv, self.m) +# +# # Integer-to-octet-string conversion +# z = pkcs_i2osp(z, self.mLen/8) +# self.secret = z +# +# return z +# +# def check_params(self): +# #XXX Do me, maybe +# pass + diff --git a/scapy/layers/tls/crypto/h_mac.py b/scapy/layers/tls/crypto/h_mac.py new file mode 100644 index 0000000000000000000000000000000000000000..6be3803b9c41b80050d7a2f5561d0415deddf6dd --- /dev/null +++ b/scapy/layers/tls/crypto/h_mac.py @@ -0,0 +1,107 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +HMAC classes. +""" + +import hmac + +from scapy.layers.tls.crypto.hash import tls_hash_algs + + +SSLv3_PAD1_MD5 = "\x36"*48 +SSLv3_PAD1_SHA1 = "\x36"*40 +SSLv3_PAD2_MD5 = "\x5c"*48 +SSLv3_PAD2_SHA1 = "\x5c"*40 + +tls_hmac_algs = {} + +class _GenericHMACMetaclass(type): + """ + HMAC classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + + Note that, when used with TLS, the HMAC key length equates the output of + the associated hash function (see RFC 5246, appendix C). + Also, we do not need to instantiate the associated hash function. + """ + def __new__(cls, hmac_name, bases, dct): + hash_name = hmac_name[5:] # remove leading "Hmac_" + if hmac_name != "_GenericHMAC": + dct["name"] = "HMAC-%s" % hash_name + dct["hash_alg"] = tls_hash_algs[hash_name] + dct["hmac_len"] = tls_hash_algs[hash_name].hash_len + dct["key_len"] = dct["hmac_len"] + the_class = super(_GenericHMACMetaclass, cls).__new__(cls, hmac_name, + bases, dct) + if hmac_name != "_GenericHMAC": + tls_hmac_algs[dct["name"]] = the_class + return the_class + + +class HMACError(Exception): + """ + Raised when HMAC verification fails. + """ + pass + +class _GenericHMAC(object): + __metaclass__ = _GenericHMACMetaclass + + def __init__(self, key=None): + self.key = key + + def digest(self, tbd): + if self.key is None: + raise HMACError + return hmac.new(self.key, tbd, self.hash_alg.hash_cls).digest() + + def digest_sslv3(self, tbd): + if self.key is None: + raise HMACError + + h = self.hash_alg() + if h.name == "SHA": + pad1 = SSLv3_PAD1_SHA1 + pad2 = SSLv3_PAD2_SHA1 + elif h.name == "MD5": + pad1 = SSLv3_PAD1_MD5 + pad2 = SSLv3_PAD2_MD5 + else: + raise HMACError("Provided hash does not work with SSLv3.") + + return h.digest(self.key + pad2 + + h.digest(self.key + pad1 + tbd)) + + +class Hmac_NULL(_GenericHMAC): + hmac_len = 0 + key_len = 0 + + def digest(self, tbd): + return "" + + def digest_sslv3(self, tbd): + return "" + +class Hmac_MD5(_GenericHMAC): + pass + +class Hmac_SHA(_GenericHMAC): + pass + +class Hmac_SHA224(_GenericHMAC): + pass + +class Hmac_SHA256(_GenericHMAC): + pass + +class Hmac_SHA384(_GenericHMAC): + pass + +class Hmac_SHA512(_GenericHMAC): + pass + diff --git a/scapy/layers/tls/crypto/hash.py b/scapy/layers/tls/crypto/hash.py new file mode 100644 index 0000000000000000000000000000000000000000..de735788f41f90181679e059d76badcb3c3edb78 --- /dev/null +++ b/scapy/layers/tls/crypto/hash.py @@ -0,0 +1,66 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Hash classes. +""" + +from hashlib import md5, sha1, sha224, sha256, sha384, sha512 + + +tls_hash_algs = {} + +class _GenericHashMetaclass(type): + """ + Hash classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, hash_name, bases, dct): + if hash_name != "_GenericHash": + dct["name"] = hash_name[5:] # remove leading "Hash_" + the_class = super(_GenericHashMetaclass, cls).__new__(cls, hash_name, + bases, dct) + if hash_name != "_GenericHash": + tls_hash_algs[hash_name[5:]] = the_class + return the_class + + +class _GenericHash(object): + __metaclass__ = _GenericHashMetaclass + + def digest(self, tbd): + return self.hash_cls(tbd).digest() + + +class Hash_NULL(_GenericHash): + hash_len = 0 + + def digest(self, tbd): + return "" + +class Hash_MD5(_GenericHash): + hash_cls = md5 + hash_len = 16 + +class Hash_SHA(_GenericHash): + hash_cls = sha1 + hash_len = 20 + +class Hash_SHA224(_GenericHash): + hash_cls = sha224 + hash_len = 28 + +class Hash_SHA256(_GenericHash): + hash_cls = sha256 + hash_len = 32 + +class Hash_SHA384(_GenericHash): + hash_cls = sha384 + hash_len = 48 + +class Hash_SHA512(_GenericHash): + hash_cls = sha512 + hash_len = 64 + diff --git a/scapy/layers/tls/crypto/kx_algs.py b/scapy/layers/tls/crypto/kx_algs.py new file mode 100644 index 0000000000000000000000000000000000000000..501e22ff1aad865d5a085b1eb8cd8ad507059946 --- /dev/null +++ b/scapy/layers/tls/crypto/kx_algs.py @@ -0,0 +1,179 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Key Exchange algorithms as listed in appendix C of RFC 4346. + +XXX Incomplete support for static DH, DSS, PSK, SRP, KRB and anonymous kx. +""" + +from scapy.layers.tls.keyexchange import (ServerDHParams, + ServerRSAParams, + ClientDiffieHellmanPublic, + ClientECDiffieHellmanPublic, + _tls_server_ecdh_cls_guess, + EncryptedPreMasterSecret) + + +tls_kx_algs = {} + +class _GenericKXMetaclass(type): + """ + We could try to set server_kx_msg and client_kx_msg while parsing + the class name... :) + """ + def __new__(cls, kx_name, bases, dct): + if kx_name != "_GenericKX": + dct["name"] = kx_name[3:] # remove leading "KX_" + the_class = super(_GenericKXMetaclass, cls).__new__(cls, kx_name, + bases, dct) + if kx_name: + the_class.export = kx_name.endswith("_EXPORT") + the_class.anonymous = "_anon_" in kx_name + the_class.no_ske = not ("DHE" in kx_name or "_anon_" in kx_name) + the_class.no_ske &= not the_class.export + tls_kx_algs[kx_name[3:]] = the_class + return the_class + + +class _GenericKX: + __metaclass__ = _GenericKXMetaclass + + +class KX_NULL(_GenericKX): + descr = "No key exchange" + server_kx_msg_cls = lambda _,m: None + client_kx_msg_cls = None + + +### Standard RSA-authenticated key exchange + +class KX_RSA(_GenericKX): + descr = "RSA encryption" + server_kx_msg_cls = lambda _,m: None + client_kx_msg_cls = EncryptedPreMasterSecret + +#class KX_DH_RSA(_GenericKX): +# descr = "DH with RSA-based certificates" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + +class KX_DHE_RSA(_GenericKX): + descr = "Ephemeral DH with RSA signature" + server_kx_msg_cls = lambda _,m: ServerDHParams + client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_ECDH_RSA(_GenericKX): +# descr = "ECDH RSA key exchange" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + +class KX_ECDHE_RSA(_GenericKX): + descr = "Ephemeral ECDH with RSA signature" + server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) + client_kx_msg_cls = ClientECDiffieHellmanPublic + +class KX_RSA_EXPORT(KX_RSA): + descr = "RSA encryption, export version" + server_kx_msg_cls = lambda _,m: ServerRSAParams + +#class KX_DH_RSA_EXPORT(KX_DH_RSA): +# descr = "DH with RSA-based certificates - Export version" + +class KX_DHE_RSA_EXPORT(KX_DHE_RSA): + descr = "Ephemeral DH with RSA signature, export version" + + +### Standard ECDSA-authenticated key exchange + +# class KX_ECDH_ECDSA(_GenericKX): +# descr = "ECDH ECDSA key exchange" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + +class KX_ECDHE_ECDSA(_GenericKX): + descr = "Ephemeral ECDH with ECDSA signature" + server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) + client_kx_msg_cls = ClientECDiffieHellmanPublic + + +### Classes below are offered without any guarantee. +### They may offer some parsing capabilities, +### but surely won't be able to handle a proper TLS negotiation. +### Uncomment them at your own risk. + +### Standard DSS-authenticated key exchange + +# class KX_DH_DSS(_GenericKX): +# descr = "DH with DSS-based certificates" +# server_kx_msg_cls = lambda _,m: ServerDHParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +#class KX_DHE_DSS(_GenericKX): +# descr = "Ephemeral DH with DSS signature" +# server_kx_msg_cls = lambda _,m: ServerDHParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_DH_DSS_EXPORT(KX_DH_DSS): +# descr = "DH with DSS-based certificates - Export version" + +#class KX_DHE_DSS_EXPORT(KX_DHE_DSS): +# descr = "Ephemeral DH with DSS signature, export version" + + +### PSK-based key exchange + +# class KX_PSK(_GenericKX): # RFC 4279 +# descr = "PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = None + +# class KX_RSA_PSK(_GenericKX): # RFC 4279 +# descr = "RSA PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = None + +# class KX_DHE_PSK(_GenericKX): # RFC 4279 +# descr = "Ephemeral DH with PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_ECDHE_PSK(_GenericKX): # RFC 5489 +# descr = "Ephemeral ECDH PSK key exchange" +# server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) +# client_kx_msg_cls = ClientDiffieHellmanPublic + + +### SRP-based key exchange + +# + + +### Kerberos-based key exchange + +# class KX_KRB5(_GenericKX): +# descr = "Kerberos 5 key exchange" +# server_kx_msg_cls = lambda _,m: None # No SKE with kerberos +# client_kx_msg_cls = None + +# class KX_KRB5_EXPORT(KX_KRB5): +# descr = "Kerberos 5 key exchange - Export version" + + +### Unauthenticated key exchange (opportunistic encryption) + +# class KX_DH_anon(_GenericKX): +# descr = "Anonymous DH, no signatures" +# server_kx_msg_cls = lambda _,m: ServerDHParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_ECDH_anon(_GenericKX): +# descr = "ECDH anonymous key exchange" +# server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_DH_anon_EXPORT(KX_DH_anon): +# descr = "Anonymous DH, no signatures - Export version" + diff --git a/scapy/layers/tls/crypto/pkcs1.py b/scapy/layers/tls/crypto/pkcs1.py index 956268e79adaa7078a8f00da58365df2b7e7c015..e4b49c4104730f21f05acebae714e50699ac6986 100644 --- a/scapy/layers/tls/crypto/pkcs1.py +++ b/scapy/layers/tls/crypto/pkcs1.py @@ -5,6 +5,10 @@ """ PKCS #1 methods as defined in RFC 3447. + +XXX We cannot rely solely on the cryptography library, because it does not +support our "tls" hash used with TLS 1.0. Once it is added to (or from) the +library, most of the present module should be removed. """ import os, popen2, tempfile @@ -19,42 +23,14 @@ if conf.crypto_valid: else: InvalidSignature = dafault_backend = hashes = padding = None +from scapy.utils import randstring, zerofree_randstring, strxor, strand +from scapy.error import warning + ##################################################################### # Some helpers ##################################################################### -def _warning(m): - print "WARNING: %s" % m - -def randstring(l): - """ - Returns a random string of length l (l >= 0) - """ - tmp = map(lambda x: struct.pack("B", random.randrange(0, 256, 1)), [""]*l) - return "".join(tmp) - -def zerofree_randstring(l): - """ - Returns a random string of length l (l >= 0) without zero in it. - """ - tmp = map(lambda x: struct.pack("B", random.randrange(1, 256, 1)), [""]*l) - return "".join(tmp) - -def strxor(s1, s2): - """ - Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 - must be of same length. - """ - return "".join(map(lambda x,y:chr(ord(x)^ord(y)), s1, s2)) - -def strand(s1, s2): - """ - Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 - must be of same length. - """ - return "".join(map(lambda x,y:chr(ord(x)&ord(y)), s1, s2)) - # OS2IP function defined in RFC 3447 for octet string to integer conversion def pkcs_os2ip(x): """ @@ -100,24 +76,27 @@ def pkcs_ilen(n): i += 1 return i -# for every hash function a tuple is provided, giving access to + +##################################################################### +# Hash functions +##################################################################### + +# For every hash function a tuple is provided, giving access to # - hash output length in byte # - associated hash function that take data to be hashed as parameter # XXX I do not provide update() at the moment. # - DER encoding of the leading bits of digestInfo (the hash value # will be concatenated to create the complete digestInfo). # -# Notes: -# - MD4 asn.1 value should be verified. Also, as stated in -# PKCS#1 v2.1, MD4 should not be used. -# - 'tls' one is the concatenation of both md5 and sha1 hashes used -# by SSL/TLS when signing/verifying things +# Note that 'tls' is the concatenation of both md5 and sha1 hashes used by +# SSL/TLS 1.0 when signing/verifying things. _hashFuncParams = {} if conf.crypto_valid: def _hashWrapper(hash_algo, message, backend=default_backend()): - digest = hashes.Hash(hash_algo, backend).update(message) + digest = hashes.Hash(hash_algo(), backend) + digest.update(message) return digest.finalize() _hashFuncParams = { @@ -152,12 +131,18 @@ if conf.crypto_valid: } def mapHashFunc(hashStr): + if hashStr == "tls": + raise Exception("mapHashFunc is not supposed to be called on 'tls'") try: - return _hashFuncParams[hashStr][1] + return _hashFuncParams[hashStr][1]() except: raise Exception("Unknown hash function %s" % hashStr) +##################################################################### +# Some more PKCS helpers +##################################################################### + def pkcs_mgf1(mgfSeed, maskLen, h): """ Implements generic MGF1 Mask Generation function as described in @@ -179,12 +164,12 @@ def pkcs_mgf1(mgfSeed, maskLen, h): # steps are those of Appendix B.2.1 if not _hashFuncParams.has_key(h): - _warning("pkcs_mgf1: invalid hash (%s) provided" % h) + warning("pkcs_mgf1: invalid hash (%s) provided" % h) return None hLen = _hashFuncParams[h][0] hFunc = _hashFuncParams[h][2] if maskLen > 2**32 * hLen: # 1) - _warning("pkcs_mgf1: maskLen > 2**32 * hLen") + warning("pkcs_mgf1: maskLen > 2**32 * hLen") return None T = "" # 2) maxCounter = math.ceil(float(maskLen) / float(hLen)) # 3) @@ -222,7 +207,7 @@ def pkcs_emsa_pss_encode(M, emBits, h, mgf, sLen): mHash = hFunc(M) emLen = int(math.ceil(emBits/8.)) if emLen < hLen + sLen + 2: # 3) - _warning("encoding error (emLen < hLen + sLen + 2)") + warning("encoding error (emLen < hLen + sLen + 2)") return None salt = randstring(sLen) # 4) MPrime = '\x00'*8 + mHash + salt # 5) @@ -328,7 +313,7 @@ def pkcs_emsa_pkcs1_v1_5_encode(M, emLen, h): # section 9.2 of RFC 3447 T = hLeadingDigestInfo + H tLen = len(T) if emLen < tLen + 11: # 3) - _warning("pkcs_emsa_pkcs1_v1_5_encode:" + warning("pkcs_emsa_pkcs1_v1_5_encode:" "intended encoded message length too short") return None PS = '\xff'*(emLen - tLen - 3) # 4) @@ -336,165 +321,166 @@ def pkcs_emsa_pkcs1_v1_5_encode(M, emLen, h): # section 9.2 of RFC 3447 return EM # 6) -# XXX should add other pgf1 instance in a better fashion. +##################################################################### +# Asymmetric Cryptography wrappers +##################################################################### -def create_ca_file(anchor_list, filename): - """ - Concatenate all the certificates (PEM format for the export) in - 'anchor_list' and write the result to file 'filename'. On success - 'filename' is returned, None otherwise. +class _EncryptAndVerifyRSA(object): - If you are used to OpenSSL tools, this function builds a CAfile - that can be used for certificate and CRL check. + def _rsaep(self, m): + """ + Internal method providing raw RSA encryption, i.e. simple modular + exponentiation of the given message representative 'm', a long + between 0 and n-1. - Also see create_temporary_ca_file(). - """ - try: - f = open(filename, "w") - for a in anchor_list: - s = a.output(fmt="PEM") - f.write(s) - f.close() - except: - return None - return filename + This is the encryption primitive RSAEP described in PKCS#1 v2.1, + i.e. RFC 3447 Sect. 5.1.1. -def create_temporary_ca_file(anchor_list): - """ - Concatenate all the certificates (PEM format for the export) in - 'anchor_list' and write the result to file to a temporary file - using mkstemp() from tempfile module. On success 'filename' is - returned, None otherwise. + Input: + m: message representative, a long between 0 and n-1, where + n is the key modulus. - If you are used to OpenSSL tools, this function builds a CAfile - that can be used for certificate and CRL check. - """ - try: - f, fname = tempfile.mkstemp() - for a in anchor_list: - s = a.output(fmt="PEM") - l = os.write(f, s) - os.close(f) - except: - return None - return fname + Output: + ciphertext representative, a long between 0 and n-1 -def create_temporary_ca_path(anchor_list, folder): - """ - Create a CA path folder as defined in OpenSSL terminology, by - storing all certificates in 'anchor_list' list in PEM format - under provided 'folder' and then creating the associated links - using the hash as usually done by c_rehash. + Not intended to be used directly. Please, see encrypt() method. + """ - Note that you can also include CRL in 'anchor_list'. In that - case, they will also be stored under 'folder' and associated - links will be created. + n = self._modulus + if isinstance(m, int): + m = long(m) + if (not isinstance(m, long)) or m > n-1: + warning("Key._rsaep() expects a long between 0 and n-1") + return None - In folder, the files are created with names of the form - 0...ZZ.pem. If you provide an empty list, folder will be created - if it does not already exist, but that's all. + return pow(m, self._pubExp, n) - The number of certificates written to folder is returned on - success, None on error. - """ - # We should probably avoid writing duplicate anchors and also - # check if they are all certs. - try: - if not os.path.isdir(folder): - os.makedirs(folder) - except: - return None - l = len(anchor_list) - if l == 0: - return None - fmtstr = "%%0%sd.pem" % math.ceil(math.log(l, 10)) - i = 0 - try: - for a in anchor_list: - fname = os.path.join(folder, fmtstr % i) - f = open(fname, "w") - s = a.output(fmt="PEM") - f.write(s) - f.close() - i += 1 - except: - return None + @crypto_validator + def encrypt(self, m, t="pkcs", h=None, mgf=None, L=None): + if h == "tls" or t is None: + #return self.encrypt_legacy(m, t=t, h=h, mgf=mgf, L=L) + warning("Cannot call encrypt_legacy anymore.") + return None - r,w=popen2.popen2("c_rehash %s" % folder) - r.close(); w.close() + if h is not None: + h = mapHashFunc(h) - return l + if t == "pkcs": + pad = padding.PKCS1v15() + elif t == "oaep": + pad = padding.OAEP(mgf=mgf(h), algorithm=h, label=L) + else: + warning("Key.encrypt(): Unknown encryption type (%s) provided" % t) + return None + return self.pubkey.encrypt(m, pad) -##################################################################### -# Public Key Cryptography related stuff -##################################################################### + ### Below are verification related methods -class _EncryptAndVerifyRSA(object): - @crypto_validator - def encrypt(self, m, t=None, h=None, mgf=None, L=None): + def _rsavp1(self, s): """ - Encrypt message 'm' using 't' encryption scheme where 't' can be: + Internal method providing raw RSA verification, i.e. simple modular + exponentiation of the given signature representative 'c', an integer + between 0 and n-1. - - None: the message 'm' is directly applied the RSAEP encryption - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 - Sect 5.1.1. Simply put, the message undergo a modular - exponentiation using the public key. Additionnal method - parameters are just ignored. + This is the signature verification primitive RSAVP1 described in + PKCS#1 v2.1, i.e. RFC 3447 Sect. 5.2.2. - -'pkcs': the message 'm' is applied RSAES-PKCS1-V1_5-ENCRYPT encryption - scheme as described in section 7.2.1 of RFC 3447. In that - context, other parameters ('h', 'mgf', 'l') are not used. + Input: + s: signature representative, an integer between 0 and n-1, + where n is the key modulus. - -'oaep': the message 'm' is applied the RSAES-OAEP-ENCRYPT encryption - scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect - 7.1.1. In that context, + Output: + message representative, an integer between 0 and n-1 - 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. + Not intended to be used directly. Please, see verify() method. + """ + return self._rsaep(s) - 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 'L' is the optional label to be associated with the message. - If not provided, the default value is used, i.e the empty - string. No check is done on the input limitation of the hash - function regarding the size of 'L' (for instance, 2^61 - 1 - for SHA-1). You have been warned. + def _rsassa_pss_verify(self, M, S, h=None, mgf=None, sLen=None): """ - if h is not None: - h = mapHashFunc(h) - if t is None: # Raw encryption - return self.key.encrypt( - m, - padding.AsymmetricPadding(), - ) - elif t == "pkcs": - return self.key.encrypt( - m, - padding.PKCS1v15(), - ) + Implements RSASSA-PSS-VERIFY() function described in Sect 8.1.2 + of RFC 3447 - elif t == "oaep": - return self.key.encrypt( - m, - padding.OAEP( - mgf=mgf(h()), - algorithm=h(), - label=L, - ), - ) + Input: + M: message whose signature is to be verified + S: signature to be verified, an octet string of length k, where k + is the length in octets of the RSA modulus n. - else: - _warning("Key.encrypt(): Unknown encryption type (%s) provided" % t) - return None + Output: + True is the signature is valid. False otherwise. + """ - @crypto_validator - def verify(self, M, S, t=None, h=None, mgf=None, sLen=None): + # Set default parameters if not provided + if h is None: # By default, sha1 + h = "sha1" + if not _hashFuncParams.has_key(h): + warning("Key._rsassa_pss_verify(): unknown hash function " + "provided (%s)" % h) + return False + if mgf is None: # use mgf1 with underlying hash function + mgf = lambda x,y: pkcs_mgf1(x, y, h) + if sLen is None: # use Hash output length (A.2.3 of RFC 3447) + hLen = _hashFuncParams[h][0] + sLen = hLen + + # 1) Length checking + modBits = self._modulusLen + k = modBits / 8 + if len(S) != k: + return False + + # 2) RSA verification + s = pkcs_os2ip(S) # 2.a) + m = self._rsavp1(s) # 2.b) + emLen = math.ceil((modBits - 1) / 8.) # 2.c) + EM = pkcs_i2osp(m, emLen) + + # 3) EMSA-PSS verification + result = pkcs_emsa_pss_verify(M, EM, modBits - 1, h, mgf, sLen) + + return result # 4) + + + def _rsassa_pkcs1_v1_5_verify(self, M, S, h): + """ + Implements RSASSA-PKCS1-v1_5-VERIFY() function as described in + Sect. 8.2.2 of RFC 3447. + + Input: + M: message whose signature is to be verified, an octet string + S: signature to be verified, an octet string of length k, where + k is the length in octets of the RSA modulus n + h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). + + Output: + True if the signature is valid. False otherwise. + """ + + # 1) Length checking + k = self._modulusLen / 8 + if len(S) != k: + warning("invalid signature (len(S) != k)") + return False + + # 2) RSA verification + s = pkcs_os2ip(S) # 2.a) + m = self._rsavp1(s) # 2.b) + EM = pkcs_i2osp(m, k) # 2.c) + + # 3) EMSA-PKCS1-v1_5 encoding + EMPrime = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) + if EMPrime is None: + warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.") + return False + + # 4) Comparison + return EM == EMPrime + + + def verify_legacy(self, M, S, t=None, h=None, mgf=None, sLen=None): """ Verify alleged signature 'S' is indeed the signature of message 'M' using 't' signature scheme where 't' can be: @@ -531,33 +517,51 @@ class _EncryptAndVerifyRSA(object): default value (the byte length of the hash value for provided algorithm) by providing another one with that parameter. """ - if h is not None: - h = mapHashFunc(h) - if t is None: # RSAVP1 - pad_inst = padding.AsymmetricPadding() + S = pkcs_os2ip(S) + n = self._modulus + if S > n-1: + warning("Signature to be verified is too long for key modulus") + return False + m = self._rsavp1(S) + if m is None: + return False + l = int(math.ceil(math.log(m, 2) / 8.)) # Hack + m = pkcs_i2osp(m, l) + return M == m elif t == "pkcs": # RSASSA-PKCS1-v1_5-VERIFY if h is None: - h = hashes.SHA1 - pad_inst = padding.PKCS1v15() + h = "sha1" + return self._rsassa_pkcs1_v1_5_verify(M, S, h) elif t == "pss": # RSASSA-PSS-VERIFY - pad_inst = padding.PSS(mgf=mgf, salt_length=sLen) + return self._rsassa_pss_verify(M, S, h, mgf, sLen) + else: + warning("Key.verify(): Unknown signature type (%s) provided" % t) + return None + @crypto_validator + def verify(self, M, S, t="pkcs", h=None, mgf=None, sLen=None): + if h == "tls" or t is None: + return self.verify_legacy(M, S, t=t, h=h, mgf=mgf, sLen=sLen) + + if h is not None: + h = mapHashFunc(h) + + if t == "pkcs": # RSASSA-PKCS1-v1_5-VERIFY + pad = padding.PKCS1v15() + elif t == "pss": # RSASSA-PSS-VERIFY + pad = padding.PSS(mgf=mgf(h), salt_length=sLen) else: - _warning("Key.verify(): Unknown signature type (%s) provided" % t) + warning("Key.verify(): Unknown signature type (%s) provided" % t) return None try: - self.key.verify( - signature=S, - data=M, - padding=pad_inst, - algorithm=h(), - ) + self.pubkey.verify(signature=S, data=M, padding=pad, algorithm=h) return True except InvalidSignature: return False + class _DecryptAndSignRSA(object): ### Below are decryption related methods. Encryption ones are inherited ### from PubKey @@ -581,214 +585,35 @@ class _DecryptAndSignRSA(object): Not intended to be used directly. Please, see decrypt() method. """ - n = self.modulus + n = self._modulus if isinstance(c, int): c = long(c) if (not isinstance(c, long)) or c > n-1: - _warning("Key._rsaep() expects a long between 0 and n-1") - return None - - return self.key.decrypt(c) - - - def _rsaes_pkcs1_v1_5_decrypt(self, C): - """ - Implements RSAES-PKCS1-V1_5-DECRYPT() function described in section - 7.2.2 of RFC 3447. - - Input: - C: ciphertext to be decrypted, an octet string of length k, where - k is the length in octets of the RSA modulus n. - - Output: - an octet string of length k at most k - 11 - - on error, None is returned. - """ - - # 1) Length checking - cLen = len(C) - k = self.modulusLen / 8 - if cLen != k or k < 11: - _warning("Key._rsaes_pkcs1_v1_5_decrypt() decryption error " - "(cLen != k or k < 11)") + warning("Key._rsaep() expects a long between 0 and n-1") return None - # 2) RSA decryption - c = pkcs_os2ip(C) # 2.a) - m = self._rsadp(c) # 2.b) - EM = pkcs_i2osp(m, k) # 2.c) - - # 3) EME-PKCS1-v1_5 decoding + privExp = self.key.private_numbers().d + return pow(c, privExp, n) - # I am aware of the note at the end of 7.2.2 regarding error - # conditions reporting but the one provided below are for _local_ - # debugging purposes. --arno - if EM[0] != '\x00': - _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(first byte is not 0x00)") + def decrypt(self, C, t="pkcs", h=None, mgf=None, L=None): + if h == "tls" or t is None: + #return self.decrypt_legacy(C, t=t, h=h, mgf=mgf, L=L) + warning("Cannot call decrypt_legacy anymore.") return None - if EM[1] != '\x02': - _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(second byte is not 0x02)") - return None - - tmp = EM[2:].split('\x00', 1) - if len(tmp) != 2: - _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(no 0x00 to separate PS from M)") - return None - - PS, M = tmp - if len(PS) < 8: - _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(PS is less than 8 byte long)") - return None - - return M # 4) - - - def _rsaes_oaep_decrypt(self, C, h=None, mgf=None, L=None): - """ - Internal method providing RSAES-OAEP-DECRYPT as defined in Sect. - 7.1.2 of RFC 3447. Not intended to be used directly. Please, see - encrypt() method for type "OAEP". - - - Input: - C : ciphertext to be decrypted, an octet string of length k, where - k = 2*hLen + 2 (k denotes the byte length of the RSA modulus - and hLen the byte length of the hash function output) - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). 'sha1' is used if none is provided. - mgf: the mask generation function f : seed, maskLen -> mask - L : optional label whose association with the message is to be - verified; the default value for L, if not provided is the empty - string. - - Output: - message, an octet string of length k mLen, where mLen <= k-2*hLen-2 - - On error, None is returned. - """ - # The steps below are the one described in Sect. 7.1.2 of RFC 3447. - - # 1) Length Checking - # 1.a) is not done - if h is None: - h = "sha1" - if not _hashFuncParams.has_key(h): - _warning("Key._rsaes_oaep_decrypt(): unknown hash function %s." % h) - return None - hLen = _hashFuncParams[h][0] - hFun = _hashFuncParams[h][2] - k = self.modulusLen / 8 - cLen = len(C) - if cLen != k: # 1.b) - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(cLen != k)") - return None - if k < 2*hLen + 2: - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(k < 2*hLen + 2)") - return None - - # 2) RSA decryption - c = pkcs_os2ip(C) # 2.a) - m = self._rsadp(c) # 2.b) - EM = pkcs_i2osp(m, k) # 2.c) - - # 3) EME-OAEP decoding - if L is None: # 3.a) - L = "" - lHash = hFun(L) - Y = EM[:1] # 3.b) - if Y != '\x00': - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(Y is not zero)") - return None - maskedSeed = EM[1:1+hLen] - maskedDB = EM[1+hLen:] - if mgf is None: - mgf = lambda x,y: pkcs_mgf1(x, y, h) - seedMask = mgf(maskedDB, hLen) # 3.c) - seed = strxor(maskedSeed, seedMask) # 3.d) - dbMask = mgf(seed, k - hLen - 1) # 3.e) - DB = strxor(maskedDB, dbMask) # 3.f) - - # I am aware of the note at the end of 7.1.2 regarding error - # conditions reporting but the one provided below are for _local_ - # debugging purposes. --arno - - lHashPrime = DB[:hLen] # 3.g) - tmp = DB[hLen:].split('\x01', 1) - if len(tmp) != 2: - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(0x01 separator not found)") - return None - PS, M = tmp - if PS != '\x00'*len(PS): - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(invalid padding string)") - return None - if lHash != lHashPrime: - _warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(invalid hash)") - return None - return M # 4) - - - def decrypt(self, C, t=None, h=None, mgf=None, L=None): - """ - Decrypt ciphertext 'C' using 't' decryption scheme where 't' can be: - - - None: the ciphertext 'C' is directly applied the RSADP decryption - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 - Sect 5.1.2. Simply, put the message undergo a modular - exponentiation using the private key. Additionnal method - parameters are just ignored. - - - 'pkcs': the ciphertext 'C' is applied RSAES-PKCS1-V1_5-DECRYPT - decryption scheme as described in section 7.2.2 of RFC 3447. - In that context, other parameters ('h', 'mgf', 'l') are not - used. - - - 'oaep': the ciphertext 'C' is applied the RSAES-OAEP-DECRYPT - decryption scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 - Sect 7.1.2. 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 by default. - - 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 'L' is the optional label to be associated with the - message. If not provided, the default value is used, i.e - the empty string. No check is done on the input limitation - of the hash function regarding the size of 'L' (for - instance, 2^61 - 1 for SHA-1). You have been warned. - """ - if t is None: - C = pkcs_os2ip(C) - c = self._rsadp(C) - l = int(math.ceil(math.log(c, 2) / 8.)) # Hack - return pkcs_i2osp(c, l) - - elif t == "pkcs": - return self._rsaes_pkcs1_v1_5_decrypt(C) + if h is not None: + h = mapHashFunc(h) + if t == "pkcs": + pad = padding.PKCS1v15() elif t == "oaep": - return self._rsaes_oaep_decrypt(C, h, mgf, L) - + pad = padding.OAEP(mgf=mgf(h), algorithm=h, label=L) else: - _warning("Key.decrypt(): Unknown decryption type (%s) provided" % t) + warning("Key.decrypt(): Unknown decryption type (%s) provided" % t) return None + return self.key.decrypt(C, pad) + ### Below are signature related methods. ### Verification methods are inherited from PubKey. @@ -833,7 +658,7 @@ class _DecryptAndSignRSA(object): if h is None: # By default, sha1 h = "sha1" if not _hashFuncParams.has_key(h): - _warning("Key._rsassa_pss_sign(): unknown hash function " + warning("Key._rsassa_pss_sign(): unknown hash function " "provided (%s)" % h) return None if mgf is None: # use mgf1 with underlying hash function @@ -843,11 +668,11 @@ class _DecryptAndSignRSA(object): sLen = hLen # 1) EMSA-PSS encoding - modBits = self.modulusLen + modBits = self._modulusLen k = modBits / 8 EM = pkcs_emsa_pss_encode(M, modBits - 1, h, mgf, sLen) if EM is None: - _warning("Key._rsassa_pss_sign(): unable to encode") + warning("Key._rsassa_pss_sign(): unable to encode") return None # 2) RSA signature @@ -873,10 +698,10 @@ class _DecryptAndSignRSA(object): """ # 1) EMSA-PKCS1-v1_5 encoding - k = self.modulusLen / 8 + k = self._modulusLen / 8 EM = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) if EM is None: - _warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") + warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") return None # 2) RSA signature @@ -887,7 +712,7 @@ class _DecryptAndSignRSA(object): return S # 3) - def sign(self, M, t=None, h=None, mgf=None, sLen=None): + def sign_legacy(self, M, t=None, h=None, mgf=None, sLen=None): """ Sign message 'M' using 't' signature scheme where 't' can be: @@ -920,28 +745,133 @@ class _DecryptAndSignRSA(object): default value (the byte length of the hash value for provided algorithm) by providing another one with that parameter. """ - if t is None: # RSASP1 M = pkcs_os2ip(M) - n = self.modulus + n = self._modulus if M > n-1: - _warning("Message to be signed is too long for key modulus") + warning("Message to be signed is too long for key modulus") return None s = self._rsasp1(M) if s is None: return None - return pkcs_i2osp(s, self.modulusLen/8) - + return pkcs_i2osp(s, self._modulusLen/8) elif t == "pkcs": # RSASSA-PKCS1-v1_5-SIGN if h is None: h = "sha1" return self._rsassa_pkcs1_v1_5_sign(M, h) - elif t == "pss": # RSASSA-PSS-SIGN return self._rsassa_pss_sign(M, h, mgf, sLen) + else: + warning("Key.sign(): Unknown signature type (%s) provided" % t) + return None + def sign(self, M, t="pkcs", h=None, mgf=None, sLen=None): + if h == "tls" or t is None: + return self.sign_legacy(M, t=t, h=h, mgf=mgf, sLen=sLen) + + if h is not None: + h = mapHashFunc(h) + + if t == "pkcs": # RSASSA-PKCS1-v1_5-SIGN + pad = padding.PKCS1v15() + elif t == "pss": # RSASSA-PSS-SIGN + pad = padding.PSS(mgf=mgf(h), salt_length=sLen) else: - _warning("Key.sign(): Unknown signature type (%s) provided" % t) + warning("Key.sign(): Unknown signature type (%s) provided" % t) return None + return self.key.sign(M, pad, h) + + + +##################################################################### +# CA files helpers +##################################################################### +def create_ca_file(anchor_list, filename): + """ + Concatenate all the certificates (PEM format for the export) in + 'anchor_list' and write the result to file 'filename'. On success + 'filename' is returned, None otherwise. + + If you are used to OpenSSL tools, this function builds a CAfile + that can be used for certificate and CRL check. + + Also see create_temporary_ca_file(). + """ + try: + f = open(filename, "w") + for a in anchor_list: + s = a.output(fmt="PEM") + f.write(s) + f.close() + except: + return None + return filename + +def create_temporary_ca_file(anchor_list): + """ + Concatenate all the certificates (PEM format for the export) in + 'anchor_list' and write the result to file to a temporary file + using mkstemp() from tempfile module. On success 'filename' is + returned, None otherwise. + + If you are used to OpenSSL tools, this function builds a CAfile + that can be used for certificate and CRL check. + """ + try: + f, fname = tempfile.mkstemp() + for a in anchor_list: + s = a.output(fmt="PEM") + l = os.write(f, s) + os.close(f) + except: + return None + return fname + +def create_temporary_ca_path(anchor_list, folder): + """ + Create a CA path folder as defined in OpenSSL terminology, by + storing all certificates in 'anchor_list' list in PEM format + under provided 'folder' and then creating the associated links + using the hash as usually done by c_rehash. + + Note that you can also include CRL in 'anchor_list'. In that + case, they will also be stored under 'folder' and associated + links will be created. + + In folder, the files are created with names of the form + 0...ZZ.pem. If you provide an empty list, folder will be created + if it does not already exist, but that's all. + + The number of certificates written to folder is returned on + success, None on error. + """ + # We should probably avoid writing duplicate anchors and also + # check if they are all certs. + try: + if not os.path.isdir(folder): + os.makedirs(folder) + except: + return None + + l = len(anchor_list) + if l == 0: + return None + fmtstr = "%%0%sd.pem" % math.ceil(math.log(l, 10)) + i = 0 + try: + for a in anchor_list: + fname = os.path.join(folder, fmtstr % i) + f = open(fname, "w") + s = a.output(fmt="PEM") + f.write(s) + f.close() + i += 1 + except: + return None + + r,w=popen2.popen2("c_rehash %s" % folder) + r.close(); w.close() + + return l diff --git a/scapy/layers/tls/crypto/prf.py b/scapy/layers/tls/crypto/prf.py new file mode 100644 index 0000000000000000000000000000000000000000..1f93f92569074352bfb156160ec3518e16b9e70c --- /dev/null +++ b/scapy/layers/tls/crypto/prf.py @@ -0,0 +1,317 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS Pseudorandom Function. +""" + +from scapy.error import warning +from scapy.utils import strxor + +from scapy.layers.tls.crypto.hash import tls_hash_algs +from scapy.layers.tls.crypto.h_mac import tls_hmac_algs + + +### Data expansion functions + +def _tls_P_hash(secret, seed, req_len, hm): + """ + Provides the implementation of P_hash function defined in + section 5 of RFC 4346 (and section 5 of RFC 5246). Two + parameters have been added (hm and req_len): + + - secret : the key to be used. If RFC 4868 is to be believed, + the length must match hm.key_len. Actually, + python hmac takes care of formatting every key. + - seed : the seed to be used. + - req_len : the length of data to be generated by iterating + the specific HMAC function (hm). This prevents + multiple calls to the function. + - hm : the hmac function class to use for iteration (either + Hmac_MD5 or Hmac_SHA1 in TLS <= 1.1 or + Hmac_SHA256 or Hmac_SHA384 in TLS 1.2) + """ + + hash_len = hm.hash_alg.hash_len + n = (req_len + hash_len - 1) / hash_len + + res = "" + a = hm(secret).digest(seed) # A(1) + + while n > 0: + res += hm(secret).digest(a + seed) + a = hm(secret).digest(a) + n -= 1 + + return res[:req_len] + + +def _tls_P_MD5(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, tls_hmac_algs["HMAC-MD5"]) + +def _tls_P_SHA1(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, tls_hmac_algs["HMAC-SHA"]) + +def _tls_P_SHA256(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, tls_hmac_algs["HMAC-SHA256"]) + +def _tls_P_SHA384(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, tls_hmac_algs["HMAC-SHA384"]) + +def _tls_P_SHA512(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, tls_hmac_algs["HMAC-SHA512"]) + + +### PRF functions, according to the protocol version + +def _ssl_PRF(secret, seed, req_len): + """ + Provides the implementation of SSLv3 PRF function: + + SSLv3-PRF(secret, seed) = + MD5(secret || SHA-1("A" || secret || seed)) || + MD5(secret || SHA-1("BB" || secret || seed)) || + MD5(secret || SHA-1("CCC" || secret || seed)) || ... + + req_len should not be more than 26 x 16 = 416. + + """ + if req_len > 416: + warning("_ssl_PRF() is not expected to provide more than 416 bytes") + return "" + + d = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] + res = "" + hash_md5 = tls_hash_algs["MD5"] + rounds = (req_len + hash_md5.hash_len - 1) / hash_md5.hash_len + + for i in range(rounds): + label = d[i] * (i+1) + tmp = tls_hash_algs["SHA"]().digest(label + secret + seed) + res += tls_hash_algs["MD5"]().digest(secret + tmp) + + return res[:req_len] + +def _tls_PRF(secret, label, seed, req_len): + """ + Provides the implementation of TLS PRF function as defined in + section 5 of RFC 4346: + + PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR + P_SHA-1(S2, label + seed) + + Parameters are: + + - secret: the secret used by the HMAC in the 2 expansion + functions (S1 and S2 are the halves of this secret). + - label: specific label as defined in various sections of the RFC + depending on the use of the generated PRF keystream + - seed: the seed used by the expansion functions. + - req_len: amount of keystream to be generated + + """ + + l = (len(secret) + 1) / 2 + S1 = secret[:l] + S2 = secret[-l:] + + a1 = _tls_P_MD5(S1, label+seed, req_len) + a2 = _tls_P_SHA1(S2, label+seed, req_len) + + return strxor(a1, a2) + +def _tls12_SHA256PRF(secret, label, seed, req_len): + """ + Provides the implementation of TLS 1.2 PRF function as + defined in section 5 of RFC 5246: + + PRF(secret, label, seed) = P_SHA256(secret, label + seed) + + Parameters are: + + - secret: the secret used by the HMAC in the 2 expansion + functions (S1 and S2 are the halves of this secret). + - label: specific label as defined in various sections of the RFC + depending on the use of the generated PRF keystream + - seed: the seed used by the expansion functions. + - req_len: amount of keystream to be generated + + """ + return _tls_P_SHA256(secret, label+seed, req_len) + +def _tls12_SHA384PRF(secret, label, seed, req_len): + return _tls_P_SHA384(secret, label+seed, req_len) + +def _tls12_SHA512PRF(secret, label, seed, req_len): + return _tls_P_SHA512(secret, label+seed, req_len) + + +class PRF(object): + """ + The PRF used by SSL/TLS varies based on the version of the protocol and + (for TLS 1.2) possibly the Hash algorithm of the negotiated cipher suite. + The various uses of the PRF (key derivation, computation of verify_data, + computation of pre_master_secret values) for the different versions of the + protocol also changes. In order to abstract those elements, the common + _tls_PRF() object is provided. It is expected to be initialised in the + context of the connection state using the tls_version and the cipher suite. + """ + def __init__(self, hash_name="SHA256", tls_version=0x0303): + self.tls_version = tls_version + self.hash_name = hash_name + + if (tls_version == 0x0200 or # SSLv2 + tls_version == 0x0300): # SSLv3 + self.prf = _ssl_PRF + elif (tls_version == 0x0301 or # TLS 1.0 + tls_version == 0x0302): # TLS 1.1 + self.prf = _tls_PRF + elif tls_version == 0x0303: # TLS 1.2 + if hash_name == "SHA384": + self.prf = _tls12_SHA384PRF + elif hash_name == "SHA512": + self.prf = _tls12_SHA512PRF + else: + self.prf = _tls12_SHA256PRF + else: + warning("Unknown TLS version") + + def compute_master_secret(self, pre_master_secret, + client_random, server_random): + """ + Return the 48-byte master_secret, computed from pre_master_secret, + client_random and server_random. See RFC 5246, section 6.3. + """ + seed = client_random + server_random + if self.tls_version <= 0x0300: + return self.prf(pre_master_secret, seed, 48) + else: + return self.prf(pre_master_secret, "master secret", seed, 48) + + def derive_key_block(self, master_secret, server_random, + client_random, req_len): + """ + Perform the derivation of master_secret into a key_block of req_len + requested length. See RFC 5246, section 6.3. + """ + seed = server_random + client_random + if self.tls_version <= 0x0300: + return self.prf(master_secret, seed, req_len) + else: + return self.prf(master_secret, "key expansion", seed, req_len) + + def compute_verify_data(self, con_end, read_or_write, + handshake_msg, master_secret): + """ + Return verify_data based on handshake messages, connection end, + master secret, and read_or_write position. See RFC 5246, section 7.4.9. + + Every TLS 1.2 cipher suite has a verify_data of length 12. Note also: + "This PRF with the SHA-256 hash function is used for all cipher + suites defined in this document and in TLS documents published + prior to this document when TLS 1.2 is negotiated." + Cipher suites using SHA-384 were defined later on. + """ + if self.tls_version <= 0x0300: + + if read_or_write == "write": + d = {"client": "CLNT", "server": "SRVR"} + else: + d = {"client": "SRVR", "server": "CLNT"} + label = d[con_end] + + sslv3_md5_pad1 = "\x36"*48 + sslv3_md5_pad2 = "\x5c"*48 + sslv3_sha1_pad1 = "\x36"*40 + sslv3_sha1_pad2 = "\x5c"*40 + + md5 = tls_hash_algs["MD5"]() + sha1 = tls_hash_algs["SHA"]() + + md5_hash = md5.digest(master_secret + sslv3_md5_pad2 + + md5.digest(handshake_msg + label + + master_secret + sslv3_md5_pad1)) + sha1_hash = sha1.digest(master_secret + sslv3_sha1_pad2 + + sha1.digest(handshake_msg + label + + master_secret + sslv3_sha1_pad1)) + verify_data = md5_hash + sha1_hash + + else: + + if read_or_write == "write": + d = {"client": "client", "server": "server"} + else: + d = {"client": "server", "server": "client"} + label = d[con_end] + " finished" + + if self.tls_version <= 0x0302: + s1 = tls_hash_algs["MD5"]().digest(handshake_msg) + s2 = tls_hash_algs["SHA"]().digest(handshake_msg) + verify_data = self.prf(master_secret, label, s1 + s2, 12) + else: + if self.hash_name in ["MD5", "SHA"]: + h = tls_hash_algs["SHA256"]() + else: + h = tls_hash_algs[self.hash_name]() + s = h.digest(handshake_msg) + verify_data = self.prf(master_secret, label, s, 12) + + return verify_data + + def postprocess_key_for_export(self, key, client_random, server_random, + con_end, read_or_write, req_len): + """ + Postprocess cipher key for EXPORT ciphersuite, i.e. weakens it. + An export key generation example is given in section 6.3.1 of RFC 2246. + See also page 86 of EKR's book. + """ + s = con_end + read_or_write + s = (s == "clientwrite" or s == "serverread") + + if self.tls_version == 0x0300: + if s: + tbh = key + client_random + server_random + else: + tbh = key + server_random + client_random + export_key = tls_hash_algs["MD5"]().digest(tbh)[:req_len] + else: + if s: + tag = "client write key" + else: + tag = "server write key" + export_key = self.prf(key, + tag, + client_random + server_random, + req_len) + return export_key + + def generate_iv_for_export(self, client_random, server_random, + con_end, read_or_write, req_len): + """ + Generate IV for EXPORT ciphersuite, i.e. weakens it. + An export IV generation example is given in section 6.3.1 of RFC 2246. + See also page 86 of EKR's book. + """ + s = con_end + read_or_write + s = (s == "clientwrite" or s == "serverread") + + if self.tls_version == 0x0300: + if s: + tbh = client_random + server_random + else: + tbh = server_random + client_random + iv = tls_hash_algs["MD5"]().digest(tbh)[:req_len] + else: + iv_block = self.prf("", + "IV block", + client_random + server_random, + 2*req_len) + if s: + iv = iv_block[:req_len] + else: + iv = iv_block[req_len:] + return iv + diff --git a/scapy/layers/tls/crypto/suites.py b/scapy/layers/tls/crypto/suites.py new file mode 100644 index 0000000000000000000000000000000000000000..7605a736e88ac40910ecc151095e7b9952d1f1a0 --- /dev/null +++ b/scapy/layers/tls/crypto/suites.py @@ -0,0 +1,971 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS cipher suites. + +A comprehensive list of specified cipher suites can be consulted at: +https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml +""" + +from scapy.layers.tls.crypto.kx_algs import tls_kx_algs +from scapy.layers.tls.crypto.hash import tls_hash_algs +from scapy.layers.tls.crypto.h_mac import tls_hmac_algs +from scapy.layers.tls.crypto.ciphers import tls_cipher_algs + + +def get_algs_from_ciphersuite_name(ciphersuite_name): + """ + Return the 3-tuple made of the Key Exchange Algorithm class, the Cipher + class and the HMAC class, through the parsing of the ciphersuite name. + """ + s = ciphersuite_name[4:] + + kx_name, s = s.split("_WITH_") + kx_alg = tls_kx_algs.get(kx_name) + + if s.endswith("CCM") or s.endswith("CCM_8"): + + hash_alg = tls_hash_algs.get("SHA256") + cipher_alg = tls_cipher_algs.get(s) + hmac_alg = None + + else: + + hash_name = s.split('_')[-1] + hash_alg = tls_hash_algs.get(hash_name) + + cipher_name = s[:-(len(hash_name) + 1)] + cipher_alg = tls_cipher_algs.get(cipher_name) + + hmac_alg = None + if cipher_alg is not None and cipher_alg.type != "aead": + hmac_name = "HMAC-%s" % hash_name + hmac_alg = tls_hmac_algs.get(hmac_name) + + return kx_alg, cipher_alg, hmac_alg, hash_alg + + +_tls_cipher_suites = {} +_tls_cipher_suites_cls = {} + +class _GenericCipherSuiteMetaclass(type): + """ + Cipher suite classes are automatically registered through this metaclass. + Their name attribute equates their respective class name. + + We also pre-compute every expected length of the key block to be generated, + which may vary according to the current tls_version. The default is set to + the TLS 1.2 length, and the value should be set at class instantiation. + + Regarding the AEAD cipher suites, note that the 'hmac_alg' attribute will + be set to None. Yet, we always need a 'hash_alg' for the PRF. + + Also, if pycrypto 2.7a is not installed, there will be no AES.GCM nor + AES.CCM support, so the 'usable' attribute will be set to False. + """ + def __new__(cls, cs_name, bases, dct): + cs_val = dct.get("val") + + if cs_name != "_GenericCipherSuite": + kx, c, hm, h = get_algs_from_ciphersuite_name(cs_name) + + if (kx is None or c is None or h is None): + dct["usable"] = False + else: + dct["usable"] = True + dct["name"] = cs_name + dct["kx_alg"] = kx + dct["cipher_alg"] = c + dct["hmac_alg"] = hm + dct["hash_alg"] = h + + kb_len = 2*c.key_len + + if c.type == "stream" or c.type == "block": + kb_len += 2*hm.key_len + + kb_len_v1_0 = kb_len + if c.type == "block": + kb_len_v1_0 += 2*c.block_size + # no explicit IVs added for TLS 1.1+ + elif c.type == "aead": + kb_len_v1_0 += 2*c.salt_len + kb_len += 2*c.salt_len + + dct["_key_block_len_v1_0"] = kb_len_v1_0 + dct["key_block_len"] = kb_len + + _tls_cipher_suites[cs_val] = cs_name + the_class = super(_GenericCipherSuiteMetaclass, cls).__new__(cls, + cs_name, + bases, + dct) + if cs_name != "_GenericCipherSuite": + _tls_cipher_suites_cls[cs_val] = the_class + return the_class + + +class _GenericCipherSuite(object): + __metaclass__ = _GenericCipherSuiteMetaclass + + def __init__(self, tls_version=0x0303): + """ + Most of the attributes are fixed and have already been set by the + metaclass, but we still have to provide tls_version differentiation. + + For now, the key_block_len remains the only application if this. + Indeed for TLS 1.1+, when using a block cipher, there are no implicit + IVs derived from the master secret. Note that an overlong key_block_len + would not affect the secret generation (the trailing bytes would + simply be discarded), but we still provide this for completeness. + """ + super(_GenericCipherSuite, self).__init__() + if tls_version <= 0x301: + self.key_block_len = self._key_block_len_v1_0 + + +class TLS_NULL_WITH_NULL_NULL(_GenericCipherSuite): + val = 0x0000 + +class TLS_RSA_WITH_NULL_MD5(_GenericCipherSuite): + val = 0x0001 + +class TLS_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x0002 + +class TLS_RSA_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x0003 + +class TLS_RSA_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0004 + +class TLS_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0005 + +class TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): + val = 0x0006 + +class TLS_RSA_WITH_IDEA_CBC_SHA(_GenericCipherSuite): + val = 0x0007 + +class TLS_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0008 + +class TLS_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0009 + +class TLS_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x000A + +class TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x000B + +class TLS_DH_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x000C + +class TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x000D + +class TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x000E + +class TLS_DH_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x000F + +class TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0010 + +class TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0011 + +class TLS_DHE_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0012 + +class TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0013 + +class TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0014 + +class TLS_DHE_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0015 + +class TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0016 + +class TLS_DH_anon_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x0017 + +class TLS_DH_anon_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0018 + +class TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0019 + +class TLS_DH_anon_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x001A + +class TLS_DH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x001B + +class TLS_KRB5_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x001E + +class TLS_KRB5_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x001F + +class TLS_KRB5_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0020 + +class TLS_KRB5_WITH_IDEA_CBC_SHA(_GenericCipherSuite): + val = 0x0021 + +class TLS_KRB5_WITH_DES_CBC_MD5(_GenericCipherSuite): + val = 0x0022 + +class TLS_KRB5_WITH_3DES_EDE_CBC_MD5(_GenericCipherSuite): + val = 0x0023 + +class TLS_KRB5_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0024 + +class TLS_KRB5_WITH_IDEA_CBC_MD5(_GenericCipherSuite): + val = 0x0025 + +class TLS_KRB5_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0026 + +class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA(_GenericCipherSuite): + val = 0x0027 + +class TLS_KRB5_EXPORT_WITH_RC4_40_SHA(_GenericCipherSuite): + val = 0x0028 + +class TLS_KRB5_EXPORT_WITH_DES40_CBC_MD5(_GenericCipherSuite): + val = 0x0029 + +class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): + val = 0x002A + +class TLS_KRB5_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x002B + +class TLS_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002C + +class TLS_DHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002D + +class TLS_RSA_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002E + +class TLS_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x002F + +class TLS_DH_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0030 + +class TLS_DH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0031 + +class TLS_DHE_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0032 + +class TLS_DHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0033 + +class TLS_DH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0034 + +class TLS_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0035 + +class TLS_DH_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0036 + +class TLS_DH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0037 + +class TLS_DHE_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0038 + +class TLS_DHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0039 + +class TLS_DH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x003A + +class TLS_RSA_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x003B + +class TLS_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003C + +class TLS_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x003D + +class TLS_DH_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003E + +class TLS_DH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003F + +class TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x0040 + +class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0041 + +class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0042 + +class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0043 + +class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0044 + +class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0045 + +class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0046 + +class TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x0067 + +class TLS_DH_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x0068 + +class TLS_DH_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x0069 + +class TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006A + +class TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006B + +class TLS_DH_anon_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x006C + +class TLS_DH_anon_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006D + +class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0084 + +class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0085 + +class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0086 + +class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0087 + +class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0088 + +class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0089 + +class TLS_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x008A + +class TLS_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x008B + +class TLS_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x008C + +class TLS_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x008D + +class TLS_DHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x008E + +class TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x008F + +class TLS_DHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0090 + +class TLS_DHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0091 + +class TLS_RSA_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0092 + +class TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0093 + +class TLS_RSA_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0094 + +class TLS_RSA_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0095 + +class TLS_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0096 + +class TLS_DH_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0097 + +class TLS_DH_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0098 + +class TLS_DHE_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0099 + +class TLS_DHE_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x009A + +class TLS_DH_anon_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x009B + +class TLS_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x009C + +class TLS_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x009D + +class TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x009E + +class TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x009F + +class TLS_DH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A0 + +class TLS_DH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A1 + +class TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A2 + +class TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A3 + +class TLS_DH_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A4 + +class TLS_DH_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A5 + +class TLS_DH_anon_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A6 + +class TLS_DH_anon_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A7 + +class TLS_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A8 + +class TLS_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A9 + +class TLS_DHE_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00AA + +class TLS_DHE_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00AB + +class TLS_RSA_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00AC + +class TLS_RSA_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00AD + +class TLS_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00AE + +class TLS_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00AF + +class TLS_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B0 + +class TLS_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B1 + +class TLS_DHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00B2 + +class TLS_DHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00B3 + +class TLS_DHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B4 + +class TLS_DHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B5 + +class TLS_RSA_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00B6 + +class TLS_RSA_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00B7 + +class TLS_RSA_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B8 + +class TLS_RSA_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B9 + +class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BA + +class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BB + +class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BC + +class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BD + +class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BE + +class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BF + +class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C0 + +class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C1 + +class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C2 + +class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C3 + +class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C4 + +class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C5 + +#class TLS_EMPTY_RENEGOTIATION_INFO_CSV(_GenericCipherSuite): +# val = 0x00FF + +#class TLS_FALLBACK_SCSV(_GenericCipherSuite): +# val = 0x5600 + +class TLS_ECDH_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC001 + +class TLS_ECDH_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC002 + +class TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC003 + +class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC004 + +class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC005 + +class TLS_ECDHE_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC006 + +class TLS_ECDHE_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC007 + +class TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC008 + +class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC009 + +class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC00A + +class TLS_ECDH_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC00B + +class TLS_ECDH_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC00C + +class TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC00D + +class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC00E + +class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC00F + +class TLS_ECDHE_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC010 + +class TLS_ECDHE_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC011 + +class TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC012 + +class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC013 + +class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC014 + +class TLS_ECDH_anon_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC015 + +class TLS_ECDH_anon_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC016 + +class TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC017 + +class TLS_ECDH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC018 + +class TLS_ECDH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC019 + +class TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01A + +class TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01B + +class TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01C + +class TLS_SRP_SHA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01D + +class TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01E + +class TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01F + +class TLS_SRP_SHA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC020 + +class TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC021 + +class TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC022 + +class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC023 + +class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC024 + +class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC025 + +class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC026 + +class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC027 + +class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC028 + +class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC029 + +class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC02A + +class TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02B + +class TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC02C + +class TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02D + +class TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC02E + +class TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02F + +class TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC030 + +class TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC031 + +class TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC032 + +class TLS_ECDHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC033 + +class TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC034 + +class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC035 + +class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC036 + +class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC037 + +class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC038 + +class TLS_ECDHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC039 + +class TLS_ECDHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0xC03A + +class TLS_ECDHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0xC03B + +# suites 0xC03C-C071 use ARIA + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC072 + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC073 + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC074 + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC075 + +class TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC076 + +class TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC077 + +class TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC078 + +class TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC079 + +class TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07A + +class TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07B + +class TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07C + +class TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07D + +class TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07E + +class TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07F + +class TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC080 + +class TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC081 + +class TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC082 + +class TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC083 + +class TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC084 + +class TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC085 + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC086 + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC087 + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC088 + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC089 + +class TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08A + +class TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08B + +class TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08C + +class TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08D + +class TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08E + +class TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08F + +class TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC090 + +class TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC091 + +class TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC092 + +class TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC093 + +class TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC094 + +class TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC095 + +class TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC096 + +class TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC097 + +class TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC098 + +class TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC099 + +class TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC09A + +class TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC09B + +class TLS_RSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC09C + +class TLS_RSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC09D + +class TLS_DHE_RSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC09E + +class TLS_DHE_RSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC09F + +class TLS_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A0 + +class TLS_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A1 + +class TLS_DHE_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A2 + +class TLS_DHE_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A3 + +class TLS_PSK_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0A4 + +class TLS_PSK_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0A5 + +class TLS_DHE_PSK_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0A6 + +class TLS_DHE_PSK_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0A7 + +class TLS_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A8 + +class TLS_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A9 + +class TLS_DHE_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0AA + +class TLS_DHE_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0AB + +class TLS_ECDHE_ECDSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0AC + +class TLS_ECDHE_ECDSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0AD + +class TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0AE + +class TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0AF + +#class TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCA8 +# +#class TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCA9 +# +#class TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCAA +# +#class TLS_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCAB +# +#class TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCAC +# +#class TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCAD +# +#class TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): +# val = 0xCCAE + + +_tls_cipher_suites[0x00ff] = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" +_tls_cipher_suites[0x5600] = "TLS_FALLBACK_SCSV" + + +def get_usable_ciphersuites(l, kx): + """ + From a list of proposed ciphersuites, this function returns a list of + usable cipher suites, i.e. for which key exchange, cipher and hash + algorithms are known to be implemented and usable in current version of the + TLS extension. The order of the cipher suites in the list returned by the + function matches the one of the proposal. + """ + res = [] + for c in l: + if _tls_cipher_suites_cls.has_key(c): + ciph = _tls_cipher_suites_cls[c] + if ciph.usable: + #XXX select among RSA and ECDSA cipher suites + # according to the key(s) the server was given + if kx in ciph.kx_alg.name: + res.append(c) + return res + diff --git a/scapy/layers/tls/handshake.py b/scapy/layers/tls/handshake.py new file mode 100644 index 0000000000000000000000000000000000000000..5ee9aaeaa6bc99ff2c58f7082800675041407ac7 --- /dev/null +++ b/scapy/layers/tls/handshake.py @@ -0,0 +1,1471 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS handshake fields & logic. + +This module covers the handshake TLS subprotocol, except for the key exchange +mechanisms which are addressed with keyexchange.py. +""" + +import math + +from scapy.error import warning +from scapy.fields import * +from scapy.packet import Packet, Raw, Padding +from scapy.utils import repr_hex +from scapy.layers.x509 import X509_Extensions, OCSP_Response +from scapy.layers.tls.cert import Cert, PrivKey, PubKey +from scapy.layers.tls.basefields import _tls_version, _TLSVersionField +from scapy.layers.tls.keyexchange import (_tls_named_curves, _tls_hash_sig, + _TLSSignature, _TLSServerParamsField, + _TLSSignatureField, ServerRSAParams, + SigAndHashAlgsField, + SigAndHashAlgsLenField) +from scapy.layers.tls.session import (_GenericTLSSessionInheritance, + writeConnState, + readConnState) +from scapy.layers.tls.crypto.compression import (_tls_compression_algs, + _tls_compression_algs_cls, + _GenericComp, + _GenericCompMetaclass) +from scapy.layers.tls.crypto.suites import (_tls_cipher_suites, + _tls_cipher_suites_cls, + _GenericCipherSuite, + _GenericCipherSuiteMetaclass, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA) + + +############################################################################### +### Generic TLS Handshake message ### +############################################################################### + +_tls_handshake_type = { 0: "hello_request", 1: "client_hello", + 2: "server_hello", 3: "hello_verify_request", + 4: "session_ticket", 11: "certificate", + 12: "server_key_exchange", 13: "certificate_request", + 14: "server_hello_done", 15: "certificate_verify", + 16: "client_key_exchange", 20: "finished", + 21: "certificate_url", 22: "certificate_status", + 23: "supplemental_data" } + + +class _TLSHandshake(_GenericTLSSessionInheritance): + """ + Inherited by other Handshake classes to get post_build(). + Also used as a fallback for unknown TLS Handshake packets. + """ + name = "TLS Handshake Generic message" + fields_desc = [ ByteEnumField("msgtype", None, _tls_handshake_type), + ThreeBytesField("msglen", None), + StrLenField("msg", "", + length_from=lambda pkt: pkt.msglen) ] + + def post_build(self, p, pay): + l = len(p) + if self.msglen is None: + l2 = l - 4 + p = struct.pack("!I", (ord(p[0]) << 24) | l2) + p[4:] + return p + pay + + def guess_payload_class(self, p): + return Padding + + def tls_session_update(self, msg_str): + """ + Covers both post_build- and post_dissection- context updates. + """ + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + +############################################################################### +### HelloRequest ### +############################################################################### + +class TLSHelloRequest(_TLSHandshake): + name = "TLS Handshake - Hello Request" + fields_desc = [ ByteEnumField("msgtype", 0, _tls_handshake_type), + ThreeBytesField("msglen", None) ] + + def tls_session_update(self, msg_str): + """ + Message should not be added to the list of handshake messages + that will be hashed in the finished and certificate verify messages. + """ + return + + +############################################################################### +### ClientHello fields ### +############################################################################### + +class _GMTUnixTimeField(IntField): + """ + Piggybacked from scapy6 UTCTimeField + "The current time and date in standard UNIX 32-bit format (seconds since + the midnight starting Jan 1, 1970, GMT, ignoring leap seconds)." + """ + epoch = (1970, 1, 1, 0, 0, 0, 3, 1, 0) + + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + from time import gmtime, strftime, mktime + delta = mktime(gmtime(0)) - mktime(self.epoch) + x = x-delta + t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(x)) + return "%s (%d)" % (t, x) + + def i2h(self, pkt, x): + if x is not None: + return x + return 0 + +class _TLSRandomBytesField(StrFixedLenField): + def i2repr(self, pkt, x): + if x is None: + return repr(x) + return repr_hex(self.i2h(pkt,x)) + + +class _SessionIDField(StrLenField): + """ + opaque SessionID<0..32>; section 7.4.1.2 of RFC 4346 + """ + pass + + +class _CipherSuitesField(StrLenField): + __slots__ = ["itemfmt", "itemsize", "i2s", "s2i"] + islist = 1 + def __init__(self, name, default, dico, length_from=None, itemfmt="!H"): + StrLenField.__init__(self, name, default, length_from=length_from) + self.itemfmt = itemfmt + self.itemsize = struct.calcsize(itemfmt) + i2s = self.i2s = {} + s2i = self.s2i = {} + keys = dico.keys() + for k in keys: + i2s[k] = dico[k] + s2i[dico[k]] = k + + def any2i_one(self, pkt, x): + if (isinstance(x, _GenericCipherSuite) or + isinstance(x, _GenericCipherSuiteMetaclass)): + x = x.val + if type(x) is str: + x = self.s2i[x] + return x + + def i2repr_one(self, pkt, x): + fmt = "0x%%0%dx" % self.itemsize + return self.i2s.get(x, fmt % x) + + def any2i(self, pkt, x): + if type(x) is not list: + x = [x] + return map(lambda z,pkt=pkt:self.any2i_one(pkt,z), x) + + def i2repr(self, pkt, x): + if x is None: + return "None" + l = map(lambda z,pkt=pkt:self.i2repr_one(pkt,z), x) + if len(l) == 1: + l = l[0] + else: + l = "[%s]" % ", ".join(l) + return l + + def i2m(self, pkt, val): + if val is None: + val = [] + return "".join(map(lambda x: struct.pack(self.itemfmt, x), val)) + + def m2i(self, pkt, m): + res = [] + itemlen = struct.calcsize(self.itemfmt) + while m: + res.append(struct.unpack(self.itemfmt, m[:itemlen])[0]) + m = m[itemlen:] + return res + + def i2len(self, pkt, i): + return len(i)*self.itemsize + + +class _CompressionMethodsField(_CipherSuitesField): + + def any2i_one(self, pkt, x): + if (isinstance(x, _GenericComp) or + isinstance(x, _GenericCompMetaclass)): + x = x.val + if type(x) is str: + x = self.s2i[x] + return x + + +############################################################################### +### ClientHello/ServerHello extensions ### +############################################################################### + +# We provide these extensions mostly for packet manipulation purposes. +# For now, most of them are not considered by our automaton. + +_tls_ext = { 0: "server_name", # RFC 4366 + 1: "max_fragment_length", # RFC 4366 + 2: "client_certificate_url", # RFC 4366 + 3: "trusted_ca_keys", # RFC 4366 + 4: "truncated_hmac", # RFC 4366 + 5: "status_request", # RFC 4366 + 6: "user_mapping", # RFC 4681 + 7: "client_authz", # RFC 5878 + 8: "server_authz", # RFC 5878 + 9: "cert_type", # RFC 6091 + 10: "elliptic_curves", # RFC 4492 + 11: "ec_point_formats", # RFC 4492 + 13: "signature_algorithms", # RFC 5246 + 0x0f: "heartbeat", # RFC 6520 + 0x10: "alpn", # RFC 7301 + 0x15: "padding", # RFC 7685 + 0x23: "session_ticket", # RFC 5077 + 0x3374: "next_protocol_negotiation", + # RFC-draft-agl-tls-nextprotoneg-03 + 0xff01: "renegotiation_info" # RFC 5746 + } + + +class TLS_Ext_Unknown(_GenericTLSSessionInheritance): + name = "TLS Extension - Scapy Unknown" + fields_desc = [ShortEnumField("type", None, _tls_ext), + FieldLenField("len", None, fmt="!H", length_of="val"), + StrLenField("val", "", + length_from=lambda pkt: pkt.len) ] + + def post_build(self, p, pay): + if self.len is None: + l = len(p) - 4 + p = p[:2] + struct.pack("!H", l) + p[4:] + return p+pay + +class TLS_Ext_PrettyPacketList(TLS_Ext_Unknown): + """ + Dummy extension used for server_name/ALPN/NPN for a lighter representation: + the final field is showed as a 1-line list rather than as lots of packets. + XXX Define a new condition for packet lists in Packet._show_or_dump? + """ + def _show_or_dump(self, dump=False, indent=3, + lvl="", label_lvl="", first_call=True): + """ Reproduced from packet.py """ + ct = AnsiColorTheme() if dump else conf.color_theme + s = "%s%s %s %s \n" % (label_lvl, ct.punct("###["), + ct.layer_name(self.name), ct.punct("]###")) + for f in self.fields_desc[:-1]: + ncol = ct.field_name + vcol = ct.field_value + fvalue = self.getfieldval(f.name) + begn = "%s %-10s%s " % (label_lvl+lvl, ncol(f.name), + ct.punct("="),) + reprval = f.i2repr(self,fvalue) + if type(reprval) is str: + reprval = reprval.replace("\n", "\n"+" "*(len(label_lvl) + +len(lvl) + +len(f.name) + +4)) + s += "%s%s\n" % (begn,vcol(reprval)) + f = self.fields_desc[-1] + ncol = ct.field_name + vcol = ct.field_value + fvalue = self.getfieldval(f.name) + begn = "%s %-10s%s " % (label_lvl+lvl, ncol(f.name), ct.punct("="),) + reprval = f.i2repr(self,fvalue) + if type(reprval) is str: + reprval = reprval.replace("\n", "\n"+" "*(len(label_lvl) + +len(lvl) + +len(f.name) + +4)) + s += "%s%s\n" % (begn,vcol(reprval)) + if self.payload: + s += self.payload._show_or_dump(dump=dump, indent=indent, + lvl=lvl+(" "*indent*self.show_indent), + label_lvl=label_lvl, first_call=False) + + if first_call and not dump: + print s + else: + return s + + +_tls_server_name_types = { 0: "host_name" } + +class ServerName(Packet): + name = "HostName" + fields_desc = [ ByteEnumField("nametype", 0, _tls_server_name_types), + FieldLenField("namelen", None, length_of="servername"), + StrLenField("servername", "", + length_from=lambda pkt: pkt.namelen) ] + def guess_payload_class(self, p): + return Padding + +class ServerListField(PacketListField): + def i2repr(self, pkt, x): + res = [p.servername for p in x] + return "[%s]" % ", ".join(res) + +class TLS_Ext_ServerName(TLS_Ext_PrettyPacketList): # RFC 4366 + name = "TLS Extension - Server Name" + fields_desc = [ShortEnumField("type", 0, _tls_ext), + FieldLenField("len", None, length_of="servernames", + adjust=lambda pkt,x: x+2), + FieldLenField("servernameslen", None, + length_of="servernames"), + ServerListField("servernames", [], ServerName, + length_from=lambda pkt: pkt.servernameslen)] + + +class TLS_Ext_MaxFragLen(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Server Name" + fields_desc = [ShortEnumField("type", 1, _tls_ext), + ShortField("len", None), + ByteEnumField("maxfraglen", 4, { 1: "2^9", + 2: "2^10", + 3: "2^11", + 4: "2^12" }) ] + + +class TLS_Ext_ClientCertURL(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Server Name" + fields_desc = [ShortEnumField("type", 2, _tls_ext), + ShortField("len", None) ] + + +_tls_trusted_authority_types = {0: "pre_agreed", + 1: "key_sha1_hash", + 2: "x509_name", + 3: "cert_sha1_hash" } + +class TAPreAgreed(Packet): + name = "Trusted authority - pre_agreed" + fields_desc = [ ByteEnumField("idtype", 0, _tls_trusted_authority_types) ] + def guess_payload_class(self, p): + return Padding + +class TAKeySHA1Hash(Packet): + name = "Trusted authority - key_sha1_hash" + fields_desc = [ ByteEnumField("idtype", 1, _tls_trusted_authority_types), + StrFixedLenField("id", None, 20) ] + def guess_payload_class(self, p): + return Padding + +class TAX509Name(Packet): + """ + XXX Section 3.4 of RFC 4366. Implement a more specific DNField + rather than current StrLenField. + """ + name = "Trusted authority - x509_name" + fields_desc = [ ByteEnumField("idtype", 2, _tls_trusted_authority_types), + FieldLenField("dnlen", None, length_of="dn"), + StrLenField("dn", "", length_from=lambda pkt: pkt.dnlen) ] + def guess_payload_class(self, p): + return Padding + +class TACertSHA1Hash(Packet): + name = "Trusted authority - cert_sha1_hash" + fields_desc = [ ByteEnumField("idtype", 3, _tls_trusted_authority_types), + StrFixedLenField("id", None, 20) ] + def guess_payload_class(self, p): + return Padding + +_tls_trusted_authority_cls = {0: TAPreAgreed, + 1: TAKeySHA1Hash, + 2: TAX509Name, + 3: TACertSHA1Hash } + +class _TAListField(PacketListField): + """ + Specific version that selects the right Trusted Authority (previous TA*) + class to be used for dissection based on idtype. + """ + def m2i(self, pkt, m): + idtype = ord(m[0]) + cls = self.cls + if _tls_trusted_authority_cls.has_key(idtype): + cls = _tls_trusted_authority_cls[idtype] + return cls(m) + +class TLS_Ext_TrustedCAInd(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Trusted CA Indication" + fields_desc = [ShortEnumField("type", 3, _tls_ext), + ShortField("len", None), + FieldLenField("talen", None, length_of="ta"), + _TAListField("ta", [], Raw, + length_from=lambda pkt: pkt.talen) ] + + +class TLS_Ext_TruncatedHMAC(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Truncated HMAC" + fields_desc = [ShortEnumField("type", 4, _tls_ext), + ShortField("len", None) ] + + +class ResponderID(Packet): + name = "Responder ID structure" + fields_desc = [ FieldLenField("respidlen", None, length_of="respid"), + StrLenField("respid", "", + length_from=lambda pkt: pkt.respidlen)] + def guess_payload_class(self, p): + return Padding + +class OCSPStatusRequest(Packet): + """ + This is the structure defined in RFC 6066, not in RFC 6960! + """ + name = "OCSPStatusRequest structure" + fields_desc = [ FieldLenField("respidlen", None, length_of="respid"), + PacketListField("respid", [], ResponderID, + length_from=lambda pkt: pkt.respidlen), + FieldLenField("reqextlen", None, length_of="reqext"), + PacketField("reqext", "", X509_Extensions) ] + def guess_payload_class(self, p): + return Padding + +_cert_status_type = { 1: "ocsp" } +_cert_status_req_cls = { 1: OCSPStatusRequest } + +class _StatusReqField(PacketListField): + def m2i(self, pkt, m): + idtype = pkt.stype + cls = self.cls + if _cert_status_req_cls.has_key(idtype): + cls = _cert_status_req_cls[idtype] + return cls(m) + +class TLS_Ext_CSR(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Certificate Status Request" + fields_desc = [ShortEnumField("type", 5, _tls_ext), + ShortField("len", None), + ByteEnumField("stype", None, _cert_status_type), + _StatusReqField("req", [], Raw, + length_from=lambda pkt: pkt.len - 1) ] + + +class TLS_Ext_UserMapping(TLS_Ext_Unknown): # RFC 4681 + name = "TLS Extension - User Mapping" + fields_desc = [ShortEnumField("type", 6, _tls_ext), + ShortField("len", None), + FieldLenField("umlen", None, fmt="B", length_of="um"), + FieldListField("um", [], + ByteField("umtype", 0), + length_from=lambda pkt: pkt.umlen) ] + + +class TLS_Ext_ClientAuthz(TLS_Ext_Unknown): # RFC 5878 + """ XXX Unsupported """ + name = "TLS Extension - Client Authz" + fields_desc = [ShortEnumField("type", 7, _tls_ext), + ShortField("len", None), + ] + +class TLS_Ext_ServerAuthz(TLS_Ext_Unknown): # RFC 5878 + """ XXX Unsupported """ + name = "TLS Extension - Server Authz" + fields_desc = [ShortEnumField("type", 8, _tls_ext), + ShortField("len", None), + ] + + +_tls_cert_types = { 0: "X.509", 1: "OpenPGP" } + +class TLS_Ext_ClientCertType(TLS_Ext_Unknown): # RFC 5081 + name = "TLS Extension - Certificate Type (client version)" + fields_desc = [ShortEnumField("type", 9, _tls_ext), + ShortField("len", None), + FieldLenField("ctypeslen", None, length_of="ctypes"), + FieldListField("ctypes", [0, 1], + ByteEnumField("certtypes", None, + _tls_cert_types), + length_from=lambda pkt: pkt.ctypeslen) ] + +class TLS_Ext_ServerCertType(TLS_Ext_Unknown): # RFC 5081 + name = "TLS Extension - Certificate Type (server version)" + fields_desc = [ShortEnumField("type", 9, _tls_ext), + ShortField("len", None), + ByteEnumField("ctype", None, _tls_cert_types) ] + +def _TLS_Ext_CertTypeDispatcher(m, *args, **kargs): + """ + We need to select the correct one on dissection. We use the length for + that, as 1 for client version would emply an empty list. + """ + l = struct.unpack("!H", m[2:4])[0] + if l == 1: + cls = TLS_Ext_ServerCertType + else: + cls = TLS_Ext_ClientCertType + return cls(m, *args, **kargs) + + +class TLS_Ext_SupportedEllipticCurves(TLS_Ext_Unknown): # RFC 4492 + name = "TLS Extension - Supported Elliptic Curves" + fields_desc = [ShortEnumField("type", 10, _tls_ext), + ShortField("len", None), + FieldLenField("ecllen", None, length_of="ecl"), + FieldListField("ecl", [], + ShortEnumField("nc", None, + _tls_named_curves), + length_from=lambda pkt: pkt.ecllen) ] + + +_tls_ecpoint_format = { 0: "uncompressed", + 1: "ansiX962_compressed_prime", + 2: "ansiX962_compressed_char2" } + +class TLS_Ext_SupportedPointFormat(TLS_Ext_Unknown): # RFC 4492 + name = "TLS Extension - Supported Point Format" + fields_desc = [ShortEnumField("type", 11, _tls_ext), + ShortField("len", None), + FieldLenField("ecpllen", None, fmt="B", length_of="ecpl"), + FieldListField("ecpl", [0], + ByteEnumField("nc", None, + _tls_ecpoint_format), + length_from=lambda pkt: pkt.ecpllen) ] + + +class TLS_Ext_SignatureAlgorithms(TLS_Ext_Unknown): # RFC 5246 + name = "TLS Extension - Signature Algorithms" + fields_desc = [ShortEnumField("type", 13, _tls_ext), + ShortField("len", None), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [], + EnumField("hash_sig", None, + _tls_hash_sig), + length_from= + lambda pkt: pkt.sig_algs_len) ] + + +class TLS_Ext_Heartbeat(TLS_Ext_Unknown): # RFC 6520 + name = "TLS Extension - Heartbeat" + fields_desc = [ShortEnumField("type", 0x0f, _tls_ext), + ShortField("len", None), + ByteEnumField("heartbeat_mode", 2, + { 1: "peer_allowed_to_send", + 2: "peer_not_allowed_to_send" }) ] + + +class ProtocolName(Packet): + name = "Protocol Name" + fields_desc = [ FieldLenField("len", None, fmt='B', length_of="protocol"), + StrLenField("protocol", "", + length_from=lambda pkt: pkt.len)] + def guess_payload_class(self, p): + return Padding + +class ProtocolListField(PacketListField): + def i2repr(self, pkt, x): + res = [p.protocol for p in x] + return "[%s]" % ", ".join(res) + +class TLS_Ext_ALPN(TLS_Ext_PrettyPacketList): # RFC 7301 + name = "TLS Extension - Application Layer Protocol Negotiation" + fields_desc = [ShortEnumField("type", 0x10, _tls_ext), + ShortField("len", None), + FieldLenField("protocolslen", None, length_of="protocols"), + ProtocolListField("protocols", [], ProtocolName, + length_from=lambda pkt:pkt.protocolslen) ] + + +class TLS_Ext_Padding(TLS_Ext_Unknown): # RFC 7685 + name = "TLS Extension - Padding" + fields_desc = [ShortEnumField("type", 0x15, _tls_ext), + FieldLenField("len", None, length_of="padding"), + StrLenField("padding", "", + length_from=lambda pkt: pkt.len) ] + + +class TLS_Ext_SessionTicket(TLS_Ext_Unknown): # RFC 5077 + """ + RFC 5077 updates RFC 4507 according to most implementations, which do not + use another (useless) 'ticketlen' field after the global 'len' field. + """ + name = "TLS Extension - Session Ticket" + fields_desc = [ShortEnumField("type", 0x23, _tls_ext), + FieldLenField("len", None, length_of="ticket"), + StrLenField("ticket", "", + length_from=lambda pkt: pkt.len) ] + + +class TLS_Ext_NPN(TLS_Ext_PrettyPacketList): + """ + Defined in RFC-draft-agl-tls-nextprotoneg-03. Deprecated in favour of ALPN. + """ + name = "TLS Extension - Next Protocol Negotiation" + fields_desc = [ShortEnumField("type", 0x3374, _tls_ext), + FieldLenField("len", None, length_of="protocols"), + ProtocolListField("protocols", [], ProtocolName, + length_from=lambda pkt:pkt.len) ] + + +class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 + name = "TLS Extension - Renegotiation Indication" + fields_desc = [ShortEnumField("type", 0xff01, _tls_ext), + ShortField("len", None), + FieldLenField("reneg_conn_len", None, fmt='B', + length_of="renegotiated_connection"), + StrLenField("renegotiated_connection", "", + length_from=lambda pkt: pkt.reneg_conn_len) ] + + +_tls_ext_cls = { 0: TLS_Ext_ServerName, + 1: TLS_Ext_MaxFragLen, + 2: TLS_Ext_ClientCertURL, + 3: TLS_Ext_TrustedCAInd, + 4: TLS_Ext_TruncatedHMAC, + 5: TLS_Ext_CSR, + 6: TLS_Ext_UserMapping, + 7: TLS_Ext_ClientAuthz, + 8: TLS_Ext_ServerAuthz, + 9: _TLS_Ext_CertTypeDispatcher, + 10: TLS_Ext_SupportedEllipticCurves, + 11: TLS_Ext_SupportedPointFormat, + 13: TLS_Ext_SignatureAlgorithms, + 0x0f: TLS_Ext_Heartbeat, + 0x10: TLS_Ext_ALPN, + 0x15: TLS_Ext_Padding, + 0x23: TLS_Ext_SessionTicket, + 0x3374: TLS_Ext_NPN, + 0xff01: TLS_Ext_RenegotiationInfo + } + + +class _ExtensionsLenField(FieldLenField): + """ + This field provides the first half of extensions support implementation + as defined in RFC 3546. The second is provided by _ExtensionsField. Both + are used as the last fields at the end of ClientHello messages. + + The idea is quite simple: + - dissection : the _ExtensionsLenField will compute the remaining length of + the message based on the value of a provided field (for instance 'msglen' + in ClientHello) and a list of other fields that are considered "shifters". + This shifters are length fields of some vectors. The sum of their value + will be substracted to the one of the main field. If the result is + positive, this means that extensions are present and the + _ExtensionsLenField behaves just like a normal FieldLenField. If the value + is null, invalid or not sufficient to grab a length, the getfield method of + the field will simply return a 0 value without "eating" bytes from current + string. In a sense, the field is always present (which means that its value + is available for the _ExtensionsField field) but the behavior during + dissection is conditional. Then, the _ExtensionsField uses the length value + from the _ExtensionsLenField, to know how much data it should grab (TLS + extension is basically a vector). If no extensions are present, the length + field will have a null value and nothing will be grabbed. + + - build: during build, if some extensions are provided, the + _ExtensionsLenField will automatically access the whole length and use it + if the user does not provide a specific value. Now, if no extensions are + available and the user does not provide a specific value, nothing is added + during the build, i.e. no length field with a null value will appear. As a + side note, this is also the case for the rebuild of a dissected packet: if + the initial packet had a length field with a null value, one will be built. + If no length field was was present, nothing is added, i.e. a rebuilt + dissected packet will look like the original. Another side note is that the + shifters allow us to decide if there is an extension vector but the length + of that vector is grabbed from the value of the 2 first bytes, not from the + value computed from shifters and msglen. + """ + __slots__ = ["lfld", "shifters"] + def __init__(self, name, default, + lfld, shifters=[], + fmt="!H", length_of=None): + FieldLenField.__init__(self, name, default, + fmt=fmt, length_of=length_of) + self.lfld = lfld + self.shifters = shifters + + def getfield(self, pkt, s): + # compute the length of remaining data to see if there are ext + l = getattr(pkt, self.lfld) + for fname in self.shifters: + if type(fname) is int: + l -= fname + else: + l -= getattr(pkt, fname) + + if l is None or l <= 0 or l < self.sz: + return s, None # let's consider there's no extensions + + return Field.getfield(self, pkt, s) + + def addfield(self, pkt, s, i): + if i is None: + if self.length_of is not None: + fld,fval = pkt.getfield_and_val(self.length_of) + f = fld.i2len(pkt, fval) + i = self.adjust(pkt, f) + if i == 0: # for correct build if no ext and not explicitly 0 + return s + return s + struct.pack(self.fmt, i) + +class _ExtensionsField(StrLenField): + """ + See ExtensionsLenField documentation. + """ + islist=1 + holds_packets=1 + + def i2len(self, pkt, i): + if i is None: + return 0 + return len(self.i2m(pkt, i)) + + def getfield(self, pkt, s): + l = self.length_from(pkt) + if l is None: + return s, [] + return s[l:], self.m2i(pkt, s[:l]) + + def i2m(self, pkt, i): + if i is None: + return "" + return "".join(map(str, i)) + + def m2i(self, pkt, m): + res = [] + while m: + t = struct.unpack("!H", m[:2])[0] + l = struct.unpack("!H", m[2:4])[0] + cls = _tls_ext_cls.get(t, TLS_Ext_Unknown) + res.append(cls(m[:l+4], tls_session=pkt.tls_session)) + m = m[l+4:] + return res + + +############################################################################### +### ClientHello ### +############################################################################### + +class TLSClientHello(_TLSHandshake): + """ + TLS ClientHello, with abilities to handle extensions. + + The Random structure follows the RFC 5246: while it is 32-byte long, + many implementations use the first 4 bytes as a gmt_unix_time, and then + the remaining 28 byts should be completely random. This was designed in + order to (sort of) mitigate broken RNGs. If you prefer to show the full + 32 random bytes without any GMT time, just comment in/out the lines below. + """ + name = "TLS Handshake - Client Hello" + fields_desc = [ ByteEnumField("msgtype", 1, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSVersionField("version", 0x0303, _tls_version), + + #_TLSRandomBytesField("random_bytes", None, 32), + _GMTUnixTimeField("gmt_unix_time", None), + _TLSRandomBytesField("random_bytes", None, 28), + + FieldLenField("sidlen", None, fmt="B", length_of="sid"), + _SessionIDField("sid", "", + length_from=lambda pkt:pkt.sidlen), + + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + _CipherSuitesField("ciphers", + [TLS_DHE_RSA_WITH_AES_128_CBC_SHA], + _tls_cipher_suites, itemfmt="!H", + length_from=lambda pkt: pkt.cipherslen), + + FieldLenField("complen", None, fmt="B", length_of="comp"), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from= + lambda pkt: pkt.complen), + + _ExtensionsLenField("extlen", None, "msglen", + shifters = ["sidlen", "cipherslen", + "complen", 38], + length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: pkt.extlen) ] + + def post_build(self, p, pay): + if self.random_bytes is None: + p = p[:10] + randstring(28) + p[10+28:] + return super(TLSClientHello, self).post_build(p, pay) + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the client_random + along with the raw string representing this handshake message. + """ + self.tls_session.advertised_tls_version = self.version + self.random_bytes = msg_str[10:38] + self.tls_session.client_random = (struct.pack('!I', + self.gmt_unix_time) + + self.random_bytes) + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + +############################################################################### +### ServerHello ### +############################################################################### + +class TLSServerHello(TLSClientHello): + """ + TLS ServerHello, with abilities to handle extensions. + + The Random structure follows the RFC 5246: while it is 32-byte long, + many implementations use the first 4 bytes as a gmt_unix_time, and then + the remaining 28 byts should be completely random. This was designed in + order to (sort of) mitigate broken RNGs. If you prefer to show the full + 32 random bytes without any GMT time, just comment in/out the lines below. + """ + name = "TLS Handshake - Server Hello" + fields_desc = [ ByteEnumField("msgtype", 2, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSVersionField("version", None, _tls_version), + + #_TLSRandomBytesField("random_bytes", None, 32), + _GMTUnixTimeField("gmt_unix_time", None), + _TLSRandomBytesField("random_bytes", None, 28), + + FieldLenField("sidlen", None, length_of="sid", fmt="B"), + _SessionIDField("sid", "", + length_from = lambda pkt: pkt.sidlen), + + EnumField("cipher", None, _tls_cipher_suites), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: 1), + + _ExtensionsLenField("extlen", None, "msglen", + shifters = ["sidlen", 38], + length_of="ext"), + _ExtensionsField("ext", [], + length_from=lambda pkt: pkt.extlen) ] + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the server_random + along with the raw string representing this handshake message. + We also store the session_id, the cipher suite (if recognized), + the compression method, and finally we instantiate the pending write + and read connection states. Usually they get updated later on in the + negotiation when we learn the session keys, and eventually they + are committed once a ChangeCipherSpec has been sent/received. + """ + self.tls_session.tls_version = self.version + self.random_bytes = msg_str[10:38] + self.tls_session.server_random = (struct.pack('!I', + self.gmt_unix_time) + + self.random_bytes) + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + self.tls_session.sid = self.sid + + if self.cipher: + cs_val = self.cipher + if not _tls_cipher_suites_cls.has_key(cs_val): + warning("Unknown cipher suite %d from ServerHello" % cs_val) + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + if self.comp: + comp_val = self.comp[0] + if not _tls_compression_algs_cls.has_key(comp_val): + err = "Unknown compression alg %d from ServerHello" % comp_val + warning(err) + comp_val = 0 + comp_cls = _tls_compression_algs_cls[comp_val] + + connection_end = self.tls_session.connection_end + self.tls_session.pwcs = writeConnState(ciphersuite=cs_cls, + compression_alg=comp_cls, + connection_end=connection_end, + tls_version=self.version) + self.tls_session.prcs = readConnState(ciphersuite=cs_cls, + compression_alg=comp_cls, + connection_end=connection_end, + tls_version=self.version) + + +############################################################################### +### Certificate ### +############################################################################### + +class _ASN1CertLenField(FieldLenField): + """ + This is mostly a 3-byte FieldLenField. + """ + def __init__(self, name, default, length_of=None, adjust=lambda pkt, x: x): + self.length_of = length_of + self.adjust = adjust + Field.__init__(self, name, default, fmt="!I") + + def i2m(self, pkt, x): + if x is None: + if self.length_of is not None: + fld,fval = pkt.getfield_and_val(self.length_of) + f = fld.i2len(pkt, fval) + x = self.adjust(pkt, f) + return x + + def addfield(self, pkt, s, val): + return s + struct.pack(self.fmt, self.i2m(pkt,val))[1:4] + + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, struct.unpack(self.fmt, "\x00" + s[:3])[0]) + + +class _ASN1CertListField(StrLenField): + islist = 1 + def i2len(self, pkt, i): + if i is None: + return 0 + return len(self.i2m(pkt, i)) + + def getfield(self, pkt, s): + """ + Extract Certs in a loop. + XXX We should providesafeguards when trying to parse a Cert. + """ + l = None + if self.length_from is not None: + l = self.length_from(pkt) + + lst = [] + ret = "" + m = s + if l is not None: + m, ret = s[:l], s[l:] + while m: + clen = struct.unpack("!I", '\x00' + m[:3])[0] + lst.append((clen, Cert(m[3:3 + clen]))) + m = m[3 + clen:] + return m + ret, lst + + def i2m(self, pkt, i): + def i2m_one(i): + if type(i) is str: + return i + if isinstance(i, Cert): + s = i.der + l = struct.pack("!I", len(s))[1:4] + return l + s + + (l, s) = i + if isinstance(s, Cert): + s = s.der + return struct.pack("!I", l)[1:4] + s + + if i is None: + return "" + if type(i) is str: + return i + if isinstance(i, Cert): + i = [i] + return "".join(map(lambda x: i2m_one(x), i)) + + def any2i(self, pkt, x): + return x + + +class TLSCertificate(_TLSHandshake): + """ + XXX We do not support RFC 5081, i.e. OpenPGP certificates. + """ + name = "TLS Handshake - Certificate" + fields_desc = [ ByteEnumField("msgtype", 11, _tls_handshake_type), + ThreeBytesField("msglen", None), + _ASN1CertLenField("certslen", None, length_of="certs"), + _ASN1CertListField("certs", [], + length_from = lambda pkt: pkt.certslen) ] + + def post_dissection_tls_session_update(self, msg_str): + connection_end = self.tls_session.connection_end + if connection_end == "client": + self.tls_session.server_certs = map(lambda x: x[1], self.certs) + else: + self.tls_session.client_certs = map(lambda x: x[1], self.certs) + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + +############################################################################### +### ServerKeyExchange ### +############################################################################### + +class TLSServerKeyExchange(_TLSHandshake): + name = "TLS Handshake - Server Key Exchange" + fields_desc = [ ByteEnumField("msgtype", 12, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSServerParamsField("params", None, + length_from=lambda pkt: pkt.msglen), + _TLSSignatureField("sig", None, + length_from=lambda pkt: pkt.msglen - len(pkt.params)) ] + + def build(self, *args, **kargs): + """ + We overload build() method in order to provide a valid default value + for params based on TLS session if not provided. This cannot be done by + overriding i2m() because the method is called on a copy of the packet. + + The 'params' field is built according to key_exchange.server_kx_msg_cls + which should have been set after receiving a cipher suite in a + previous ServerHello. Usual cases are: + - None: for RSA encryption or fixed FF/ECDH. This should never happen, + as no ServerKeyExchange should be generated in the first place. + - ServerDHParams: for ephemeral FFDH. In that case, the parameter to + server_kx_msg_cls does not matter. + - ServerECDH*Params: for ephemeral ECDH. There are actually three + classes, which are dispatched by _tls_server_ecdh_cls_guess on + the first byte retrieved. The default here is "\03", which + corresponds to ServerECDHNamedCurveParams (implicit curves). + + When the Server*DHParams are built via .fill_missing(), the session + server_kx_params and client_kx_params will be updated accordingly. + """ + fval = self.getfieldval("params") + if fval is None: + s = self.tls_session + if s.pwcs: + if s.pwcs.key_exchange.export: + cls = ServerRSAParams(tls_session=s) + else: + cls = s.pwcs.key_exchange.server_kx_msg_cls("\x03") + cls = cls(tls_session=s) + try: + cls.fill_missing() + except: + pass + else: + cls = Raw() + self.params = cls + + fval = self.getfieldval("sig") + if fval is None: + s = self.tls_session + if s.pwcs: + if not s.pwcs.key_exchange.anonymous: + p = self.params + if p is None: + p = "" + m = s.client_random + s.server_random + str(p) + cls = _TLSSignature(tls_session=s) + cls._update_sig(m, s.server_key) + else: + cls = Raw() + else: + cls = Raw() + self.sig = cls + + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + """ + While previously dissecting Server*DHParams, the session + server_kx_params and client_kx_params should have been updated. + + XXX Add a 'fixed_dh' OR condition to the 'anonymous' test. + """ + s = self.tls_session + if s.prcs and s.prcs.key_exchange.anonymous: + print "USELESS SERVER KEY EXCHANGE" + if (s.client_random and s.server_random and + s.server_certs and len(s.server_certs) > 0): + m = s.client_random + s.server_random + str(self.params) + sig_test = self.sig._verify_sig(m, s.server_certs[0]) + if not sig_test: + print "INVALID SERVER KEY EXCHANGE SIGNATURE" + + +############################################################################### +### CertificateRequest ### +############################################################################### + +_tls_client_certificate_types = { 1: "rsa_sign", + 2: "dss_sign", + 3: "rsa_fixed_dh", + 4: "dss_fixed_dh", + 5: "rsa_ephemeral_dh_RESERVED", + 6: "dss_ephemeral_dh_RESERVED", + 20: "fortezza_dms_RESERVED", + 64: "ecdsa_sign", + 65: "rsa_fixed_ecdh", + 66: "ecdsa_fixed_ecdh" } + + +class _CertTypesField(_CipherSuitesField): + pass + +class _CertAuthoritiesField(StrLenField): + """ + XXX Rework this with proper ASN.1 parsing. + """ + islist = 1 + + def getfield(self, pkt, s): + l = self.length_from(pkt) + return s[l:], self.m2i(pkt, s[:l]) + + def m2i(self, pkt, m): + res = [] + while len(m) > 1: + l = struct.unpack("!H", m[:2])[0] + if len(m) < l + 2: + res.append((l, m[2:])) + break + dn = m[2:2+l] + res.append((l, dn)) + m = m[2+l:] + return res + + def i2m(self, pkt, i): + return "".join(map(lambda (x,y): struct.pack("!H", x) + y, i)) + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def i2len(self, pkt, val): + if val is None: + return 0 + else: + return len(self.i2m(pkt, val)) + + +class TLSCertificateRequest(_TLSHandshake): + name = "TLS Handshake - Certificate Request" + fields_desc = [ ByteEnumField("msgtype", 13, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("ctypeslen", None, fmt="B", + length_of="ctypes"), + _CertTypesField("ctypes", [], + _tls_client_certificate_types, + itemfmt="!B", + length_from=lambda pkt: pkt.ctypeslen), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [], + EnumField("hash_sig", None, + _tls_hash_sig), + length_from= + lambda pkt: pkt.sig_algs_len), + FieldLenField("certauthlen", None, fmt="!H", + length_of="certauth"), + _CertAuthoritiesField("certauth", [], + length_from= + lambda pkt: pkt.certauthlen) ] + + +############################################################################### +### ServerHelloDone ### +############################################################################### + +class TLSServerHelloDone(_TLSHandshake): + name = "TLS Handshake - Server Hello Done" + fields_desc = [ ByteEnumField("msgtype", 14, _tls_handshake_type), + ThreeBytesField("msglen", None) ] + + +############################################################################### +### CertificateVerify ### +############################################################################### + +class TLSCertificateVerify(_TLSHandshake): + name = "TLS Handshake - Certificate Verify" + fields_desc = [ ByteEnumField("msgtype", 15, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSSignatureField("sig", None, + length_from = lambda pkt: pkt.msglen) ] + + def build(self, *args, **kargs): + sig = self.getfieldval("sig") + if sig is None: + s = self.tls_session + m = "".join(s.handshake_messages) + self.sig = _TLSSignature(tls_session=s) + self.sig._update_sig(m, s.client_key) + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + m = "".join(s.handshake_messages) + if s.client_certs and len(s.client_certs) > 0: + sig_test = self.sig._verify_sig(m, s.client_certs[0]) + if not sig_test: + print "INVALID CERTIFICATE VERIFY SIGNATURE" + + +############################################################################### +### ClientKeyExchange ### +############################################################################### + +class _TLSCKExchKeysField(PacketField): + __slots__ = ["length_from"] + holds_packet = 1 + def __init__(self, name, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, None, None, remain=remain) + + def m2i(self, pkt, m): + """ + The client_kx_msg may be either None, EncryptedPreMasterSecret + (for RSA encryption key exchange), ClientDiffieHellmanPublic, + or ClientECDiffieHellmanPublic. When either one of them gets + dissected, the session context is updated accordingly. + """ + l = self.length_from(pkt) + tbd, rem = m[:l], m[l:] + + s = pkt.tls_session + cls = None + + if s.prcs and s.prcs.key_exchange: + cls = s.prcs.key_exchange.client_kx_msg_cls + + if cls is None: + return Raw(tbd)/Padding(rem) + + return cls(tbd, tls_session=s)/Padding(rem) + + +class TLSClientKeyExchange(_TLSHandshake): + """ + This class mostly works like TLSServerKeyExchange and its 'params' field. + """ + name = "TLS Handshake - Client Key Exchange" + fields_desc = [ ByteEnumField("msgtype", 16, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSCKExchKeysField("exchkeys", + length_from = lambda pkt: pkt.msglen) ] + + def build(self, *args, **kargs): + fval = self.getfieldval("exchkeys") + if fval is None: + s = self.tls_session + if s.prcs: + cls = s.prcs.key_exchange.client_kx_msg_cls + cls = cls(tls_session=s) + else: + cls = Raw() + self.exchkeys = cls + return _TLSHandshake.build(self, *args, **kargs) + + +############################################################################### +### Finished ### +############################################################################### + +class _VerifyDataField(StrLenField): + def getfield(self, pkt, s): + if pkt.tls_session.tls_version == 0x300: + sep = 36 + else: + sep = 12 + return s[sep:], s[:sep] + +class TLSFinished(_TLSHandshake): + name = "TLS Handshake - Finished" + fields_desc = [ ByteEnumField("msgtype", 20, _tls_handshake_type), + ThreeBytesField("msglen", None), + _VerifyDataField("vdata", None) ] + + def build(self, *args, **kargs): + fval = self.getfieldval("vdata") + if fval is None: + s = self.tls_session + handshake_msg = "".join(s.handshake_messages) + ms = s.master_secret + con_end = s.connection_end + self.vdata = s.wcs.prf.compute_verify_data(con_end, "write", + handshake_msg, ms) + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + handshake_msg = "".join(s.handshake_messages) + if s.master_secret is not None: + ms = s.master_secret + con_end = s.connection_end + verify_data = s.rcs.prf.compute_verify_data(con_end, "read", + handshake_msg, ms) + if self.vdata != verify_data: + print "INVALID TLS FINISHED RECEIVED" + + +## Additional handshake messages + +############################################################################### +### HelloVerifyRequest ### +############################################################################### + +class TLSHelloVerifyRequest(_TLSHandshake): + """ + Defined for DTLS, see RFC 6347. + """ + name = "TLS Handshake - Hello Verify Request" + fields_desc = [ ByteEnumField("msgtype", 21, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("cookielen", None, + fmt="B", length_of="cookie"), + StrLenField("cookie", "", + length_from=lambda pkt: pkt.cookielen) ] + + +############################################################################### +### CertificateURL ### +############################################################################### + +_tls_cert_chain_types = { 0: "individual_certs", + 1: "pkipath" } + +class URLAndOptionalHash(Packet): + name = "URLAndOptionHash structure for TLSCertificateURL" + fields_desc = [ FieldLenField("urllen", None, length_of="url"), + StrLenField("url", "", + length_from=lambda pkt: pkt.urllen), + FieldLenField("hash_present", None, + fmt="B", length_of="hash", + adjust=lambda pkt,x: int(math.ceil(x/20.))), + StrLenField("hash", "", + length_from=lambda pkt: 20*pkt.hash_present) ] + def guess_payload_class(self, p): + return Padding + +class TLSCertificateURL(_TLSHandshake): + """ + Defined in RFC 4366. PkiPath structure of section 8 is not implemented yet. + """ + name = "TLS Handshake - Certificate URL" + fields_desc = [ ByteEnumField("msgtype", 21, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("certchaintype", None, _tls_cert_chain_types), + FieldLenField("uahlen", None, length_of="uah"), + PacketListField("uah", [], URLAndOptionalHash, + length_from=lambda pkt: pkt.uahlen) ] + + +############################################################################### +### CertificateStatus ### +############################################################################### + +class ThreeBytesLenField(FieldLenField): + def __init__(self, name, default, length_of=None, adjust=lambda pkt, x:x): + FieldLenField.__init__(self, name, default, length_of=length_of, + fmt='!I', adjust=adjust) + def i2repr(self, pkt, x): + if x is None: + return 0 + return repr(self.i2h(pkt,x)) + def addfield(self, pkt, s, val): + return s+struct.pack(self.fmt, self.i2m(pkt,val))[1:4] + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, struct.unpack(self.fmt, "\x00"+s[:3])[0]) + +_cert_status_cls = { 1: OCSP_Response } + +class _StatusField(PacketField): + def m2i(self, pkt, m): + idtype = pkt.status_type + cls = self.cls + if _cert_status_cls.has_key(idtype): + cls = _cert_status_cls[idtype] + return cls(m) + +class TLSCertificateStatus(_TLSHandshake): + name = "TLS Handshake - Certificate Status" + fields_desc = [ ByteEnumField("msgtype", 22, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("status_type", 1, _cert_status_type), + ThreeBytesLenField("responselen", None, + length_of="response"), + _StatusField("response", None, Raw) ] + + +############################################################################### +### SupplementalData ### +############################################################################### + +class SupDataEntry(Packet): + name = "Supplemental Data Entry - Generic" + fields_desc = [ ShortField("sdtype", None), + FieldLenField("len", None, length_of="data"), + StrLenField("data", "", + length_from=lambda pkt:pkt.len) ] + def guess_payload_class(self, p): + return Padding + +class UserMappingData(Packet): + name = "User Mapping Data" + fields_desc = [ ByteField("version", None), + FieldLenField("len", None, length_of="data"), + StrLenField("data", "", + length_from=lambda pkt: pkt.len)] + def guess_payload_class(self, p): + return Padding + +class SupDataEntryUM(Packet): + name = "Supplemental Data Entry - User Mapping" + fields_desc = [ ShortField("sdtype", None), + FieldLenField("len", None, length_of="data", + adjust=lambda pkt, x: x+2), + FieldLenField("dlen", None, length_of="data"), + PacketListField("data", [], UserMappingData, + length_from=lambda pkt:pkt.dlen) ] + def guess_payload_class(self, p): + return Padding + +class TLSSupplementalData(_TLSHandshake): + name = "TLS Handshake - Supplemental Data" + fields_desc = [ ByteEnumField("msgtype", 23, _tls_handshake_type), + ThreeBytesField("msglen", None), + ThreeBytesLenField("sdatalen", None, length_of="sdata"), + PacketListField("sdata", [], SupDataEntry, + length_from=lambda pkt: pkt.sdatalen) ] + + +############################################################################### +### NewSessionTicket ### +############################################################################### + +class Ticket(Packet): + name = "Recommended Ticket Construction" + fields_desc = [ StrFixedLenField("key_name", None, 16), + StrFixedLenField("iv", None, 16), + FieldLenField("estatelen", None, length_of="estate"), + StrLenField("estate", "", + length_from=lambda pkt: pkt.estatelen), + StrFixedLenField("mac", None, 32) ] + +class TLSNewSessionTicket(_TLSHandshake): + """ + XXX When knowing the right secret, we should be able to read the ticket. + """ + name = "TLS Handshake - New Session Ticket" + fields_desc = [ ByteEnumField("msgtype", 4, _tls_handshake_type), + ThreeBytesField("msglen", None), + IntField("lifetime", 0xffffffff), + FieldLenField("ticketlen", None, length_of="ticket"), + StrLenField("ticket", "", + length_from=lambda pkt: pkt.msglen) ] + + +############################################################################### +### All handshake messages defined in this module ### +############################################################################### + +_tls_handshake_cls = { 0: TLSHelloRequest, 1: TLSClientHello, + 2: TLSServerHello, 3: TLSHelloVerifyRequest, + 4: TLSNewSessionTicket, 11: TLSCertificate, + 12: TLSServerKeyExchange, 13: TLSCertificateRequest, + 14: TLSServerHelloDone, 15: TLSCertificateVerify, + 16: TLSClientKeyExchange, 20: TLSFinished, + 21: TLSCertificateURL, 22: TLSCertificateStatus, + 23: TLSSupplementalData } + diff --git a/scapy/layers/tls/keyexchange.py b/scapy/layers/tls/keyexchange.py new file mode 100644 index 0000000000000000000000000000000000000000..cfff57b6408c1df21de7c1119196a5edd8dcebc0 --- /dev/null +++ b/scapy/layers/tls/keyexchange.py @@ -0,0 +1,861 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS key exchange logic. +""" + +import math + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import dh, ec, rsa + +from scapy.config import conf +from scapy.error import warning +from scapy.fields import * +from scapy.packet import Packet, Raw, Padding +from scapy.layers.tls.cert import PubKeyRSA, PrivKeyRSA +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.basefields import _tls_version, _TLSClientVersionField +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +from scapy.layers.tls.crypto.ffdh import FFDH_GROUPS + + +############################################################################### +### Common Fields ### +############################################################################### + +_tls_hash_sig = { 0x0000: "none+anon", 0x0001: "none+rsa", + 0x0002: "none+dsa", 0x0003: "none+ecdsa", + 0x0100: "md5+anon", 0x0101: "md5+rsa", + 0x0102: "md5+dsa", 0x0103: "md5+ecdsa", + 0x0200: "sha1+anon", 0x0201: "sha1+rsa", + 0x0202: "sha1+dsa", 0x0203: "sha1+ecdsa", + 0x0300: "sha224+anon", 0x0301: "sha224+rsa", + 0x0302: "sha224+dsa", 0x0303: "sha224+ecdsa", + 0x0400: "sha256+anon", 0x0401: "sha256+rsa", + 0x0402: "sha256+dsa", 0x0403: "sha256+ecdsa", + 0x0500: "sha384+anon", 0x0501: "sha384+rsa", + 0x0502: "sha384+dsa", 0x0503: "sha384+ecdsa", + 0x0600: "sha512+anon", 0x0601: "sha512+rsa", + 0x0602: "sha512+dsa", 0x0603: "sha512+ecdsa" } + + +def phantom_mode(pkt): + """ + We expect this. If tls_version is not set, this means we did not process + any complete ClientHello, so we're most probably reading/building a + signature_algorithms extension, hence we cannot be in phantom_mode. + However, if the tls_version has been set, we test for TLS 1.2. + XXX Make this more generic. Also, factorize the classes below (metaclass?). + """ + if not pkt.tls_session: + return False + if not pkt.tls_session.tls_version: + return False + return pkt.tls_session.tls_version < 0x0303 + +def phantom_decorate(f, get_or_add): + """ + Decorator for version-dependent fields. + If get_or_add is True (means get), we return s, self.phantom_value. + If it is False (means add), we return s. + """ + def wrapper(*args): + self, pkt, s = args[:3] + if phantom_mode(pkt): + if get_or_add: + return s, self.phantom_value + return s + return f(*args) + return wrapper + +class SigAndHashAlgField(EnumField): + """ + Used in _TLSSignature. + """ + phantom_value = None + getfield = phantom_decorate(EnumField.getfield, True) + addfield = phantom_decorate(EnumField.addfield, False) + +class SigAndHashAlgsLenField(FieldLenField): + """ + Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest. + """ + phantom_value = 0 + getfield = phantom_decorate(FieldLenField.getfield, True) + addfield = phantom_decorate(FieldLenField.addfield, False) + +class SigAndHashAlgsField(FieldListField): + """ + Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest. + """ + phantom_value = [] + getfield = phantom_decorate(FieldListField.getfield, True) + addfield = phantom_decorate(FieldListField.addfield, False) + + +class _TLSSignature(_GenericTLSSessionInheritance): + """ + Prior to TLS 1.2, digitally-signed structure implictly used the + concatenation of a SHA-1 hash and a MD5 hash (this is the 'tls' mode + of key signing). TLS 1.2 introduced explicit SignatureAndHashAlgorithms, + i.e. couples of (hash_alg, sig_alg). See RFC 5246, section 7.4.1.4.1. + + By default, the _TLSSignature implements the TLS 1.2 scheme, + but if it is provided a TLS context with a tls_version < 0x0303 + at initialization, it will fall back to the implicit signature. + + #XXX 'sig_alg' should be set in __init__ depending on the context. + """ + name = "TLS Digital Signature" + fields_desc = [ SigAndHashAlgField("sig_alg", 0x0401, _tls_hash_sig), + FieldLenField("sig_len", None, fmt="!H", + length_of="sig_val"), + StrLenField("sig_val", None, + length_from = lambda pkt: pkt.sig_len) ] + + def __init__(self, *args, **kargs): + _GenericTLSSessionInheritance.__init__(self, *args, **kargs) + if ("tls_session" in kargs and + kargs["tls_session"].tls_version and + kargs["tls_session"].tls_version < 0x0303): + self.sig_alg = None + + def _update_sig(self, m, key): + """ + Sign 'm' with the PrivKey 'key' and update our own 'sig_val'. + Note that, even when 'sig_alg' is not None, we use the signature scheme + of the PrivKey (neither do we care to compare the both of them). + """ + if self.sig_alg is None: + self.sig_val = key.sign(m, t='pkcs', h='tls') + else: + h = _tls_hash_sig[self.sig_alg].split('+')[0] + self.sig_val = key.sign(m, t='pkcs', h=h) + + def _verify_sig(self, m, cert): + """ + Verify that our own 'sig_val' carries the signature of 'm' by the + key associated to the Cert 'cert'. + """ + if self.sig_val: + if self.sig_alg: + h = _tls_hash_sig[self.sig_alg].split('+')[0] + return cert.verify(m, self.sig_val, t='pkcs', h=h) + else: + return cert.verify(m, self.sig_val, t='pkcs', h='tls') + return False + + def guess_payload_class(self, p): + return Padding + +class _TLSSignatureField(PacketField): + """ + Used for 'digitally-signed struct' in several ServerKeyExchange, + and also in CertificateVerify. We can handle the anonymous case. + """ + __slots__ = ["length_from"] + def __init__(self, name, default, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, default, _TLSSignature, remain=remain) + + def m2i(self, pkt, m): + l = self.length_from(pkt) + if l == 0: + return None + return _TLSSignature(m, tls_session=pkt.tls_session) + + +class _TLSServerParamsField(PacketField): + """ + This is a dispatcher for the Server*DHParams below, used in + TLSServerKeyExchange and based on the key_exchange.server_kx_msg_cls. + When this cls is None, it means that we should not see a ServerKeyExchange, + so we grab everything within length_from and make it available using Raw. + + When the context has not been set (e.g. when no ServerHello was parsed or + dissected beforehand), we (kinda) clumsily set the cls by trial and error. + XXX We could use Serv*DHParams.check_params() once it has been implemented. + """ + __slots__ = ["length_from"] + def __init__(self, name, default, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, default, None, remain=remain) + + def m2i(self, pkt, m): + s = pkt.tls_session + l = self.length_from(pkt) + if s.prcs: + cls = s.prcs.key_exchange.server_kx_msg_cls(m) + if cls is None: + return None, Raw(m[:l])/Padding(m[l:]) + return cls(m, tls_session=s) + else: + try: + p = ServerDHParams(m, tls_session=s) + if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: + raise Exception + return p + except: + cls = _tls_server_ecdh_cls_guess(m) + p = cls(m, tls_session=s) + if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: + return None, Raw(m[:l])/Padding(m[l:]) + return p + + +############################################################################### +### Server Key Exchange parameters & value ### +############################################################################### + +### Finite Field Diffie-Hellman + +class ServerDHParams(_GenericTLSSessionInheritance): + """ + ServerDHParams for FFDH-based key exchanges, + as it is defined in RFC 5246, section 7.4.3. + + Either with .fill_missing() or .post_dissection(), the server_kx_params and + client_kx_params of the TLS context are updated according to the + parsed/assembled values. It is the user's responsibility to store and + restore the original values if he wants to keep them. For instance, this + could be done between the writing of a ServerKeyExchange and the receiving + of a ClientKeyExchange (which includes secret generation). + """ + name = "Server FFDH parameters" + fields_desc = [ FieldLenField("dh_plen", None, length_of="dh_p"), + StrLenField("dh_p", "", + length_from=lambda pkt: pkt.dh_plen), + FieldLenField("dh_glen", None, length_of="dh_g"), + StrLenField("dh_g", "", + length_from=lambda pkt: pkt.dh_glen), + FieldLenField("dh_Yslen", None, length_of="dh_Ys"), + StrLenField("dh_Ys", "", + length_from=lambda pkt: pkt.dh_Yslen) ] + + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things everytime it is called. This method can be called specifically + to have things filled in a smart fashion. + + Note that we do not expect dh_params_def.g to be more than 0xff. + """ + s = self.tls_session + + default_params = FFDH_GROUPS['modp2048'][0].parameter_numbers() + default_mLen = FFDH_GROUPS['modp2048'][1] + + if self.dh_p is "": + self.dh_p = pkcs_i2osp(default_params.p, default_mLen/8) + if self.dh_plen is None: + self.dh_plen = len(self.dh_p) + + if self.dh_g is "": + self.dh_g = pkcs_i2osp(default_params.g, 1) + if self.dh_glen is None: + self.dh_glen = 1 + + p = pkcs_os2ip(self.dh_p) + g = pkcs_os2ip(self.dh_g) + real_params = dh.DHParameterNumbers(p, g).parameters(default_backend()) + + if self.dh_Ys is "": + s.server_kx_privkey = real_params.generate_private_key() + pubkey = s.server_kx_privkey.public_key() + y = pubkey.public_numbers().y + self.dh_Ys = pkcs_i2osp(y, pubkey.key_size/8) + # else, we assume that the user wrote the server_kx_privkey by himself + if self.dh_Yslen is None: + self.dh_Yslen = len(self.dh_Ys) + + if not s.client_kx_ffdh_params: + s.client_kx_ffdh_params = real_params + + def post_dissection(self, r): + """ + XXX Check that the pubkey received is in the group. + """ + #if self.dh_g and self.dh_p and self.dh_Ys: #XXX remove this, probably + p = pkcs_os2ip(self.dh_p) + g = pkcs_os2ip(self.dh_g) + pn = dh.DHParameterNumbers(p, g) + + y = pkcs_os2ip(self.dh_Ys) + public_numbers = dh.DHPublicNumbers(y, pn) + + s = self.tls_session + s.server_kx_pubkey = public_numbers.public_key(default_backend()) + + if not s.client_kx_ffdh_params: + s.client_kx_ffdh_params = pn.parameters(default_backend()) + + def guess_payload_class(self, p): + """ + The signature after the params gets saved as Padding. + This way, the .getfield() which _TLSServerParamsField inherits + from PacketField will return the signature remain as expected. + """ + return Padding + + +### Elliptic Curve Diffie-Hellman + +_tls_ec_curve_types = { 1: "explicit_prime", + 2: "explicit_char2", + 3: "named_curve" } + +_tls_named_curves = { 1: "sect163k1", 2: "sect163r1", 3: "sect163r2", + 4: "sect193r1", 5: "sect193r2", 6: "sect233k1", + 7: "sect233r1", 8: "sect239k1", 9: "sect283k1", + 10: "sect283r1", 11: "sect409k1", 12: "sect409r1", + 13: "sect571k1", 14: "sect571r1", 15: "secp160k1", + 16: "secp160r1", 17: "secp160r2", 18: "secp192k1", + 19: "secp192r1", 20: "secp224k1", 21: "secp224r1", + 22: "secp256k1", 23: "secp256r1", 24: "secp384r1", + 25: "secp521r1", 26: "brainpoolP256r1", + 27: "brainpoolP384r1", 28: "brainpoolP512r1", + 0xff01: "arbitrary_explicit_prime_curves", + 0xff02: "arbitrary_explicit_char2_curves"} + +_tls_ec_basis_types = { 0: "ec_basis_trinomial", 1: "ec_basis_pentanomial"} + +class ECCurvePkt(Packet): + name = "Elliptic Curve" + fields_desc = [ FieldLenField("alen", None, length_of="a", fmt="B"), + StrLenField("a", "", length_from = lambda pkt: pkt.alen), + FieldLenField("blen", None, length_of="b", fmt="B"), + StrLenField("b", "", length_from = lambda pkt: pkt.blen) ] + + +## Char2 Curves + +class ECTrinomialBasis(Packet): + name = "EC Trinomial Basis" + val = 0 + fields_desc = [ FieldLenField("klen", None, length_of="k", fmt="B"), + StrLenField("k", "", length_from = lambda pkt: pkt.klen) ] + def guess_payload_class(self, p): + return Padding + +class ECPentanomialBasis(Packet): + name = "EC Pentanomial Basis" + val = 1 + fields_desc = [ FieldLenField("k1len", None, length_of="k1", fmt="B"), + StrLenField("k1", "", length_from=lambda pkt: pkt.k1len), + FieldLenField("k2len", None, length_of="k2", fmt="B"), + StrLenField("k2", "", length_from=lambda pkt: pkt.k2len), + FieldLenField("k3len", None, length_of="k3", fmt="B"), + StrLenField("k3", "", length_from=lambda pkt: pkt.k3len) ] + def guess_payload_class(self, p): + return Padding + +_tls_ec_basis_cls = { 0: ECTrinomialBasis, 1: ECPentanomialBasis} + +class _ECBasisTypeField(ByteEnumField): + __slots__ = ["basis_type_of"] + def __init__(self, name, default, enum, basis_type_of, remain=0): + self.basis_type_of = basis_type_of + EnumField.__init__(self, name, default, enum, "B") + + def i2m(self, pkt, x): + if x is None: + val = 0 + fld,fval = pkt.getfield_and_val(self.basis_type_of) + x = fld.i2basis_type(pkt, fval) + return x + +class _ECBasisField(PacketField): + __slots__ = ["clsdict", "basis_type_from"] + def __init__(self, name, default, basis_type_from, clsdict, remain=0): + self.clsdict = clsdict + self.basis_type_from = basis_type_from + PacketField.__init__(self, name, default, None, remain=remain) + + def m2i(self, pkt, m): + basis = self.basis_type_from(pkt) + cls = self.clsdict[basis] + return cls(m) + + def i2basis_type(self, pkt, x): + val = 0 + try: + val = x.val + except: + pass + return val + + +## Distinct ECParameters +## +## To support the different ECParameters structures defined in Sect. 5.4 of +## RFC 4492, we define 3 separates classes for implementing the 3 associated +## ServerECDHParams: ServerECDHNamedCurveParams, ServerECDHExplicitPrimeParams +## and ServerECDHExplicitChar2Params (support for this one is only partial). +## The most frequent encounter of the 3 is (by far) ServerECDHNamedCurveParams. + +class ServerECDHExplicitPrimeParams(_GenericTLSSessionInheritance): + """ + XXX We provide parsing abilities for ExplicitPrimeParams, but there is no + 'cryptography' support, hence no context operations. + """ + name = "Server ECDH parameters - Explicit Prime" + fields_desc = [ ByteEnumField("curve_type", 1, _tls_ec_curve_types), + FieldLenField("plen", None, length_of="p", fmt="B"), + StrLenField("p", "", length_from=lambda pkt: pkt.plen), + PacketField("curve", None, ECCurvePkt), + FieldLenField("baselen", None, length_of="base", fmt="B"), + StrLenField("base", "", + length_from=lambda pkt: pkt.baselen), + FieldLenField("orderlen", None, + length_of="order", fmt="B"), + StrLenField("order", "", + length_from=lambda pkt: pkt.orderlen), + FieldLenField("cofactorlen", None, + length_of="cofactor", fmt="B"), + StrLenField("cofactor", "", + length_from=lambda pkt: pkt.cofactorlen), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", "", + length_from=lambda pkt: pkt.pointlen) ] + + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things everytime it is called. This method can be called specifically + to have things filled in a smart fashion. + + XXX Note that if it is not set by the user, the cofactor will always + be 1. It is true for most, but not all, TLS elliptic curves. + + XXX Try and create a curve with the 'cryptography' lib somehow, + extract the missing fields for filling, then set s.server_kx_privkey. + """ + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["explicit_prime"] + + def post_dissection(self, pkt): + """ + XXX Store the server_kx_pubkey. + XXX Check that the pubkey received is on the curve. + """ + + def guess_payload_class(self, p): + return Padding + + +class ServerECDHExplicitChar2Params(_GenericTLSSessionInheritance): + """ + XXX We provide parsing abilities for Char2Params, but there is no + 'cryptography' support, hence no context operations. + """ + name = "Server ECDH parameters - Explicit Char2" + fields_desc = [ ByteEnumField("curve_type", 2, _tls_ec_curve_types), + ShortField("m", None), + _ECBasisTypeField("basis_type", None, + _tls_ec_basis_types, "basis"), + _ECBasisField("basis", ECTrinomialBasis(), + lambda pkt: pkt.basis_type, + _tls_ec_basis_cls), + PacketField("curve", ECCurvePkt(), ECCurvePkt), + FieldLenField("baselen", None, length_of="base", fmt="B"), + StrLenField("base", "", + length_from = lambda pkt: pkt.baselen), + ByteField("order", None), + ByteField("cofactor", None), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", "", + length_from = lambda pkt: pkt.pointlen) ] + + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things everytime it is called. This method can be called specifically + to have things filled in a smart fashion. + """ + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["explicit_char2"] + + def post_dissection(self, pkt): + """ + XXX Store the server_kx_pubkey. + XXX Check that the pubkey received is in the group. + """ + pass + + def guess_payload_class(self, p): + return Padding + + +class ServerECDHNamedCurveParams(_GenericTLSSessionInheritance): + name = "Server ECDH parameters - Named Curve" + fields_desc = [ ByteEnumField("curve_type", 3, _tls_ec_curve_types), + ShortEnumField("named_curve", None, _tls_named_curves), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", None, + length_from = lambda pkt: pkt.pointlen) ] + + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things everytime it is called. This method can be called specifically + to have things filled in a smart fashion. + XXX We should account for the point_format (before 'point' filling). + """ + s = self.tls_session + + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["named_curve"] + + if self.named_curve is None: + curve = ec.SECP256R1() + s.server_kx_privkey = ec.generate_private_key(curve, + default_backend()) + curve_id = 0 + for cid, name in _tls_named_curves.iteritems(): + if name == curve.name: + curve_id = cid + break + self.named_curve = curve_id + else: + curve_name = _tls_named_curves.get(self.named_curve) + if curve_name is None: + # this fallback is arguable + curve = ec.SECP256R1() + else: + curve_cls = ec._CURVE_TYPES.get(curve_name) + if curve_cls is None: + # this fallback is arguable + curve = ec.SECP256R1() + else: + curve = curve_cls() + s.server_kx_privkey = ec.generate_private_key(curve, + default_backend()) + + if self.point is None: + pubkey = s.server_kx_privkey.public_key() + self.point = pubkey.public_numbers().encode_point() + # else, we assume that the user wrote the server_kx_privkey by himself + if self.pointlen is None: + self.pointlen = len(self.point) + + if not s.client_kx_ecdh_params: + s.client_kx_ecdh_params = curve + + def post_dissection(self, r): + """ + XXX Support compressed point format. + XXX Check that the pubkey received is on the curve. + """ + #point_format = 0 + #if self.point[0] in ['\x02', '\x03']: + # point_format = 1 + + #if self.named_curve and self.point: #XXX remove this, probably + curve_name = _tls_named_curves[self.named_curve] + curve = ec._CURVE_TYPES[curve_name]() + import_point = ec.EllipticCurvePublicNumbers.from_encoded_point + pubnum = import_point(curve, self.point) + s = self.tls_session + s.server_kx_pubkey = pubnum.public_key(default_backend()) + + if not s.client_kx_ecdh_params: + s.client_kx_ecdh_params = curve + + def guess_payload_class(self, p): + return Padding + + +_tls_server_ecdh_cls = { 1: ServerECDHExplicitPrimeParams, + 2: ServerECDHExplicitChar2Params, + 3: ServerECDHNamedCurveParams } + +def _tls_server_ecdh_cls_guess(m): + if not m: + return None + curve_type = ord(m[0]) + return _tls_server_ecdh_cls.get(curve_type, None) + + +### RSA Encryption (export) + +class ServerRSAParams(_GenericTLSSessionInheritance): + """ + Defined for RSA_EXPORT kx : it enables servers to share RSA keys shorter + than their principal {>512}-bit key, when it is not allowed for kx. + + This should not appear in standard RSA kx negotiation, as the key + has already been advertised in the Certificate message. + """ + name = "Server RSA_EXPORT parameters" + fields_desc = [ FieldLenField("rsamodlen", None, length_of="rsamod"), + StrLenField("rsamod", "", + length_from = lambda pkt: pkt.rsamodlen), + FieldLenField("rsaexplen", None, length_of="rsaexp"), + StrLenField("rsaexp", "", + length_from = lambda pkt: pkt.rsaexplen) ] + + def fill_missing(self): + ext_k = rsa.generate_private_key(public_exponent=0x10001, + key_size=512, + backend=default_backend()) + pem_k = ext_k.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()) + k = PrivKeyRSA(pem_k) + self.tls_session.server_tmp_rsa_key = k + pubNum = k.pubkey.public_numbers() + + if self.rsamod is "": + self.rsamod = pkcs_i2osp(pubNum.n, k.pubkey.key_size/8) + if self.rsamodlen is None: + self.rsamodlen = len(self.rsamod) + + rsaexplen = math.ceil(math.log(pubNum.e)/math.log(2)/8.) + if self.rsaexp is "": + self.rsaexp = pkcs_i2osp(pubNum.e, rsaexplen) + if self.rsaexplen is None: + self.rsaexplen = len(self.rsaexp) + + def post_dissection(self, pkt): + mLen = self.rsamodlen + m = self.rsamod + e = self.rsaexp + self.tls_session.server_tmp_rsa_key = PubKeyRSA((e, m, mLen)) + + def guess_payload_class(self, p): + return Padding + + +### Pre-Shared Key + +class ServerPSKParams(Packet): + """ + XXX We provide some parsing abilities for ServerPSKParams, but the + context operations have not been implemented yet. See RFC 4279. + Note that we do not cover the (EC)DHE_PSK key exchange, + which should contain a Server*DHParams after 'psk_identity_hint'. + """ + name = "Server PSK parameters" + fields_desc = [ FieldLenField("psk_identity_hint_len", None, + length_of="psk_identity_hint", fmt="!H"), + StrLenField("psk_identity_hint", "", + length_from=lambda pkt: pkt.psk_identity_hint_len) ] + + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things everytime it is called. This method can be called specifically + to have things filled in a smart fashion. + """ + pass + + def post_dissection(self, pkt): + pass + + def guess_payload_class(self, p): + return Padding + + +############################################################################### +### Client Key Exchange value ### +############################################################################### + +### FFDH/ECDH + +class ClientDiffieHellmanPublic(_GenericTLSSessionInheritance): + """ + If the user provides a value for dh_Yc attribute, + the pms and ms are set accordingly when .post_build() is called. + + XXX As specified in 7.4.7.2. of RFC 4346, we should distinguish the needs + for implicit or explicit value depending on availability of DH parameters + in *client* certificate. For now we can only do ephemeral/explicit DH. + """ + name = "Client DH Public Value" + fields_desc = [ FieldLenField("dh_Yclen", None, length_of="dh_Yc"), + StrLenField("dh_Yc", "", + length_from=lambda pkt: pkt.dh_Yclen) ] + + def post_build(self, pkt, pay): + s = self.tls_session + + if self.dh_Yc == "": + params = s.client_kx_ffdh_params + s.client_kx_privkey = params.generate_private_key() + pubkey = s.client_kx_privkey.public_key() + y = pubkey.public_numbers().y + self.dh_Yc = pkcs_i2osp(y, pubkey.key_size/8) + # else, we assume that the user wrote the client_kx_privkey by himself + if self.dh_Yclen is None: + self.dh_Yclen = len(self.dh_Yc) + + if s.client_kx_privkey and s.server_kx_pubkey: + pms = s.client_kx_privkey.exchange(s.server_kx_pubkey) + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + return pkcs_i2osp(self.dh_Yclen, 2) + self.dh_Yc + pay + + def post_dissection(self, m): + """ + First we update the client DHParams. Then, we try to update the server + DHParams generated during Server*DHParams building, with the shared + secret. Finally, we derive the session keys and update the context. + """ + s = self.tls_session + + if s.client_kx_ffdh_params: + y = pkcs_os2ip(self.dh_Yc) + param_numbers = s.client_kx_ffdh_params.parameter_numbers() + public_numbers = dh.DHPublicNumbers(y, param_numbers) + s.client_kx_pubkey = public_numbers.public_key(default_backend()) + + if s.server_kx_privkey and s.client_kx_pubkey: + ZZ = s.server_kx_privkey.exchange(s.client_kx_pubkey) + s.pre_master_secret = ZZ + s.compute_ms_and_derive_keys() + + def guess_payload_class(self, p): + return Padding + +class ClientECDiffieHellmanPublic(_GenericTLSSessionInheritance): + """ + Note that the 'len' field is 1 byte longer than with the previous class. + """ + name = "Client ECDH Public Value" + fields_desc = [ FieldLenField("ecdh_Yclen", None, + length_of="ecdh_Yc", fmt="B"), + StrLenField("ecdh_Yc", "", + length_from=lambda pkt: pkt.ecdh_Yclen)] + + def post_build(self, pkt, pay): + s = self.tls_session + + if self.ecdh_Yc == "": + params = s.client_kx_ecdh_params + s.client_kx_privkey = ec.generate_private_key(params, + default_backend()) + pubkey = s.client_kx_privkey.public_key() + x = pubkey.public_numbers().x + y = pubkey.public_numbers().y + self.ecdh_Yc = ("\x04" + + pkcs_i2osp(x, params.key_size/8) + + pkcs_i2osp(y, params.key_size/8)) + # else, we assume that the user wrote the client_kx_privkey by himself + if self.ecdh_Yclen is None: + self.ecdh_Yclen = len(self.ecdh_Yc) + + if s.client_kx_privkey and s.server_kx_pubkey: + pms = s.client_kx_privkey.exchange(ec.ECDH(), s.server_kx_pubkey) + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + return pkcs_i2osp(self.ecdh_Yclen, 1) + self.ecdh_Yc + pay + + def post_dissection(self, m): + s = self.tls_session + + if s.client_kx_ecdh_params: + import_point = ec.EllipticCurvePublicNumbers.from_encoded_point + pub_num = import_point(s.client_kx_ecdh_params, self.ecdh_Yc) + s.client_kx_pubkey = pub_num.public_key(default_backend()) + + if s.server_kx_privkey and s.client_kx_pubkey: + ZZ = s.server_kx_privkey.exchange(ec.ECDH(), s.client_kx_pubkey) + s.pre_master_secret = ZZ + s.compute_ms_and_derive_keys() + + +### RSA Encryption (standard & export) + +class EncryptedPreMasterSecret(_GenericTLSSessionInheritance): + """ + Pay attention to implementation notes in section 7.4.7.1 of RFC 5246. + """ + name = "RSA Encrypted PreMaster Secret" + fields_desc = [ _TLSClientVersionField("client_version", None, + _tls_version), + StrFixedLenField("random", None, 46) ] + + def pre_dissect(self, m): + s = self.tls_session + tbd = m + if s.tls_version >= 0x0301: + if len(m) < 2: # Should not happen + return m + l = struct.unpack("!H", m[:2])[0] + if len(m) != l+2: + err = "TLS 1.0+, but RSA Encrypted PMS with no explicit length" + warning(err) + else: + tbd = m[2:] + if s.server_tmp_rsa_key is not None: + # priority is given to the tmp_key, if there is one + decrypted = s.server_tmp_rsa_key.decrypt(tbd) + pms = decrypted[-48:] + elif s.server_rsa_key is not None: + decrypted = s.server_rsa_key.decrypt(tbd) + pms = decrypted[-48:] + else: + pms = "\x00"*48 # Hack but we should not be there anyway + err = "No server RSA key to decrypt Pre Master Secret. Skipping." + warning(err) + + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + return pms + + def post_build(self, pkt, pay): + """ + We encrypt the premaster secret (the 48 bytes) with either the server + certificate or the temporary RSA key provided in a server key exchange + message. After that step, we add the 2 bytes to provide the length, as + described in implementation notes at the end of section 7.4.7.1. + """ + enc = pkt + + s = self.tls_session + s.pre_master_secret = enc + s.compute_ms_and_derive_keys() + + if s.server_tmp_rsa_key is not None: + enc = s.server_tmp_rsa_key.encrypt(pkt, "pkcs") + elif s.server_certs is not None and len(s.server_certs) > 0: + enc = s.server_certs[0].encrypt(pkt, "pkcs") + else: + warning("No material to encrypt Pre Master Secret") + + l = "" + if s.tls_version >= 0x0301: + l = struct.pack("!H", len(enc)) + return "%s%s%s" % (l, enc, pay) + + def guess_payload_class(self, p): + return Padding + + +# Pre-Shared Key + +class ClientPSKIdentity(Packet): + """ + XXX We provide parsing abilities for ServerPSKParams, but the context + operations have not been implemented yet. See RFC 4279. + Note that we do not cover the (EC)DHE_PSK nor the RSA_PSK key exchange, + which should contain either an EncryptedPMS or a ClientDiffieHellmanPublic. + """ + name = "Server PSK parameters" + fields_desc = [ FieldLenField("psk_identity_len", None, + length_of="psk_identity", fmt="!H"), + StrLenField("psk_identity", "", + length_from=lambda pkt: pkt.psk_identity_len) ] + diff --git a/scapy/layers/tls/record.py b/scapy/layers/tls/record.py new file mode 100644 index 0000000000000000000000000000000000000000..cebe3c21d38d551bb22d23268cc385a098946a3e --- /dev/null +++ b/scapy/layers/tls/record.py @@ -0,0 +1,664 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +Common TLS fields & bindings. + +This module covers the record layer, along with the ChangeCipherSpec, Alert and +ApplicationData submessages. For the Handshake type, see tls_handshake.py. + +See the TLS class documentation for more information. +""" + +import struct + +from scapy.config import conf +from scapy.fields import * +from scapy.packet import * +from scapy.layers.inet import TCP +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.handshake import _tls_handshake_cls, _TLSHandshake +from scapy.layers.tls.basefields import (_TLSVersionField, _tls_version, + _TLSIVField, _TLSMACField, + _TLSPadField, _TLSPadLenField, + _TLSLengthField, _tls_type) +from scapy.layers.tls.crypto.pkcs1 import randstring, pkcs_i2osp +from scapy.layers.tls.crypto.compression import Comp_NULL +from scapy.layers.tls.crypto.cipher_aead import AEADTagError +from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL +from scapy.layers.tls.crypto.ciphers import CipherError +from scapy.layers.tls.crypto.h_mac import HMACError + + +############################################################################### +### TLS Record Protocol ### +############################################################################### + +class _TLSEncryptedContent(Raw): + """ + When the content of a TLS record (more precisely, a TLSCiphertext) could + not be deciphered, we use this class to represent the encrypted data. + The MAC will still be parsed from the whole message, even though it could + not been verified. When present (depending on cipher type and protocol + version), the nonce_explicit, IV and/or padding will also be parsed. + """ + name = "Encrypted Content" + + +class _TLSMsgListField(PacketListField): + """ + This is the actual content of the TLS record. As a TLS record may pack + multiple sublayer messages (notably, several handshake messages), + we inherit from PacketListField. + """ + def __init__(self, name, default, length_from=None): + PacketListField.__init__(self, name, default, cls=None, + length_from=length_from) + + def m2i(self, pkt, m): + """ + Try to parse one of the TLS subprotocols (ccs, alert, handshake or + application_data). This is used inside a loop managed by .getfield(). + """ + cls = Raw + if pkt.type == 22: + if len(m) >= 1: + msgtype = ord(m[0]) + cls = _tls_handshake_cls.get(msgtype, Raw) + elif pkt.type == 20: + cls = TLSChangeCipherSpec + elif pkt.type == 21: + cls = TLSAlert + elif pkt.type == 23: + cls = TLSApplicationData + + if cls is Raw: + return Raw(m) + else: + return cls(m, tls_session=pkt.tls_session) + + def getfield(self, pkt, s): + """ + If the decryption of the content did not fail with a CipherError, + we begin a loop on the clear content in order to get as much messages + as possible, of the type advertised in the record header. This is + notably important for several TLS handshake implementations, which + may for instance pack a server_hello, a certificate, a + server_key_exchange and a server_hello_done, all in one record. + Each parsed message may update the TLS context throught their method + .post_dissection_tls_session_update(). + + If the decryption failed with a CipherError, presumably because we + missed the session keys, we signal it by returning a + _TLSEncryptedContent packet which simply contains the ciphered data. + """ + l = self.length_from(pkt) + lst = [] + ret = "" + remain = s + if l is not None: + remain, ret = s[:l], s[l:] + + if pkt.decipherable: + if remain == "": + return ret, [TLSApplicationData(data="")] + while remain: + raw_msg = remain + p = self.m2i(pkt, remain) + if Padding in p: + pad = p[Padding] + remain = pad.load + del(pad.underlayer.payload) + if len(remain) != 0: + raw_msg = raw_msg[:-len(remain)] + else: + remain = "" + + if not isinstance(p, Raw): + p.post_dissection_tls_session_update(raw_msg) + + lst.append(p) + return remain + ret, lst + else: + return ret, _TLSEncryptedContent(remain) + + def i2m(self, pkt, p): + """ + Update the context with information from the built packet. + If no type was given at the record layer, we try to infer it. + """ + cur = "" + if isinstance(p, _GenericTLSSessionInheritance): + if pkt.type is None: + if isinstance(p, TLSChangeCipherSpec): + pkt.type = 20 + elif isinstance(p, TLSAlert): + pkt.type = 21 + elif isinstance(p, _TLSHandshake): + pkt.type = 22 + elif isinstance(p, TLSApplicationData): + pkt.type = 23 + p.tls_session = pkt.tls_session + cur = str(p) + p.post_build_tls_session_update(cur) + else: + cur = str(p) + return cur + + def addfield(self, pkt, s, val): + """ + Reconstruct the header because the TLS type may have been updated. + Then, append the content. + """ + res = "" + for p in val: + res += self.i2m(pkt, p) + if not pkt.type: + pkt.type = 0 + hdr = struct.pack("!B", pkt.type) + s[1:5] + return hdr + res + + +class TLS(_GenericTLSSessionInheritance): + """ + The generic TLS Record message, based on section 6.2 of RFC 5246. + + When reading a TLS message, we try to parse as much as we can. + In .pre_dissect(), according to the type of the current cipher algorithm + (self.tls_session.rcs.cipher.type), we extract the 'iv', 'mac', 'pad' and + 'padlen'. Some of these fields may remain blank: for instance, when using + a stream cipher, there is no IV nor any padding. + + Once we have isolated the ciphered message aggregate (which should be one + or several TLS messages of the same type), we try to decipher it. Either we + succeed and store the clear data in 'msg', or we graciously fail with a + CipherError and store the ciphered data in 'msg'. + + Unless the user manually provides the session secrets through the passing + of a 'tls_session', obviously the ciphered messages will not be deciphered. + Indeed, the need for a proper context may also present itself when trying + to parse clear handshake messages. + + For instance, suppose you sniffed the beginning of a DHE-RSA negotiation: + t1 = TLS(<client_hello>) + t2 = TLS(<server_hello | certificate | server_key_exchange>) + t3 = TLS(<server_hello | certificate | server_key_exchange>, + tls_session=t1.tls_session) + As no context was passed to t2, neither was any client_random. Hence scapy + will not be able to verify the signature of the server_key_exchange inside + t2. However, it should be able to do so for t3, thanks to the tls_session. + The consequence of not having a complete TLS context is even more obvious + when trying to parse ciphered content, as we decribed before. + + Thus, in order to parse TLS-protected communications with scapy: + _either scapy reads every message from one side of the TLS connection and + builds every message from the other side (as such, it should know the + secrets needed for the generation of the pre_master_secret), while passing + the same tls_session context (this is how our automaton.py mostly works); + _or, if scapy did not build any TLS message, it has to create a TLS context + and feed it with secrets retrieved by whatever technique. Note that the + knowing the private key of the server certificate will not be sufficient + if a PFS ciphersuite was used. However, if you got a master_secret somehow, + use it with tls_session.(w|r)cs.derive_keys() and leave the rest to scapy. + + When building a TLS message, we expect the tls_session to have the right + parameters for ciphering. Else, .post_build() might fail. + """ + __slots__ = ["decipherable"] + name = "TLS" + fields_desc = [ ByteEnumField("type", None, _tls_type), + _TLSVersionField("version", None, _tls_version), + _TLSLengthField("len", None), + _TLSIVField("iv", None), + _TLSMsgListField("msg", None, + length_from=lambda pkt: pkt.len), + _TLSMACField("mac", None), + _TLSPadField("pad", None), + _TLSPadLenField("padlen", None) ] + + def __init__(self, *args, **kargs): + """ + As long as 'decipherable' is True, _TLSMsgListField will try to + decipher the content of the TLS message. Else, it will simply + store/deliver the ciphered version. + """ + self.decipherable = True + _GenericTLSSessionInheritance.__init__(self, *args, **kargs) + + + ### Parsing methods + + def _tls_auth_decrypt(self, hdr, s): + """ + Provided with the record header and AEAD-ciphered data, return the + sliced and clear tuple (nonce, TLSCompressed.fragment, mac). Note that + we still return the slicing of the original input in case of decryption + failure. Also, if the integrity check fails, a warning will be issued, + but we still return the sliced (unauthenticated) plaintext. + """ + try: + read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) + self.tls_session.rcs.seq_num += 1 + # self.type and self.version have not been parsed yet, + # this is why we need to look into the provided hdr. + add_data = read_seq_num + hdr[0] + hdr[1:3] + # Last two bytes of add_data are appended by the return function + return self.tls_session.rcs.cipher.auth_decrypt(add_data, s) + except CipherError as e: + self.decipherable = False + return e.args + except AEADTagError as e: + print "INTEGRITY CHECK FAILED" + return e.args + + def _tls_decrypt(self, s): + """ + Provided with stream- or block-ciphered data, return the clear version. + The cipher should have been updated with the right IV early on, + which should not be at the beginning of the input. + Note that we still return the slicing of the original input + in case of decryption failure. + """ + try: + return self.tls_session.rcs.cipher.decrypt(s) + except CipherError as e: + self.decipherable = False + return e.args + + def _tls_hmac_verify(self, hdr, msg, mac): + """ + Provided with the record header, the TLSCompressed.fragment and the + HMAC, return True if the HMAC is correct. If we could not compute the + HMAC because the key was missing, there is no sense in verifying + anything, thus we also return True. + + Meant to be used with a block cipher or a stream cipher. + It would fail with an AEAD cipher, because rcs.hmac would be None. + See RFC 5246, section 6.2.3. + """ + mac_len = self.tls_session.rcs.mac_len + if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL + return True + if len(mac) != mac_len: + return False + + read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) + self.tls_session.rcs.seq_num += 1 + alg = self.tls_session.rcs.hmac + + version = struct.unpack("!H", hdr[1:3])[0] + try: + if version > 0x300: + h = alg.digest(read_seq_num + hdr + msg) + elif version == 0x300: + h = alg.digest_sslv3(read_seq_num + hdr[0] + hdr[3:5] + msg) + else: + raise Exception("Unrecognized version.") + except HMACError: + h = mac + return h == mac + + def _tls_decompress(self, s): + """ + Provided with the TLSCompressed.fragment, + return the TLSPlaintext.fragment. + """ + alg = self.tls_session.rcs.compression + return alg.decompress(s) + + def pre_dissect(self, s): + """ + Decrypt, verify and decompress the message, + i.e. apply the previous methods according to the reading cipher type. + If the decryption was successful, 'len' will be the length of the + TLSPlaintext.fragment. Else, it should be the length of the + _TLSEncryptedContent. + """ + if len(s) < 5: + raise Exception("Invalid record: header is too short.") + + msglen = struct.unpack('!H', s[3:5])[0] + hdr, efrag, r = s[:5], s[5:5+msglen], s[msglen+5:] + + iv = mac = pad = "" + + cipher_type = self.tls_session.rcs.cipher.type + + if cipher_type == 'block': + version = struct.unpack("!H", s[1:3])[0] + + # Decrypt + if version >= 0x0302: + # Explicit IV for TLS 1.1 and 1.2 + block_size = self.tls_session.rcs.cipher.block_size + iv, efrag = efrag[:block_size], efrag[block_size:] + self.tls_session.rcs.cipher.iv = iv + pfrag = self._tls_decrypt(efrag) + hdr = hdr[:3] + struct.pack("!H", len(pfrag)) + else: + # Implicit IV for SSLv3 and TLS 1.0 + pfrag = self._tls_decrypt(efrag) + + # Excerpt below better corresponds to TLS 1.1 IV definition, + # but the result is the same as with TLS 1.2 anyway. + # This leading *IV* has been decrypted by _tls_decrypt with a + # random IV, hence it does not correspond to anything. + # What actually matters is that we got the first encrypted block + # in order to decrypt the second block (first data block). + #if version >= 0x0302: + # block_size = self.tls_session.rcs.cipher.block_size + # iv, pfrag = pfrag[:block_size], pfrag[block_size:] + # l = struct.unpack('!H', hdr[3:5])[0] + # hdr = hdr[:3] + struct.pack('!H', l-block_size) + + # Extract padding ('pad' actually includes the trailing padlen) + padlen = ord(pfrag[-1]) + 1 + mfrag, pad = pfrag[:-padlen], pfrag[-padlen:] + + # Extract MAC + l = self.tls_session.rcs.mac_len + if l != 0: + cfrag, mac = mfrag[:-l], mfrag[-l:] + else: + cfrag, mac = mfrag, "" + + # Verify integrity + hdr = hdr[:3] + struct.pack('!H', len(cfrag)) + is_mac_ok = self._tls_hmac_verify(hdr, cfrag, mac) + if not is_mac_ok: + print "INTEGRITY CHECK FAILED" + + elif cipher_type == 'stream': + # Decrypt + pfrag = self._tls_decrypt(efrag) + mfrag = pfrag + + # Extract MAC + l = self.tls_session.rcs.mac_len + if l != 0: + cfrag, mac = mfrag[:-l], mfrag[-l:] + else: + cfrag, mac = mfrag, "" + + # Verify integrity + hdr = hdr[:3] + struct.pack('!H', len(cfrag)) + is_mac_ok = self._tls_hmac_verify(hdr, cfrag, mac) + if not is_mac_ok: + print "INTEGRITY CHECK FAILED" + + elif cipher_type == 'aead': + # Authenticated encryption + # crypto/cipher_aead.py prints a warning for integrity failure + iv, cfrag, mac = self._tls_auth_decrypt(hdr, efrag) + + if self.decipherable: + frag = self._tls_decompress(cfrag) + else: + frag = cfrag + + reconstructed_body = iv + frag + mac + pad + + l = len(frag) + # note that we do not include the MAC, only the content + hdr = hdr[:3] + struct.pack("!H", l) + + return hdr + reconstructed_body + r + + def post_dissect(self, s): + """ + Commit the pending read state if it has been triggered. + We update nothing if the prcs was not set, as this probably means that + we're working out-of-context (and we need to keep the default rcs). + """ + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + return s + + def do_dissect_payload(self, s): + """ + Try to dissect the following data as a TLS message. + Note that overloading .guess_payload_class() would not be enough, + as the TLS session to be used would get lost. + """ + if s: + try: + p = TLS(s, _internal=1, _underlayer=self, + tls_session = self.tls_session) + except KeyboardInterrupt: + raise + except: + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + + + ### Building methods + + def _tls_compress(self, s): + """ + Provided with the TLSPlaintext.fragment, + return the TLSCompressed.fragment. + """ + alg = self.tls_session.wcs.compression + return alg.compress(s) + + def _tls_auth_encrypt(self, s): + """ + Return the TLSCiphertext.fragment for AEAD ciphers, i.e. the whole + GenericAEADCipher. Also, the additional data is computed right here. + """ + write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) + self.tls_session.wcs.seq_num += 1 + add_data = (write_seq_num + + pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s), 2)) + return self.tls_session.wcs.cipher.auth_encrypt(s, add_data) + + def _tls_hmac_add(self, hdr, msg): + """ + Provided with the record header (concatenation of the TLSCompressed + type, version and length fields) and the TLSCompressed.fragment, + return the concatenation of the TLSCompressed.fragment and the HMAC. + + Meant to be used with a block cipher or a stream cipher. + It would fail with an AEAD cipher, because wcs.hmac would be None. + See RFC 5246, section 6.2.3. + """ + write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) + self.tls_session.wcs.seq_num += 1 + alg = self.tls_session.wcs.hmac + + version = struct.unpack("!H", hdr[1:3])[0] + if version > 0x300: + h = alg.digest(write_seq_num + hdr + msg) + elif version == 0x300: + h = alg.digest_sslv3(write_seq_num + hdr[0] + hdr[3:5] + msg) + else: + raise Exception("Unrecognized version.") + return msg + h + + def _tls_pad(self, s): + """ + Provided with the concatenation of the TLSCompressed.fragment and the + HMAC, append the right padding and return it as a whole. + This is the TLS-style padding: while SSL allowed for random padding, + TLS (misguidedly) specifies the repetition of the same byte all over, + and this byte must be equal to len(<entire padding>) - 1. + + Meant to be used with a block cipher only. + """ + padding = "" + block_size = self.tls_session.wcs.cipher.block_size + padlen = block_size - ((len(s) + 1) % block_size) + if padlen == block_size: + padlen = 0 + pad_pattern = chr(padlen) + padding = pad_pattern * (padlen + 1) + return s + padding + + def _tls_encrypt(self, s): + """ + Return the stream- or block-ciphered version of the concatenated input. + In case of GenericBlockCipher, no IV has been specifically prepended to + the output, so this might not be the whole TLSCiphertext.fragment yet. + """ + return self.tls_session.wcs.cipher.encrypt(s) + + def post_build(self, pkt, pay): + """ + Apply the previous methods according to the writing cipher type. + """ + # Compute the length of TLSPlaintext fragment + hdr, frag = pkt[:5], pkt[5:] + l = len(frag) + hdr = hdr[:3] + struct.pack("!H", l) + + # Compression + cfrag = self._tls_compress(frag) + l = len(cfrag) # Update the length as a result of compression + hdr = hdr[:3] + struct.pack("!H", l) + + cipher_type = self.tls_session.wcs.cipher.type + + if cipher_type == 'block': + # Integrity + mfrag = self._tls_hmac_add(hdr, cfrag) + + # Excerpt below better corresponds to TLS 1.1 IV definition, + # but the result is the same as with TLS 1.2 anyway. + #if self.version >= 0x0302: + # l = self.tls_session.wcs.cipher.block_size + # iv = randstring(l) + # mfrag = iv + mfrag + + # Add padding + pfrag = self._tls_pad(mfrag) + + # Encryption + if self.version >= 0x0302: + # Explicit IV for TLS 1.1 and 1.2 + l = self.tls_session.wcs.cipher.block_size + iv = randstring(l) + self.tls_session.wcs.cipher.iv = iv + efrag = self._tls_encrypt(pfrag) + efrag = iv + efrag + else: + # Implicit IV for SSLv3 and TLS 1.0 + efrag = self._tls_encrypt(pfrag) + + elif cipher_type == "stream": + # Integrity + mfrag = self._tls_hmac_add(hdr, cfrag) + # Encryption + efrag = self._tls_encrypt(mfrag) + + elif cipher_type == "aead": + # Authenticated encryption (with nonce_explicit as header) + efrag = self._tls_auth_encrypt(cfrag) + + # Now, we can commit pending write state if needed + # We update nothing if the pwcs was not set. This probably means that + # we're working out-of-context (and we need to keep the default wcs). + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + + if self.len is not None: + # The user gave us a 'len', let's respect this ultimately + hdr = hdr[:3] + struct.pack("!H", self.len) + else: + # Update header with the length of TLSCiphertext.fragment + hdr = hdr[:3] + struct.pack("!H", len(efrag)) + + return hdr + efrag + pay + + +############################################################################### +### TLS ChangeCipherSpec ### +############################################################################### + +_tls_changecipherspec_type = { 1: "change_cipher_spec" } + +class TLSChangeCipherSpec(_GenericTLSSessionInheritance): + """ + Note that, as they are not handshake messages, the ccs messages do not get + appended to the list of messages whose integrity gets verified through the + Finished messages. + """ + name = "TLS ChangeCipherSpec" + fields_desc = [ ByteEnumField("msgtype", 1, _tls_changecipherspec_type) ] + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session.triggered_prcs_commit = True + + def post_build_tls_session_update(self, msg_str): + # Unlike for dissection case, we cannot commit pending write + # state as current write state. We need to delay this after + # the ChangeCipherSpec message has indeed been sent + self.tls_session.triggered_pwcs_commit = True + + +############################################################################### +### TLS Alert ### +############################################################################### + +_tls_alert_level = { 1: "warning", 2: "fatal"} + +_tls_alert_description = { + 0: "close_notify", 10: "unexpected_message", + 20: "bad_record_mac", 21: "decryption_failed", + 22: "record_overflow", 30: "decompression_failure", + 40: "handshake_failure", 41: "no_certificate_RESERVED", + 42: "bad_certificate", 43: "unsupported_certificate", + 44: "certificate_revoked", 45: "certificate_expired", + 46: "certificate_unknown", 47: "illegal_parameter", + 48: "unknown_ca", 49: "access_denied", + 50: "decode_error", 51: "decrypt_error", + 60: "export_restriction_RESERVED", 70: "protocol_version", + 71: "insufficient_security", 80: "internal_error", + 90: "user_canceled", 100: "no_renegotiation", + 110: "unsupported_extension", 111: "certificate_unobtainable", + 112: "unrecognized_name", 113: "bad_certificate_status_response", + 114: "bad_certificate_hash_value", 115: "unknown_psk_identity" } + +class TLSAlert(_GenericTLSSessionInheritance): + name = "TLS Alert" + fields_desc = [ ByteEnumField("level", None, _tls_alert_level), + ByteEnumField("descr", None, _tls_alert_description) ] + + def post_dissection_tls_session_update(self, msg_str): + pass + + def post_build_tls_session_update(self, msg_str): + pass + + +############################################################################### +### TLS Application Data ### +############################################################################### + +class TLSApplicationData(_GenericTLSSessionInheritance): + name = "TLS Application Data" + fields_desc = [ StrField("data", "") ] + + def post_dissection_tls_session_update(self, msg_str): + pass + + def post_build_tls_session_update(self, msg_str): + pass + + +############################################################################### +### Bindings ### +############################################################################### + +bind_bottom_up(TCP, TLS, {"dport": 443}) +bind_bottom_up(TCP, TLS, {"sport": 443}) + diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py new file mode 100644 index 0000000000000000000000000000000000000000..0c781552ccf1ef65e86f5465250331a4ca1c4019 --- /dev/null +++ b/scapy/layers/tls/session.py @@ -0,0 +1,577 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS session handler. +""" + +import random +import socket +import struct + +from scapy.config import conf +from scapy.error import warning +from scapy.packet import Packet +from scapy.utils import repr_hex +from scapy.layers.tls.crypto.compression import Comp_NULL +from scapy.layers.tls.crypto.prf import PRF + +# Note the following import may happen inside connState.__init__() +# in order to avoid to avoid cyclical dependancies. +# from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL + + +############################################################################### +### Connection states ### +############################################################################### + +class connState(object): + """ + From RFC 5246, section 6.1: + A TLS connection state is the operating environment of the TLS Record + Protocol. It specifies a compression algorithm, an encryption + algorithm, and a MAC algorithm. In addition, the parameters for + these algorithms are known: the MAC key and the bulk encryption keys + for the connection in both the read and the write directions. + Logically, there are always four connection states outstanding: the + current read and write states, and the pending read and write states. + All records are processed under the current read and write states. + The security parameters for the pending states can be set by the TLS + Handshake Protocol, and the ChangeCipherSpec can selectively make + either of the pending states current, in which case the appropriate + current state is disposed of and replaced with the pending state; the + pending state is then reinitialized to an empty state. It is illegal + to make a state that has not been initialized with security + parameters a current state. The initial current state always + specifies that no encryption, compression, or MAC will be used. + + These attributes and behaviours are mostly mapped in this class. + Also, note that scapy may make a current state out of a pending state + which has been initialized with dummy security parameters. We need + this in order to know when the content of a TLS message is encrypted, + whether we possess the right keys to decipher/verify it or not. + For instance, when scapy parses a CKE without knowledge of any secret, + and then a CCS, it needs to know that the following Finished + is encrypted and signed according to a new cipher suite, even though + it cannot decipher the message nor verify its integrity. + """ + + def __init__(self, + connection_end="client", + read_or_write="read", + compression_alg=Comp_NULL, + ciphersuite=None, + tls_version=0x0303): + + # It is the user's responsibility to keep the record seq_num + # under 2**64-1. If this value gets maxed out, the TLS class in + # record.py will crash when trying to encode it with struct.pack(). + self.seq_num = 0 + + self.connection_end = connection_end + self.row = read_or_write + + if ciphersuite is None: + from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL + ciphersuite = TLS_NULL_WITH_NULL_NULL + + self.ciphersuite = ciphersuite(tls_version=tls_version) + + self.compression = compression_alg() + self.key_exchange = ciphersuite.kx_alg() + self.prf = PRF(ciphersuite.hash_alg.name, tls_version) + + # The attributes below usually get updated by .derive_keys() + # As discussed, we need to initialize cipher and mac with dummy values. + + self.master_secret = None # 48-byte shared secret + self.cipher_secret = None # key for the symmetric cipher + self.mac_secret = None # key for the MAC (stays None for AEAD) + + self.cipher = ciphersuite.cipher_alg() + + if ciphersuite.hmac_alg is None: # AEAD + self.hmac = None + self.mac_len = self.cipher.tag_len + else: + self.hmac = ciphersuite.hmac_alg() + self.mac_len = self.hmac.hmac_len + + def debug_repr(self, name, secret): + if conf.debug_tls and secret: + print "%s %s %s: %s" % (self.connection_end, + self.row, + name, + repr_hex(secret)) + + def derive_keys(self, + client_random="", + server_random="", + master_secret=""): + + cs = self.ciphersuite + self.master_secret = master_secret + + # Derive the keys according to the cipher type and protocol version + key_block = self.prf.derive_key_block(master_secret, + server_random, + client_random, + cs.key_block_len) + + # When slicing the key_block, keep the right half of the material + skip_first = False + if ((self.connection_end == "client" and self.row == "read") or + (self.connection_end == "server" and self.row == "write")): + skip_first = True + + pos = 0 + cipher_alg = cs.cipher_alg + + ### MAC secret (for block and stream ciphers) + if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): + start = pos + if skip_first: + start += cs.hmac_alg.key_len + end = start + cs.hmac_alg.key_len + self.mac_secret = key_block[start:end] + self.debug_repr("mac_secret", self.mac_secret) + pos += 2*cs.hmac_alg.key_len + else: + self.mac_secret = None + + ### Cipher secret + start = pos + if skip_first: + start += cipher_alg.key_len + end = start + cipher_alg.key_len + key = key_block[start:end] + if cs.kx_alg.export: + reqLen = cipher_alg.expanded_key_len + key = self.prf.postprocess_key_for_export(key, + client_random, + server_random, + self.connection_end, + self.row, + reqLen) + self.cipher_secret = key + self.debug_repr("cipher_secret", self.cipher_secret) + pos += 2*cipher_alg.key_len + + ### Implicit IV (for block and AEAD ciphers) + start = pos + if cipher_alg.type == "block": + if skip_first: + start += cipher_alg.block_size + end = start + cipher_alg.block_size + elif cipher_alg.type == "aead": + if skip_first: + start += cipher_alg.salt_len + end = start + cipher_alg.salt_len + + ### Now we have the secrets, we can instantiate the algorithms + if cs.hmac_alg is None: # AEAD + self.hmac = None + self.mac_len = cipher_alg.tag_len + else: + self.hmac = cs.hmac_alg(self.mac_secret) + self.mac_len = self.hmac.hmac_len + + if cipher_alg.type == "stream": + cipher = cipher_alg(self.cipher_secret) + elif cipher_alg.type == "block": + # We set an IV every time, even though it does not matter for + # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv + # would get updated in TLS.post_build() or TLS.pre_dissect(). + iv = key_block[start:end] + if cs.kx_alg.export: + reqLen = cipher_alg.block_size + iv = self.prf.generate_iv_for_export(client_random, + server_random, + self.connection_end, + self.row, + reqLen) + cipher = cipher_alg(self.cipher_secret, iv) + self.debug_repr("block iv", iv) + elif cipher_alg.type == "aead": + salt = key_block[start:end] + nonce_explicit_init = 0 + # If you ever wanted to set a random nonce_explicit, use this: + #exp_bit_len = cipher_alg.nonce_explicit_len * 8 + #nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) + cipher = cipher_alg(self.cipher_secret, salt, nonce_explicit_init) + self.debug_repr("aead salt", salt) + self.cipher = cipher + + def __repr__(self): + def indent(s): + if s and s[-1] == '\n': + s = s[:-1] + s = '\n'.join(map(lambda x: '\t'+x, s.split('\n')) + ['']) + return s + + res = "Connection end : %s\n" % self.connection_end.upper() + res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, + self.ciphersuite.val) + res += "Compression Alg: %s (0x%02x)\n" % (self.compression.name, + self.compression.val) + tabsize = 4 + return res.expandtabs(tabsize) + +class readConnState(connState): + def __init__(self, **kargs): + connState.__init__(self, read_or_write="read", **kargs) + +class writeConnState(connState): + def __init__(self, **kargs): + connState.__init__(self, read_or_write="write", **kargs) + + +############################################################################### +### TLS session ### +############################################################################### + +class tlsSession(object): + """ + This is our TLS context, which gathers information from both sides of the + TLS connection. These sides are represented by a readConnState instance and + a writeConnState instance. Along with overarching network attributes, a + tlsSession object also holds negotiated, shared information, such as the + key exchange parameters and the master secret (when available). + """ + def __init__(self, + ipsrc=None, ipdst=None, + sport=None, dport=None, sid=None, + connection_end="client", + wcs=None, rcs=None): + + ### Network settings + + self.ipsrc = ipsrc + self.ipdst = ipdst + self.sport = sport + self.dport = dport + self.sid = sid + + # Our TCP socket. None until we send (or receive) a packet. + self.sock = None + + + ### Connection states + + self.connection_end = connection_end + + if wcs is None: + self.wcs = writeConnState(connection_end=connection_end) + self.wcs.derive_keys(client_random="", + server_random="", + master_secret="") + if rcs is None: + self.rcs = readConnState(connection_end=connection_end) + self.rcs.derive_keys(client_random="", + server_random="", + master_secret="") + + # The pending write/read states are updated by the building/parsing + # of various TLS packets. They get committed to self.wcs/self.rcs + # once scapy builds/parses a ChangeCipherSpec message. + self.pwcs = None + self.triggered_pwcs_commit = False + self.prcs = None + self.triggered_prcs_commit = False + + + ### Certificates and private keys + + # The server certificate chain, as a list of Cert instances. + # Either we act as server and it has to be provided, or it is expected + # to be sent by the server through a Certificate message. + # The server certificate should be self.server_certs[0]. + self.server_certs = [] + + # The server private key, as a PrivKey instance, when acting as server. + # XXX It would be nice to be able to provide both an RSA and an ECDSA + # key in order for the same scapy server to support both families of + # cipher suites. See INIT_TLS_SESSION() in automaton.py. + # (For now server_key holds either one of both types for DHE + # authentication, while server_rsa_key is used only for RSAkx.) + self.server_key = None + self.server_rsa_key = None + #self.server_ecdsa_key = None + + # Back in the dreadful EXPORT days, US servers were forbidden to use + # RSA keys longer than 512 bits for RSAkx. When their usual RSA key + # was longer than this, they had to create a new key and send it via + # a ServerRSAParams message. When receiving such a message, + # scapy stores this key in server_tmp_rsa_key as a PubKey instance. + self.server_tmp_rsa_key = None + + # When client authentication is performed, we need at least a + # client certificate chain. If we act as client, we also have + # to provide the key associated with the first certificate. + self.client_certs = [] + self.client_key = None + + + ### Ephemeral key exchange parameters + + ## XXX Explain why we need pubkey (which should be contained in privkey) + # also, params is used to hold params between the SKE and the CKE + self.server_kx_privkey = None + self.server_kx_pubkey = None + self.client_kx_privkey = None + self.client_kx_pubkey = None + + self.client_kx_ffdh_params = None + self.client_kx_ecdh_params = None + + ## Either an instance of FFDHParams or ECDHParams. + ## Depending on which side of the connection we operate, + ## one of these params will not hold 'priv' and 'secret' attributes. + ## We did not use these intermediaries for RSAkx, as the 'priv' would + ## equate the PrivKey, and the 'secret' the pre_master_secret. + ## (It could have been useful for RSAkx export, though...) + #self.server_kx_params = None + #self.client_kx_params = None + + + ### Negotiated session parameters + + # The advertised TLS version found in the ClientHello (and + # EncryptedPreMasterSecret if used). If acting as server, it is set to + # the value advertised by the client in its ClientHello. + #XXX See what needs to be changed in automaton.py in order to keep + # this to None. For now it is necessary for running the client. + self.advertised_tls_version = 0x303 + + # The agreed-upon TLS version found in the ServerHello. + self.tls_version = None + + # These attributes should eventually be known to both sides. + self.client_random = None + self.server_random = None + self.pre_master_secret = None + self.master_secret = None + + # Handshake messages needed for Finished computation/validation. + # No record layer headers, no HelloRequests, no ChangeCipherSpecs. + self.handshake_messages = [] + self.handshake_messages_parsed = [] + + # All exchanged TLS packets. + self.exchanged_pkts = [] + + + ### Master secret management + + def compute_master_secret(self): + if self.pre_master_secret is None: + warning("Missing pre_master_secret while computing master_secret") + if self.client_random is None: + warning("Missing client_random while computing master_secret") + if self.server_random is None: + warning("Missing server_random while computing master_secret") + + ms = self.pwcs.prf.compute_master_secret(self.pre_master_secret, + self.client_random, + self.server_random) + self.master_secret = ms + if conf.debug_tls: + print "master secret: %s" % repr_hex(ms) + + def compute_ms_and_derive_keys(self): + self.compute_master_secret() + self.prcs.derive_keys(client_random=self.client_random, + server_random=self.server_random, + master_secret=self.master_secret) + self.pwcs.derive_keys(client_random=self.client_random, + server_random=self.server_random, + master_secret=self.master_secret) + + + ### Tests for record building/parsing + + def consider_read_padding(self): + # Return True if padding is needed. Used by TLSPadField. + return self.rcs.cipher.type == "block" + + def consider_write_padding(self): + # Return True if padding is needed. Used by TLSPadField. + return self.wcs.cipher.type == "block" + + def use_explicit_iv(self, version, cipher_type): + # Return True if an explicit IV is needed. Required for TLS 1.1+ + # when either a block or an AEAD cipher is used. + if cipher_type == "stream": + return False + return version >= 0x0302 + + + ### Python object management + + def hash(self): + s1 = struct.pack("!H", self.sport) + s2 = struct.pack("!H", self.dport) + family = socket.AF_INET + if ':' in self.ipsrc: + family = socket.AF_INET6 + s1 += socket.inet_pton(family, self.ipsrc) + s2 += socket.inet_pton(family, self.ipdst) + return strxor(s1, s2) + + def eq(self, other): + ok = False + if (self.sport == other.sport and self.dport == other.dport and + self.ipsrc == other.ipsrc and self.ipdst == other.ipdst): + ok = True + + if (not ok and + self.dport == other.sport and self.sport == other.dport and + self.ipdst == other.ipsrc and self.ipsrc == other.ipdst): + ok = True + + if ok: + if self.sid and other.sid: + return self.sid == other.sid + return True + + return False + + def __repr__(self): + sid = repr(self.sid) + if len(sid) > 12: + sid = sid[:11] + "..." + return "%s:%s > %s:%s" % (self.ipsrc, str(self.sport), + self.ipdst, str(self.dport)) + + +############################################################################### +### Session singleton ### +############################################################################### + +class _GenericTLSSessionInheritance(Packet): + """ + Many classes inside the TLS module need to get access to session-related + information. For instance, an encrypted TLS record cannot be parsed without + some knowledge of the cipher suite being used and the secrets which have + been negotiated. Passing information is also essential to the handshake. + To this end, various TLS objects inherit from the present class. + """ + __slots__ = ["tls_session"] + name = "Dummy Generic TLS Packet" + fields_desc = [] + + def __init__(self, _pkt="", post_transform=None, _internal=0, + _underlayer=None, tls_session=None, **fields): + try: + setme = self.tls_session is None + except: + setme = True + + if setme: + if tls_session is None: + self.tls_session = tlsSession() + else: + self.tls_session = tls_session + + Packet.__init__(self, _pkt=_pkt, post_transform=post_transform, + _internal=_internal, _underlayer=_underlayer, + **fields) + + def tls_session_update(self, msg_str): + """ + post_{build, dissection}_tls_session_update() are used to update the + tlsSession context. The default definitions below, along with + tls_session_update(), may prevent code duplication in some cases. + """ + pass + + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + def copy(self): + pkt = Packet.copy(self) + pkt.tls_session = self.tls_session + return pkt + + def clone_with(self, payload=None, **kargs): + pkt = Packet.clone_with(self, payload=payload, **kargs) + pkt.tls_session = self.tls_session + return pkt + + def show2(self): + """ + Rebuild the TLS packet with the same context, and then .show() it. + We need self.__class__ to call the subclass in a dynamic way. + """ + self.__class__(str(self), tls_session=self.tls_session).show() + + # Uncomment this when the automata update IPs and ports properly + #def mysummary(self): + # return "TLS %s" % repr(self.tls_session) + + +############################################################################### +### Multiple TLS sessions ### +############################################################################### + +class _tls_sessions(object): + def __init__(self): + self.sessions = {} + + def add(self, session): + s = self.find(session) + if s: + print "TLS session already exists. Not adding..." + return + + h = session.hash() + if self.sessions.has_key(h): + self.sessions[h].append(session) + else: + self.sessions[h] = [session] + + def rem(self, session): + s = self.find(session) + if s: + print "TLS session does not exist. Not removing..." + return + + h = session.hash() + self.sessions[h].remove(session) + + def find(self, session): + h = session.hash() + if self.sessions.has_key(h): + for k in self.sessions[h]: + if k.eq(session): + if conf.tls_verbose: + print "Found Matching session %s" % k + return k + if conf.tls_verbose: + print "Did not find matching session %s" % session + return None + + def __repr__(self): + res = [("First endpoint", "Second endpoint", "Session ID")] + for l in self.sessions.values(): + for s in l: + src = "%s[%d]" % (s.ipsrc, s.sport) + dst = "%s[%d]" % (s.ipdst, s.dport) + sid = repr(s.sid) + if len(sid) > 12: + sid = sid[:11] + "..." + res.append((src, dst, sid)) + colwidth = map(lambda x: max(map(lambda y: len(y), x)), + apply(zip, res)) + fmt = " ".join(map(lambda x: "%%-%ds"%x, colwidth)) + return "\n".join(map(lambda x: fmt % x, res)) + + +conf.tls_sessions = _tls_sessions() +conf.tls_verbose = False + diff --git a/scapy/layers/tls/tools.py b/scapy/layers/tls/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..921e669ce735987b93c62e82c7b5aca860c17e71 --- /dev/null +++ b/scapy/layers/tls/tools.py @@ -0,0 +1,211 @@ +## This file is part of Scapy +## Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +## 2015, 2016 Maxence Tury +## This program is published under a GPLv2 license + +""" +TLS helpers, provided as out-of-context methods. +""" + +from scapy.error import warning +from scapy.fields import (ByteEnumField, ShortEnumField, + FieldLenField, StrLenField) +from scapy.packet import Packet + +from scapy.layers.tls.basefields import _tls_type, _tls_version + + +class TLSPlaintext(Packet): + name = "TLS Plaintext" + fields_desc = [ ByteEnumField("type", None, _tls_type), + ShortEnumField("version", None, _tls_version), + FieldLenField("len", None, length_of="fragment", + fmt="!H"), + StrLenField("fragment", "", + length_from = lambda pkt: pkt.length) ] + +class TLSCompressed(TLSPlaintext): + name = "TLS Compressed" + +class TLSCiphertext(TLSPlaintext): + name = "TLS Ciphertext" + + +def _tls_compress(alg, p): + """ + Compress p (a TLSPlaintext instance) using compression algorithm instance + alg and return a TLSCompressed instance. + """ + c = TLSCompressed() + c.type = p.type + c.version = p.version + c.fragment = alg.compress(p.fragment) + c.len = len(c.fragment) + return c + +def _tls_decompress(alg, c): + """ + Decompress c (a TLSCompressed instance) using compression algorithm + instance alg and return a TLSPlaintext instance. + """ + p = TLSPlaintext() + p.type = c.type + p.version = c.version + p.fragment = alg.decompress(c.fragment) + p.len = len(p.fragment) + return p + +def _tls_mac_add(alg, c, write_seq_num): + """ + Compute the MAC using provided MAC alg instance over TLSCiphertext c using + current write sequence number write_seq_num. Computed MAC is then appended + to c.fragment and c.length is updated to reflect that change. It is the + caller responsability to increment the sequence number after the operation. + The function has no return value. + """ + write_seq_num = struct.pack("!Q", write_seq_num) + h = alg.digest(write_seq_num + str(c)) + c.fragment += h + c.len += alg.hash_len + +def _tls_mac_verify(alg, p, read_seq_num): + """ + Verify if the MAC in provided message (message resulting from decryption + and padding removal) is valid. Current read sequence number is used in + the verification process. + + If the MAC is valid: + - The function returns True + - The packet p is updated in the following way: trailing MAC value is + removed from p.fragment and length is updated accordingly. + + In case of error, False is returned, and p may have been modified. + + Also note that it is the caller's responsibility to update the read + sequence number after the operation. + """ + h_size = alg.hash_len + if p.len < h_size: + return False + received_h = p.fragment[-h_size:] + p.len -= h_size + p.fragment = p.fragment[:-h_size] + + read_seq_num = struct.pack("!Q", read_seq_num) + h = alg.digest(read_seq_num + str(p)) + return h == received_h + +def _tls_add_pad(p, block_size): + """ + Provided with cipher block size parameter and current TLSCompressed packet + p (after MAC addition), the function adds required, deterministic padding + to p.fragment before encryption step, as it is defined for TLS (i.e. not + SSL and its allowed random padding). The function has no return value. + """ + padlen = block_size - ((p.len + 1) % block_size) + if padlen == block_size: + padlen = 0 + padding = chr(padlen) * (padlen + 1) + p.len += len(padding) + p.fragment += padding + +def _tls_del_pad(p): + """ + Provided with a just decrypted TLSCiphertext (now a TLSPlaintext instance) + p, the function removes the trailing padding found in p.fragment. It also + performs some sanity checks on the padding (length, content, ...). False + is returned if one of the check fails. Otherwise, True is returned, + indicating that p.fragment and p.len have been updated. + """ + + if p.len < 1: + warning("Message format is invalid (padding)") + return False + + padlen = ord(p.fragment[-1]) + 1 + if (p.len < padlen): + warning("Invalid padding length") + return False + + if (p.fragment[-padlen:] != p.fragment[-1] * padlen): + warning("Padding content is invalid %s" % repr(p.fragment[-padlen:])) + return False + + p.fragment = p.fragment[:-padlen] + p.len -= padlen + + return True + +def _tls_encrypt(alg, p): + """ + Provided with an already MACed TLSCompressed packet, and a stream or block + cipher alg, the function converts it into a TLSCiphertext (i.e. encrypts it + and updates length). The function returns a newly created TLSCiphertext + instance. + """ + c = TLSCiphertext() + c.type = p.type + c.version = p.version + c.fragment = alg.encrypt(p.fragment) + c.len = len(c.fragment) + return c + +def _tls_decrypt(alg, c): + """ + Provided with a TLSCiphertext instance c, and a stream or block cipher alg, + the function decrypts c.fragment and returns a newly created TLSPlaintext. + """ + p = TLSPlaintext() + p.type = c.type + p.version = c.version + p.fragment = alg.decrypt(c.fragment) + p.len = len(p.fragment) + return p + +def _tls_aead_auth_encrypt(alg, p, write_seq_num): + """ + Provided with a TLSCompressed instance p, the function applies AEAD + cipher alg to p.fragment and builds a new TLSCiphertext instance. Unlike + for block and stream ciphers, for which the authentication step is done + separately, AEAD alg does it simultaneously: this is the reason why + write_seq_num is passed to the function, to be incorporated in + authenticated data. Note that it is the caller's responibility to increment + write_seq_num afterwards. + """ + P = str(p) + write_seq_num = struct.pack("!Q", write_seq_num) + A = write_seq_num + P[:5] + + c = TLCCiphertext() + c.type = p.type + c.version = p.version + c.fragment = alg.auth_encrypt(P, A) + c.len = len(c.fragment) + return c + +def _tls_aead_auth_decrypt(alg, c, read_seq_num): + """ + Provided with a TLSCiphertext instance c, the function applies AEAD + cipher alg auth_decrypt function to c.fragment (and additional data) + in order to authenticate the data and decrypt c.fragment. When those + steps succeed, the result is a newly created TLSCompressed instance. + On error, None is returned. Note that it is the caller's responsibility to + increment read_seq_num afterwards. + """ + # 'Deduce' TLSCompressed length from TLSCiphertext length + # There is actually no guaranty of this equality, but this is defined as + # such in TLS 1.2 specifications, and it works for GCM and CCM at least. + l = p.len - alg.nonce_explicit_len - alg.tag_len + read_seq_num = struct.pack("!Q", read_seq_num) + A = read_seq_num + struct.pack('!BHH', p.type, p.version, l) + + p = TLSCompressed() + p.type = c.type + p.version = c.version + p.len = l + p.fragment = alg.auth_decrypt(A, c.fragment) + + if p.fragment is None: # Verification failed. + return None + return p + diff --git a/scapy/layers/x509.py b/scapy/layers/x509.py index ee14bc43d24ced4e00f6551d64bcb1df30e07171..b5b03c9b13724184a1799132c8a6a48dbd0c2cb1 100644 --- a/scapy/layers/x509.py +++ b/scapy/layers/x509.py @@ -16,6 +16,7 @@ from scapy.packet import Packet from scapy.fields import PacketField from scapy.volatile import * + class ASN1P_OID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_OID("oid", "0") @@ -29,10 +30,10 @@ class ASN1P_PRIVSEQ(ASN1_Packet): # It showcases the private high-tag decoding capacities of scapy. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( - ASN1F_IA5_STRING("str", ""), - ASN1F_STRING("int", 0), - explicit_tag=0, - flexible_tag=True) + ASN1F_IA5_STRING("str", ""), + ASN1F_STRING("int", 0), + explicit_tag=0, + flexible_tag=True) ####################### @@ -201,7 +202,7 @@ class X509_X400Address(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_field("x400Address", "") -default_directoryName = [ +_default_directoryName = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( @@ -215,7 +216,7 @@ default_directoryName = [ class X509_DirectoryName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_SEQUENCE_OF("directoryName", default_directoryName, + ASN1_root = ASN1F_SEQUENCE_OF("directoryName", _default_directoryName, X509_RDN) class X509_EDIPartyName(ASN1_Packet): @@ -303,7 +304,7 @@ class X509_ExtDistributionPointName(ASN1_Packet): ASN1_root = ASN1F_CHOICE("distributionPointName", None, X509_ExtFullName, X509_ExtNameRelativeToCRLIssuer) -reasons_mapping = ["unused", +_reasons_mapping = ["unused", "keyCompromise", "cACompromise", "affiliationChanged", @@ -322,14 +323,14 @@ class X509_ExtDistributionPoint(ASN1_Packet): X509_ExtDistributionPointName, explicit_tag=0xa0)), ASN1F_optional( - ASN1F_FLAGS("reasons", None, reasons_mapping, + ASN1F_FLAGS("reasons", None, _reasons_mapping, implicit_tag=0x81)), ASN1F_optional( ASN1F_SEQUENCE_OF("cRLIssuer", None, X509_GeneralName, implicit_tag=0xa2))) -ku_mapping = ["digitalSignature", +_ku_mapping = ["digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment", @@ -341,7 +342,7 @@ ku_mapping = ["digitalSignature", class X509_ExtKeyUsage(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_FLAGS("keyUsage", "101", ku_mapping) + ASN1_root = ASN1F_FLAGS("keyUsage", "101", _ku_mapping) def get_keyUsage(self): return self.ASN1_root.get_flags(self) @@ -380,7 +381,7 @@ class X509_ExtCRLNumber(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("cRLNumber", 0) -cRL_reasons = ["unspecified", +_cRL_reasons = ["unspecified", "keyCompromise", "cACompromise", "affiliationChanged", @@ -394,7 +395,7 @@ cRL_reasons = ["unspecified", class X509_ExtReasonCode(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_ENUMERATED("cRLReason", 0, cRL_reasons) + ASN1_root = ASN1F_ENUMERATED("cRLReason", 0, _cRL_reasons) class X509_ExtDeltaCRLIndicator(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER @@ -414,7 +415,7 @@ class X509_ExtIssuingDistributionPoint(ASN1_Packet): implicit_tag=0x82), ASN1F_optional( ASN1F_FLAGS("onlySomeReasons", None, - reasons_mapping, + _reasons_mapping, implicit_tag=0x83)), ASN1F_BOOLEAN("indirectCRL", False, implicit_tag=0x84), @@ -438,7 +439,7 @@ class X509_ExtIssuerAltName(ASN1_Packet): ASN1_root = ASN1F_SEQUENCE_OF("issuerAltName", [], X509_GeneralName) class X509_ExtGeneralSubtree(ASN1_Packet): -# 'minimum' is not optional in RFC 5280, yet it is in some implementations. + # 'minimum' is not optional in RFC 5280, yet it is in some implementations. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_PACKET("base", X509_GeneralName(), X509_GeneralName), @@ -583,7 +584,7 @@ class X509_ExtDefault(ASN1_Packet): # oid-info.com shows that some extensions share multiple OIDs. # Here we only reproduce those written in RFC5280. -ext_mapping = { +_ext_mapping = { "2.5.29.9" : X509_ExtSubjectDirectoryAttributes, "2.5.29.14" : X509_ExtSubjectKeyIdentifier, "2.5.29.15" : X509_ExtKeyUsage, @@ -614,7 +615,7 @@ ext_mapping = { } class ASN1F_EXT_SEQUENCE(ASN1F_SEQUENCE): -# We use explicit_tag=0x04 with extnValue as STRING encapsulation. + # We use explicit_tag=0x04 with extnValue as STRING encapsulation. def __init__(self, **kargs): seq = [ASN1F_OID("extnID", "2.5.29.19"), ASN1F_optional( @@ -637,8 +638,8 @@ class ASN1F_EXT_SEQUENCE(ASN1F_SEQUENCE): extnID.set_val(pkt, oid) s = critical.dissect(pkt, s) encapsed = X509_ExtDefault - if oid.val in ext_mapping: - encapsed = ext_mapping[oid.val] + if oid.val in _ext_mapping: + encapsed = _ext_mapping[oid.val] self.seq[2].cls = encapsed self.seq[2].cls.ASN1_root.flexible_tag = True # there are too many private extensions not to be flexible here @@ -655,6 +656,13 @@ class X509_Extension(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_EXT_SEQUENCE() +class X509_Extensions(ASN1_Packet): + # we use this in OCSP status requests, in tls/handshake.py + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_optional( + ASN1F_SEQUENCE_OF("extensions", + None, X509_Extension)) + ####### Public key wrapper ####### @@ -748,10 +756,10 @@ class RSAPrivateKey_OpenSSL(ASN1_Packet): ECDSAPublicKey, explicit_tag=0xa1))) -class _PacketFieldRaw(PacketField): # We need this hack because ECParameters parsing below must return # a Padding payload, and making the ASN1_Packet class have Padding # instead of Raw payload would break things... +class _PacketFieldRaw(PacketField): def getfield(self, pkt, s): i = self.m2i(pkt, s) remain = "" @@ -773,7 +781,7 @@ class ECDSAPrivateKey_OpenSSL(Packet): ####### TBSCertificate & Certificate ####### -default_issuer = [ +_default_issuer = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( @@ -785,7 +793,7 @@ default_issuer = [ value=ASN1_PRINTABLE_STRING("Scapy Default Issuer"))]) ] -default_subject = [ +_default_subject = [ X509_RDN(), X509_RDN( rdn=[X509_AttributeTypeAndValue( @@ -807,7 +815,7 @@ class X509_Validity(ASN1_Packet): ASN1_UTC_TIME(str(ZuluTime(+86400))), ASN1F_UTC_TIME, ASN1F_GENERALIZED_TIME)) -attrName_mapping = [ +_attrName_mapping = [ ("countryName" , "C"), ("stateOrProvinceName" , "ST"), ("localityName" , "L"), @@ -815,7 +823,7 @@ attrName_mapping = [ ("organizationUnitName" , "OU"), ("commonName" , "CN") ] -attrName_specials = [name for name, symbol in attrName_mapping] +_attrName_specials = [name for name, symbol in _attrName_mapping] class X509_TBSCertificate(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER @@ -827,11 +835,11 @@ class X509_TBSCertificate(ASN1_Packet): ASN1F_PACKET("signature", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), - ASN1F_SEQUENCE_OF("issuer", default_issuer, X509_RDN), + ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), ASN1F_PACKET("validity", X509_Validity(), X509_Validity), - ASN1F_SEQUENCE_OF("subject", default_subject, X509_RDN), + ASN1F_SEQUENCE_OF("subject", _default_subject, X509_RDN), ASN1F_PACKET("subjectPublicKeyInfo", X509_SubjectPublicKeyInfo(), X509_SubjectPublicKeyInfo), @@ -860,12 +868,12 @@ class X509_TBSCertificate(ASN1_Packet): """ name_str = "" attrsDict = self.get_issuer() - for attrType, attrSymbol in attrName_mapping: + for attrType, attrSymbol in _attrName_mapping: if attrType in attrsDict: name_str += "/" + attrSymbol + "=" name_str += attrsDict[attrType] for attrType in sorted(attrsDict): - if attrType not in attrName_specials: + if attrType not in _attrName_specials: name_str += "/" + attrType + "=" name_str += attrsDict[attrType] return name_str @@ -879,12 +887,12 @@ class X509_TBSCertificate(ASN1_Packet): def get_subject_str(self): name_str = "" attrsDict = self.get_subject() - for attrType, attrSymbol in attrName_mapping: + for attrType, attrSymbol in _attrName_mapping: if attrType in attrsDict: name_str += "/" + attrSymbol + "=" name_str += attrsDict[attrType] for attrType in sorted(attrsDict): - if attrType not in attrName_specials: + if attrType not in _attrName_specials: name_str += "/" + attrType + "=" name_str += attrsDict[attrType] return name_str @@ -962,7 +970,7 @@ class X509_TBSCertList(ASN1_Packet): ASN1F_PACKET("signature", X509_AlgorithmIdentifier(), X509_AlgorithmIdentifier), - ASN1F_SEQUENCE_OF("issuer", default_issuer, X509_RDN), + ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), ASN1F_UTC_TIME("this_update", str(ZuluTime(-1))), ASN1F_optional( ASN1F_UTC_TIME("next_update", None)), @@ -987,12 +995,12 @@ class X509_TBSCertList(ASN1_Packet): """ name_str = "" attrsDict = self.get_issuer() - for attrType, attrSymbol in attrName_mapping: + for attrType, attrSymbol in _attrName_mapping: if attrType in attrsDict: name_str += "/" + attrSymbol + "=" name_str += attrsDict[attrType] for attrType in sorted(attrsDict): - if attrType not in attrName_specials: + if attrType not in _attrName_specials: name_str += "/" + attrType + "=" name_str += attrsDict[attrType] return name_str @@ -1050,3 +1058,172 @@ class X509_CRL(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_X509_CRL() + +############################# +#### OCSP Status packets #### +############################# +########### based on RFC 6960 + +class OCSP_CertID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("hashAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_STRING("issuerNameHash", ""), + ASN1F_STRING("issuerKeyHash", ""), + ASN1F_INTEGER("serialNumber", 0)) + +class OCSP_GoodInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_NULL("info", 0) + +class OCSP_RevokedInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_GENERALIZED_TIME("revocationTime", ""), + ASN1F_optional( + ASN1F_PACKET("revocationReason", None, + X509_ExtReasonCode, + explicit_tag=0x80))) + +class OCSP_UnknownInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_NULL("info", 0) + +class OCSP_CertStatus(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("certStatus", None, + ASN1F_PACKET("good", OCSP_GoodInfo(), + OCSP_GoodInfo, implicit_tag=0x80), + ASN1F_PACKET("revoked", OCSP_RevokedInfo(), + OCSP_RevokedInfo, implicit_tag=0xa1), + ASN1F_PACKET("unknown", OCSP_UnknownInfo(), + OCSP_UnknownInfo, implicit_tag=0x82)) + +class OCSP_SingleResponse(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("certID", OCSP_CertID(), OCSP_CertID), + ASN1F_PACKET("certStatus", OCSP_CertStatus(), + OCSP_CertStatus), + ASN1F_GENERALIZED_TIME("thisUpdate", ""), + ASN1F_optional( + ASN1F_GENERALIZED_TIME("nextUpdate", "", + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("singleExtensions", None, + X509_Extension, + explicit_tag=0xa1))) + +class OCSP_ByName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("byName", [], X509_RDN) + +class OCSP_ByKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_STRING("byKey", "") + +class OCSP_ResponderID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("responderID", None, + ASN1F_PACKET("byName", OCSP_ByName(), OCSP_ByName, + explicit_tag=0xa1), + ASN1F_PACKET("byKey", OCSP_ByKey(), OCSP_ByKey, + explicit_tag=0xa2)) + +class OCSP_ResponseData(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_enum_INTEGER("version", 0, {0: "v1"}, + explicit_tag=0x80)), + ASN1F_PACKET("responderID", OCSP_ResponderID(), + OCSP_ResponderID), + ASN1F_GENERALIZED_TIME("producedAt", + str(GeneralizedTime())), + ASN1F_SEQUENCE_OF("responses", [], OCSP_SingleResponse), + ASN1F_optional( + ASN1F_SEQUENCE_OF("responseExtensions", None, + X509_Extension, + explicit_tag=0xa1))) + +class ASN1F_OCSP_BasicResponseECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsResponseData", + OCSP_ResponseData(), + OCSP_ResponseData), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING_ENCAPS("signature", + ECDSASignature(), + ECDSASignature), + ASN1F_optional( + ASN1F_SEQUENCE_OF("certs", None, X509_Cert, + explicit_tag=0xa0))] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + +class ASN1F_OCSP_BasicResponse(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsResponseData", + OCSP_ResponseData(), + OCSP_ResponseData), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING("signature", + "defaultsignature"*2), + ASN1F_optional( + ASN1F_SEQUENCE_OF("certs", None, X509_Cert, + explicit_tag=0xa0))] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + def m2i(self, pkt, x): + c,s = ASN1F_SEQUENCE.m2i(self, pkt, x) + sigtype = pkt.fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in sigtype.lower(): + return c,s + elif "ecdsa" in sigtype.lower(): + return ASN1F_OCSP_BasicResponseECDSA().m2i(pkt, x) + else: + raise Exception("could not parse OCSP basic response") + def dissect(self, pkt, s): + c,x = self.m2i(pkt, s) + return x + def build(self, pkt): + if "signatureAlgorithm" in pkt.fields: + sigtype = pkt.fields['signatureAlgorithm'].algorithm.oidname + else: + sigtype = pkt.default_fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in sigtype.lower(): + return ASN1F_SEQUENCE.build(self, pkt) + elif "ecdsa" in sigtype.lower(): + pkt.default_fields["signatureValue"] = ECDSASignature() + return ASN1F_OCSP_BasicResponseECDSA().build(pkt) + else: + raise Exception("could not build OCSP basic response") + +class OCSP_ResponseBytes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("responseType", "1.3.6.1.5.5.7.48.1.1"), + ASN1F_OCSP_BasicResponse(explicit_tag=0x04)) + +_responseStatus_mapping = ["successful", + "malformedRequest", + "internalError", + "tryLater", + "notUsed", + "sigRequired", + "unauthorized"] + +class OCSP_Response(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("responseStatus", 0, + _responseStatus_mapping), + ASN1F_optional( + ASN1F_PACKET("responseBytes", None, + OCSP_ResponseBytes, + explicit_tag=0xa0))) + diff --git a/scapy/utils.py b/scapy/utils.py index 34c78dd463f8c9d78e8d61e4134ab4ac5b16a484..9d2a56c0a347702edfc56931f6f954022635a9b7 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -152,6 +152,10 @@ def hexstr(x, onlyasc=0, onlyhex=0): s.append(sane(x)) return " ".join(s) +def repr_hex(s): + """ Convert provided bitstring to a simple string of hex digits """ + return "".join(map(lambda x: "%02x" % ord(x),s)) + @conf.commands.register def hexdiff(x,y): """Show differences between 2 binary strings""" @@ -343,8 +347,34 @@ def mac2str(mac): def str2mac(s): return ("%02x:"*6)[:-1] % tuple(map(ord, s)) -def strxor(x,y): - return "".join(map(lambda x,y:chr(ord(x)^ord(y)),x,y)) +def randstring(l): + """ + Returns a random string of length l (l >= 0) + """ + tmp = map(lambda x: struct.pack("B", random.randrange(0, 256, 1)), [""]*l) + return "".join(tmp) + +def zerofree_randstring(l): + """ + Returns a random string of length l (l >= 0) without zero in it. + """ + tmp = map(lambda x: struct.pack("B", random.randrange(1, 256, 1)), [""]*l) + return "".join(tmp) + +def strxor(s1, s2): + """ + Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return "".join(map(lambda x,y:chr(ord(x)^ord(y)), s1, s2)) + +def strand(s1, s2): + """ + Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return "".join(map(lambda x,y:chr(ord(x)&ord(y)), s1, s2)) + # Workaround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470 try: diff --git a/test/cert.uts b/test/cert.uts index 876286d26b414b9c59bb317fb0c2ac67a3b6e21e..c43652cd7ae12251078acb7f0264584189f8df7c 100644 --- a/test/cert.uts +++ b/test/cert.uts @@ -32,6 +32,7 @@ TL1SGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvz AwIDAQAB -----END PUBLIC KEY----- """) +x_pubNum = x.pubkey.public_numbers() type(x) is PubKeyRSA = PubKey class : key format is PEM @@ -39,16 +40,17 @@ x.frmt == "PEM" = PubKey class : Importing DER-encoded RSA Key y = PubKey('0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x98Wj?\xe9\xd3\x11\x9b\xa4KIK?\xec\xa3\xd6\x03H\x9a\xc1\x08\x7f\xb3\xf6\xc9$\xee\x9d\xc7\x98\xc7\t&\xe1Q9A\x17\x83m\xbd\x8b\xe7\xf1\xcb\x03\xdaO\x98\x87\x90-*\xbf\x06\x03\xb5\x99\xe3\x9cl\xe4\x89\xd9\x85GCo\x0cC\x9e\xbe\xf0*\xdb\xea}\xbc\x8b\'\x17\xe2\x1at\x1fp1D\x08\xe1\xd1\xe7W\xfa\xad\xf2\x8a[\xd8\'\x85\xbd\xfc\xd9\xc8o\xfc\x00g\x04\xb4\xa0\x98\x9f\xfe\xd4\xe4T^\xfb\x1f&\xc0|\x97^\xe4J\x9b\xa7\xe6\xc2(\x8b\xccZv\xa6n\x1fCEL\xa3\xac\x10Y\xa3\x97@\xd6\x8d\xf6\xce\x9b\x85\x06\xb2]#\xc7fR\x9c=\x82\xd7\xf4\x17@Z\xf2Q\x99\x9b\xc5*sA\xb2]\xe5\xce%A6\xbb\xb0\xa22\xed\xcc\xef\xb0L\xe9\x92\xcbM\xca0\xe7\xe6\xd0"i&L\xbdR\x1a\x1c\xf0~)\xcc\x13W\xba\xa7q\xe6\xff\xfaC\x8e\xe2o\x15\xa66\xdaM9.\x02\xee\xca\xa79\xf6\xf1b\x07t\xe8\x95\xdc\xfc\xf8\x06\xcc6;\xf3\x03\x02\x03\x01\x00\x01') +y_pubNum = y.pubkey.public_numbers() type(y) is PubKeyRSA = PubKey class : key format is DER y.frmt == "DER" = PubKey class : checking modulus value -x.modulus == y.modulus and x.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L +x_pubNum.n == y_pubNum.n and x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L = PubKey class : checking public exponent value -x.pubExp == y.pubExp and x.pubExp == 65537L +x_pubNum.e == y_pubNum.e and x_pubNum.e == 65537L = PubKey class : Importing PEM-encoded ECDSA public key z = PubKey(""" @@ -60,10 +62,10 @@ Jd5qtmDF2Zu+xrwrBRT0HBnPweDU+RsFxcyU/QxD9WYORzYarqxbcA== type(z) is PubKeyECDSA = PubKey class : checking curve -z.key.curve.name == "SECP256k1" +z.pubkey.curve.name == "secp256k1" = PubKey class : checking point value -z.key.pubkey.point.x() == 104748656174769496952370005421566518252704263000192720134585149244759951661467L +z.pubkey.public_numbers().x == 104748656174769496952370005421566518252704263000192720134585149244759951661467L ########### PrivKey class ############################################### @@ -100,18 +102,20 @@ i5+Mqk9ZCGdoReVbAovJFoRqe7Fj9yWM+b1awGjL0bOTtnqx0iljob6uFyhpl1xg W3a3ICJ/ZYLvkgb4IBEteOwWpp37fX57vzhW8EmUV2UX7ve1uNRI -----END RSA PRIVATE KEY----- """) +x_privNum = x.key.private_numbers() +x_pubNum = x.pubkey.public_numbers() type(x) is PrivKeyRSA = PrivKey class : checking public attributes -assert(x.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) -x.pubExp == 65537L +assert(x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) +x_pubNum.e == 65537L = PrivKey class : checking private attributes -assert(x.prime1 == 140977881300857803928857666115326329496639762170623218602431133528876162476487960230341078724702018316260690172014674492782486113504117653531825010840338251572887403113276393351318549036549656895326851872473595350667293402676143426484331639796163189182788306480699144107905869179435145810212051656274284113969L) -assert(x.prime2 == 136413798668820291889092636919077529673097927884427227010121877374504825870002258140616512268521246045642663981036167305976907058413796938050224182519965099316625879807962173794483933183111515251808827349718943344770056106787713032506379905031673992574818291891535689493330517205396872699985860522390496583027L) -assert(x.exponent1 == 46171616708754015342920807261537213121074749458020000367465429453038710215532257783908950878847126373502288079285334594398328912526548076894076506899568491565992572446455658740752572386903609191774044411412991906964352741123956581870694330173563737928488765282233340389888026245745090096745219902501964298369L) -assert(x.exponent2 == 58077388505079936284685944662039782610415160654764308528562806086690474868010482729442634318267235411531220690585030443434512729356878742778542733733189895801341155353491318998637269079682889033003797865508917973141494201620317820971253064836562060222814287812344611566640341960495346782352037479526674026269L) -x.privExp == 15879630313397508329451198152673380989865598204237760057319927734227125481903063742175442230739018051313441697936698689753842471306305671266572085925009572141819112648211571007521954312641597446020984266846581125287547514750428503480880603089110687015181510081018160579576523796170439894692640171752302225125980423560965987469457505107324833137678663960560798216976668670722016960863268272661588745006387723814962668678285659376534048525020951633874488845649968990679414325096323920666486328886913648207836459784281744709948801682209478580185160477801656666089536527545026197569990716720623647770979759861119273292833L +assert(x_privNum.p == 140977881300857803928857666115326329496639762170623218602431133528876162476487960230341078724702018316260690172014674492782486113504117653531825010840338251572887403113276393351318549036549656895326851872473595350667293402676143426484331639796163189182788306480699144107905869179435145810212051656274284113969L) +assert(x_privNum.q == 136413798668820291889092636919077529673097927884427227010121877374504825870002258140616512268521246045642663981036167305976907058413796938050224182519965099316625879807962173794483933183111515251808827349718943344770056106787713032506379905031673992574818291891535689493330517205396872699985860522390496583027L) +assert(x_privNum.dmp1 == 46171616708754015342920807261537213121074749458020000367465429453038710215532257783908950878847126373502288079285334594398328912526548076894076506899568491565992572446455658740752572386903609191774044411412991906964352741123956581870694330173563737928488765282233340389888026245745090096745219902501964298369L) +assert(x_privNum.dmq1 == 58077388505079936284685944662039782610415160654764308528562806086690474868010482729442634318267235411531220690585030443434512729356878742778542733733189895801341155353491318998637269079682889033003797865508917973141494201620317820971253064836562060222814287812344611566640341960495346782352037479526674026269L) +x_privNum.d == 15879630313397508329451198152673380989865598204237760057319927734227125481903063742175442230739018051313441697936698689753842471306305671266572085925009572141819112648211571007521954312641597446020984266846581125287547514750428503480880603089110687015181510081018160579576523796170439894692640171752302225125980423560965987469457505107324833137678663960560798216976668670722016960863268272661588745006387723814962668678285659376534048525020951633874488845649968990679414325096323920666486328886913648207836459784281744709948801682209478580185160477801656666089536527545026197569990716720623647770979759861119273292833L = PrivKey class : Importing PEM-encoded ECDSA private key y = PrivKey(""" @@ -124,11 +128,24 @@ weDU+RsFxcyU/QxD9WYORzYarqxbcA== type(y) is PrivKeyECDSA = PrivKey class : checking public attributes -assert(y.key.curve.name == "SECP256k1") -y.key.privkey.public_key.point.y() == 86290575637772818452062569410092503179882738810918951913926481113065456425840L +assert(y.key.curve.name == "secp256k1") +y.key.public_key().public_numbers().y == 86290575637772818452062569410092503179882738810918951913926481113065456425840L = PrivKey class : checking private attributes -y.key.privkey.secret_multiplier == 90719786431263082134670936670180839782031078050773732489701961692235185651857L +y.key.private_numbers().private_value == 90719786431263082134670936670180839782031078050773732489701961692235185651857L + + +########### PKCS crypto ############################################# +# these are legacy tests which should be removed eventually (see our pkcs1.py) + ++ PKCS 1 legacy tests + += PKCS 1 legacy : RSA signature & verification +m = "Testing our PKCS #1 legacy methods" +s = x.sign_legacy(m, t="pkcs", h="tls") +assert(s == "\x0cm\x8a\x8f\xae`o\xcdC=\xfea\xf4\xff\xf0i\xfe\xa3!\xfd\xa5=*\x99?\x08!\x03A~\xa3-B\xe8\xca\xaf\xb4H|\xa3\x98\xe9\xd5U\xfdL\xb1\x9c\xd8\xb2{\xa1/\xfcr\x8c\xa7\xd3\xa9%\xde\x13\xa8\xf6\xc6<\xc7\xdb\xe3\xa62\xeb\xe9?\xe5by\xc2\x9e\xad\xec\x92:\x14\xd96\xa8\xc0+\xea8'{=\x91$\xdf\xed\xe1+eF8\x9fI\x1f\xa1\xcb4s\xd1#\xdf\xa11\x88o\x050i Hg\x0690\xe6\xe8?\\<:k\x94\x82\x91\x0f\x06\xc7>ZQ\xc2\xcdn\xdb\xf4\x9d\x7f!\xa9>\xe8\xea\xb3\xd83]\x8d\x90\xd4\xa0b\xe6\xe6$d[\xe4\xb4 |W\xb2t\x8c\xb2\xd5>>+\xf1\xa6W'\xaf\xc2CU\x82\x13\xc4\x0b\xc4vD*\xc3\xef\xa6s\nQ\xe6\rS@B\xd2\xa4V\xdc\xd1D\x7f\x00\xaa\xac\xac\x96i\xf1kg*\xe9*\x90a@\xc8uDy\x16\xe2\x03\xd1\x9fa\xe2s\xdb\xees\xa4\x8cna\xba\xdaE\x006&\xa4") +x_pub = PubKey((x._pubExp, x._modulus, x._modulusLen)) +x_pub.verify_legacy(m, s, t="pkcs", h="tls") ########### Cert class ############################################## @@ -194,8 +211,9 @@ x.notAfter == (2026, 3, 30, 7, 38, 59, 0, 89, -1) = Cert class : Checking RSA public key assert(type(x.pubKey) is PubKeyRSA) -assert(x.pubKey.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) -x.pubKey.pubExp == 0x10001 +x_pubNum = x.pubKey.pubkey.public_numbers() +assert(x_pubNum.n == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) +x_pubNum.e == 0x10001 = Cert class : Checking extensions assert(x.cA) @@ -223,8 +241,9 @@ JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv = Cert class : Checking ECDSA public key assert(type(y.pubKey) is PubKeyECDSA) -assert(y.pubKey.key.curve.name == 'SECP384r1') -y.pubKey.key.pubkey.point.x() == 3987178688175281746349180015490646948656137448666005327832107126183726641822596270780616285891030558662603987311874L +pubkey = y.pubKey.pubkey +assert(pubkey.curve.name == 'secp384r1') +pubkey.public_numbers().x == 3987178688175281746349180015490646948656137448666005327832107126183726641822596270780616285891030558662603987311874L = Cert class : Checking ECDSA signature y.signatureValue == '0d\x020%\xa4\x81E\x02k\x12KutO\xc8#\xe3p\xf2ur\xde|\x89\xf0\xcf\x91ra\x9e^\x10\x92YV\xb9\x83\xc7\x10\xe78\xe9X&6}\xd5\xe44\x869\x020|6S\xf00\xe5bc:\x99\xe2\xb6\xa3;\x9b4\xfa\x1e\xda\x10\x92q^\x91\x13\xa7\xdd\xa4n\x92\xcc2\xd6\xf5!f\xc7/\xea\x96cjeE\x92\x95\x01\xb4' diff --git a/test/tls.uts b/test/tls.uts new file mode 100644 index 0000000000000000000000000000000000000000..e24d203df6da73108140bac85154cb37fcb5969b --- /dev/null +++ b/test/tls.uts @@ -0,0 +1,957 @@ +% Tests for TLS module +# +# Try me with : +# bash test/run_tests -t test/tls.uts -F + +~ crypto + +############################################################################### +################################### Crypto #################################### +############################################################################### + +############################################################################### +### HMAC ### +############################################################################### + ++ Test Hmac_MD5 += Crypto - Hmac_MD5 instantiation, parameter check +from scapy.layers.tls.crypto.h_mac import Hmac_MD5 +a = Hmac_MD5("somekey") +a.key_len == 16 and a.hmac_len == 16 + += Crypto - Hmac_MD5 behavior on test vectors from RFC 2202 (+ errata) +a = Hmac_MD5 +t1 = a('\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b').digest("Hi There") == '\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d' +t2 = a('Jefe').digest('what do ya want for nothing?') == '\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38' +t3 = a('\xaa'*16).digest('\xdd'*50) == '\x56\xbe\x34\x52\x1d\x14\x4c\x88\xdb\xb8\xc7\x33\xf0\xe8\xb3\xf6' +t4 = a('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19').digest('\xcd'*50) == '\x69\x7e\xaf\x0a\xca\x3a\x3a\xea\x3a\x75\x16\x47\x46\xff\xaa\x79' +t5 = a('\x0c'*16).digest("Test With Truncation") == '\x56\x46\x1e\xf2\x34\x2e\xdc\x00\xf9\xba\xb9\x95\x69\x0e\xfd\x4c' +t6 = a('\xaa'*80).digest("Test Using Larger Than Block-Size Key - Hash Key First") == '\x6b\x1a\xb7\xfe\x4b\xd7\xbf\x8f\x0b\x62\xe6\xce\x61\xb9\xd0\xcd' +t7 = a('\xaa'*80).digest("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data") == '\x6f\x63\x0f\xad\x67\xcd\xa0\xee\x1f\xb1\xf5\x62\xdb\x3a\xa5\x3e' +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test Hmac_SHA += Crypto - Hmac_SHA instantiation, parameter check +from scapy.layers.tls.crypto.h_mac import Hmac_SHA +a = Hmac_SHA("somekey") +a.key_len == 20 and a.hmac_len == 20 + += Crypto - Hmac_SHA behavior on test vectors from RFC 2202 (+ errata) +a = Hmac_SHA +t1 = a('\x0b'*20).digest("Hi There") == '\xb6\x17\x31\x86\x55\x05\x72\x64\xe2\x8b\xc0\xb6\xfb\x37\x8c\x8e\xf1\x46\xbe\x00' +t2 = a('Jefe').digest("what do ya want for nothing?") == '\xef\xfc\xdf\x6a\xe5\xeb\x2f\xa2\xd2\x74\x16\xd5\xf1\x84\xdf\x9c\x25\x9a\x7c\x79' +t3 = a('\xaa'*20).digest('\xdd'*50) == '\x12\x5d\x73\x42\xb9\xac\x11\xcd\x91\xa3\x9a\xf4\x8a\xa1\x7b\x4f\x63\xf1\x75\xd3' +t4 = a('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19').digest('\xcd'*50) == '\x4c\x90\x07\xf4\x02\x62\x50\xc6\xbc\x84\x14\xf9\xbf\x50\xc8\x6c\x2d\x72\x35\xda' +t5 = a('\x0c'*20).digest("Test With Truncation") == '\x4c\x1a\x03\x42\x4b\x55\xe0\x7f\xe7\xf2\x7b\xe1\xd5\x8b\xb9\x32\x4a\x9a\x5a\x04' +t6 = a('\xaa'*80).digest("Test Using Larger Than Block-Size Key - Hash Key First") == '\xaa\x4a\xe5\xe1\x52\x72\xd0\x0e\x95\x70\x56\x37\xce\x8a\x3b\x55\xed\x40\x21\x12' +t7 = a('\xaa'*80).digest("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data") == '\xe8\xe9\x9d\x0f\x45\x23\x7d\x78\x6d\x6b\xba\xa7\x96\x5c\x78\x08\xbb\xff\x1a\x91' +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test Hmac_SHA2 += Crypto - Hmac_SHA2 behavior on test vectors from RFC 4231 + +class _hmac_test_case_1: + Key = ('\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'+ + '\x0b\x0b\x0b\x0b\x0b\x0b\x0b') + Data = '\x48\x69\x20\x54\x68\x65\x72\x65' + HMAC_SHA_224 = ('\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4'+ + '\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68'+ + '\x4b\x22') + HMAC_SHA_256 = ('\xb0\x34\x4c\x61\xd8\xdb\x38\x53\x5c\xa8\xaf\xce\xaf'+ + '\x0b\xf1\x2b\x88\x1d\xc2\x00\xc9\x83\x3d\xa7\x26\xe9'+ + '\x37\x6c\x2e\x32\xcf\xf7') + HMAC_SHA_384 = ('\xaf\xd0\x39\x44\xd8\x48\x95\x62\x6b\x08\x25\xf4\xab'+ + '\x46\x90\x7f\x15\xf9\xda\xdb\xe4\x10\x1e\xc6\x82\xaa'+ + '\x03\x4c\x7c\xeb\xc5\x9c\xfa\xea\x9e\xa9\x07\x6e\xde'+ + '\x7f\x4a\xf1\x52\xe8\xb2\xfa\x9c\xb6') + HMAC_SHA_512 = ('\x87\xaa\x7c\xde\xa5\xef\x61\x9d\x4f\xf0\xb4\x24\x1a'+ + '\x1d\x6c\xb0\x23\x79\xf4\xe2\xce\x4e\xc2\x78\x7a\xd0'+ + '\xb3\x05\x45\xe1\x7c\xde\xda\xa8\x33\xb7\xd6\xb8\xa7'+ + '\x02\x03\x8b\x27\x4e\xae\xa3\xf4\xe4\xbe\x9d\x91\x4e'+ + '\xeb\x61\xf1\x70\x2e\x69\x6c\x20\x3a\x12\x68\x54') + +class _hmac_test_case_2: + Key = '\x4a\x65\x66\x65' + Data = ('\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61\x20\x77\x61'+ + '\x6e\x74\x20\x66\x6f\x72\x20\x6e\x6f\x74\x68\x69\x6e'+ + '\x67\x3f') + HMAC_SHA_224 = ('\xa3\x0e\x01\x09\x8b\xc6\xdb\xbf\x45\x69\x0f\x3a\x7e'+ + '\x9e\x6d\x0f\x8b\xbe\xa2\xa3\x9e\x61\x48\x00\x8f\xd0'+ + '\x5e\x44') + HMAC_SHA_256 = ('\x5b\xdc\xc1\x46\xbf\x60\x75\x4e\x6a\x04\x24\x26\x08'+ + '\x95\x75\xc7\x5a\x00\x3f\x08\x9d\x27\x39\x83\x9d\xec'+ + '\x58\xb9\x64\xec\x38\x43') + HMAC_SHA_384 = ('\xaf\x45\xd2\xe3\x76\x48\x40\x31\x61\x7f\x78\xd2\xb5'+ + '\x8a\x6b\x1b\x9c\x7e\xf4\x64\xf5\xa0\x1b\x47\xe4\x2e'+ + '\xc3\x73\x63\x22\x44\x5e\x8e\x22\x40\xca\x5e\x69\xe2'+ + '\xc7\x8b\x32\x39\xec\xfa\xb2\x16\x49') + HMAC_SHA_512 = ('\x16\x4b\x7a\x7b\xfc\xf8\x19\xe2\xe3\x95\xfb\xe7\x3b'+ + '\x56\xe0\xa3\x87\xbd\x64\x22\x2e\x83\x1f\xd6\x10\x27'+ + '\x0c\xd7\xea\x25\x05\x54\x97\x58\xbf\x75\xc0\x5a\x99'+ + '\x4a\x6d\x03\x4f\x65\xf8\xf0\xe6\xfd\xca\xea\xb1\xa3'+ + '\x4d\x4a\x6b\x4b\x63\x6e\x07\x0a\x38\xbc\xe7\x37') + +class _hmac_test_case_3: + Key = ('\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa') + Data = ('\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ + '\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ + '\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd'+ + '\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd') + HMAC_SHA_224 = ('\x7f\xb3\xcb\x35\x88\xc6\xc1\xf6\xff\xa9\x69\x4d\x7d'+ + '\x6a\xd2\x64\x93\x65\xb0\xc1\xf6\x5d\x69\xd1\xec\x83'+ + '\x33\xea') + HMAC_SHA_256 = ('\x77\x3e\xa9\x1e\x36\x80\x0e\x46\x85\x4d\xb8\xeb\xd0'+ + '\x91\x81\xa7\x29\x59\x09\x8b\x3e\xf8\xc1\x22\xd9\x63'+ + '\x55\x14\xce\xd5\x65\xfe') + HMAC_SHA_384 = ('\x88\x06\x26\x08\xd3\xe6\xad\x8a\x0a\xa2\xac\xe0\x14'+ + '\xc8\xa8\x6f\x0a\xa6\x35\xd9\x47\xac\x9f\xeb\xe8\x3e'+ + '\xf4\xe5\x59\x66\x14\x4b\x2a\x5a\xb3\x9d\xc1\x38\x14'+ + '\xb9\x4e\x3a\xb6\xe1\x01\xa3\x4f\x27') + HMAC_SHA_512 = ('\xfa\x73\xb0\x08\x9d\x56\xa2\x84\xef\xb0\xf0\x75\x6c'+ + '\x89\x0b\xe9\xb1\xb5\xdb\xdd\x8e\xe8\x1a\x36\x55\xf8'+ + '\x3e\x33\xb2\x27\x9d\x39\xbf\x3e\x84\x82\x79\xa7\x22'+ + '\xc8\x06\xb4\x85\xa4\x7e\x67\xc8\x07\xb9\x46\xa3\x37'+ + '\xbe\xe8\x94\x26\x74\x27\x88\x59\xe1\x32\x92\xfb') + +class _hmac_test_case_4: + Key = ('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d'+ + '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19') + Data = ('\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ + '\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ + '\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd'+ + '\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd') + HMAC_SHA_224 = ('\x6c\x11\x50\x68\x74\x01\x3c\xac\x6a\x2a\xbc\x1b\xb3'+ + '\x82\x62\x7c\xec\x6a\x90\xd8\x6e\xfc\x01\x2d\xe7\xaf'+ + '\xec\x5a') + HMAC_SHA_256 = ('\x82\x55\x8a\x38\x9a\x44\x3c\x0e\xa4\xcc\x81\x98\x99'+ + '\xf2\x08\x3a\x85\xf0\xfa\xa3\xe5\x78\xf8\x07\x7a\x2e'+ + '\x3f\xf4\x67\x29\x66\x5b') + HMAC_SHA_384 = ('\x3e\x8a\x69\xb7\x78\x3c\x25\x85\x19\x33\xab\x62\x90'+ + '\xaf\x6c\xa7\x7a\x99\x81\x48\x08\x50\x00\x9c\xc5\x57'+ + '\x7c\x6e\x1f\x57\x3b\x4e\x68\x01\xdd\x23\xc4\xa7\xd6'+ + '\x79\xcc\xf8\xa3\x86\xc6\x74\xcf\xfb') + HMAC_SHA_512 = ('\xb0\xba\x46\x56\x37\x45\x8c\x69\x90\xe5\xa8\xc5\xf6'+ + '\x1d\x4a\xf7\xe5\x76\xd9\x7f\xf9\x4b\x87\x2d\xe7\x6f'+ + '\x80\x50\x36\x1e\xe3\xdb\xa9\x1c\xa5\xc1\x1a\xa2\x5e'+ + '\xb4\xd6\x79\x27\x5c\xc5\x78\x80\x63\xa5\xf1\x97\x41'+ + '\x12\x0c\x4f\x2d\xe2\xad\xeb\xeb\x10\xa2\x98\xdd') + +class _hmac_test_case_5: + Key = ('\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'+ + '\x0c\x0c\x0c\x0c\x0c\x0c\x0c') + Data = ('\x54\x65\x73\x74\x20\x57\x69\x74\x68\x20\x54\x72\x75'+ + '\x6e\x63\x61\x74\x69\x6f\x6e') + HMAC_SHA_224 = ('\x0e*\xeah\xa9\x0c\x8d7\xc9\x88\xbc\xdb\x9f\xcao\xa8'+ + '\t\x9c\xd8W\xc7\xecJ\x18\x15\xca\xc5L') + HMAC_SHA_256 = ('\xa3\xb6\x16ts\x10\x0e\xe0n\x0cyl)UU+\xfao|\nj\x8a'+ + '\xef\x8b\x93\xf8`\xaa\xb0\xcd \xc5') + HMAC_SHA_384 = (':\xbf4\xc3P;*#\xa4n\xfca\x9b\xae\xf8\x97\xf4\xc8\xe4'+ + ',\x93L\xe5\\\xcb\xae\x97@\xfc\xbc\x1a\xf4\xcab&\x9e*'+ + '7\xcd\x88\xba\x92cA\xef\xe4\xae\xea') + HMAC_SHA_512 = ('A_\xadbqX\nS\x1dAy\xbc\x89\x1d\x87\xa6P\x18\x87\x07'+ + '\x92*O\xbb6f:\x1e\xb1m\xa0\x08q\x1c[P\xdd\xd0\xfc#P'+ + '\x84\xeb\x9d3d\xa1EO\xb2\xefg\xcd\x1d)\xfegs\x06\x8e'+ + '\xa2f\xe9k') + +class _hmac_test_case_6: + Key = ('\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa') + Data = ('\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61'+ + '\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f'+ + '\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d'+ + '\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72'+ + '\x73\x74') + HMAC_SHA_224 = ('\x95\xe9\xa0\xdb\x96\x20\x95\xad\xae\xbe\x9b\x2d\x6f'+ + '\x0d\xbc\xe2\xd4\x99\xf1\x12\xf2\xd2\xb7\x27\x3f\xa6'+ + '\x87\x0e') + HMAC_SHA_256 = ('\x60\xe4\x31\x59\x1e\xe0\xb6\x7f\x0d\x8a\x26\xaa\xcb'+ + '\xf5\xb7\x7f\x8e\x0b\xc6\x21\x37\x28\xc5\x14\x05\x46'+ + '\x04\x0f\x0e\xe3\x7f\x54') + HMAC_SHA_384 = ('\x4e\xce\x08\x44\x85\x81\x3e\x90\x88\xd2\xc6\x3a\x04'+ + '\x1b\xc5\xb4\x4f\x9e\xf1\x01\x2a\x2b\x58\x8f\x3c\xd1'+ + '\x1f\x05\x03\x3a\xc4\xc6\x0c\x2e\xf6\xab\x40\x30\xfe'+ + '\x82\x96\x24\x8d\xf1\x63\xf4\x49\x52') + HMAC_SHA_512 = ('\x80\xb2\x42\x63\xc7\xc1\xa3\xeb\xb7\x14\x93\xc1\xdd'+ + '\x7b\xe8\xb4\x9b\x46\xd1\xf4\x1b\x4a\xee\xc1\x12\x1b'+ + '\x01\x37\x83\xf8\xf3\x52\x6b\x56\xd0\x37\xe0\x5f\x25'+ + '\x98\xbd\x0f\xd2\x21\x5d\x6a\x1e\x52\x95\xe6\x4f\x73'+ + '\xf6\x3f\x0a\xec\x8b\x91\x5a\x98\x5d\x78\x65\x98') + +class _hmac_test_case_7: + Key = ('\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'+ + '\xaa') + Data = ('\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73'+ + '\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72'+ + '\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63'+ + '\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e'+ + '\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68'+ + '\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65'+ + '\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65'+ + '\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65'+ + '\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72'+ + '\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20'+ + '\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61'+ + '\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e') + HMAC_SHA_224 = ('\x3a\x85\x41\x66\xac\x5d\x9f\x02\x3f\x54\xd5\x17\xd0'+ + '\xb3\x9d\xbd\x94\x67\x70\xdb\x9c\x2b\x95\xc9\xf6\xf5'+ + '\x65\xd1') + HMAC_SHA_256 = ('\x9b\x09\xff\xa7\x1b\x94\x2f\xcb\x27\x63\x5f\xbc\xd5'+ + '\xb0\xe9\x44\xbf\xdc\x63\x64\x4f\x07\x13\x93\x8a\x7f'+ + '\x51\x53\x5c\x3a\x35\xe2') + HMAC_SHA_384 = ('\x66\x17\x17\x8e\x94\x1f\x02\x0d\x35\x1e\x2f\x25\x4e'+ + '\x8f\xd3\x2c\x60\x24\x20\xfe\xb0\xb8\xfb\x9a\xdc\xce'+ + '\xbb\x82\x46\x1e\x99\xc5\xa6\x78\xcc\x31\xe7\x99\x17'+ + '\x6d\x38\x60\xe6\x11\x0c\x46\x52\x3e') + HMAC_SHA_512 = ('\xe3\x7b\x6a\x77\x5d\xc8\x7d\xba\xa4\xdf\xa9\xf9\x6e'+ + '\x5e\x3f\xfd\xde\xbd\x71\xf8\x86\x72\x89\x86\x5d\xf5'+ + '\xa3\x2d\x20\xcd\xc9\x44\xb6\x02\x2c\xac\x3c\x49\x82'+ + '\xb1\x0d\x5e\xeb\x55\xc3\xe4\xde\x15\x13\x46\x76\xfb'+ + '\x6d\xe0\x44\x60\x65\xc9\x74\x40\xfa\x8c\x6a\x58') + +def _all_hmac_sha2_tests(): + from scapy.layers.tls.crypto.h_mac import (Hmac_SHA224, Hmac_SHA256, + Hmac_SHA384, Hmac_SHA512) + res = True + for t in [_hmac_test_case_1, _hmac_test_case_2, _hmac_test_case_3, + _hmac_test_case_4, _hmac_test_case_5, _hmac_test_case_6, + _hmac_test_case_7 ]: + tmp = ((Hmac_SHA224(t.Key).digest(t.Data) == t.HMAC_SHA_224) and + (Hmac_SHA256(t.Key).digest(t.Data) == t.HMAC_SHA_256) and + (Hmac_SHA384(t.Key).digest(t.Data) == t.HMAC_SHA_384) and + (Hmac_SHA512(t.Key).digest(t.Data) == t.HMAC_SHA_512)) + res = res and tmp + return res + +_all_hmac_sha2_tests() + + +############################################################################### +### PRF ### +############################################################################### + ++ Test _tls_P_MD5 += Crypto - _tls_P_MD5 behavior on test vectors borrowed from RFC 2202 (+ errata) +from scapy.layers.tls.crypto.prf import _tls_P_MD5 +t1 = _tls_P_MD5('\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b', "Hi There", 64) == '8\x99\xc0\xb8!\xd7}RI\xb2\xbb\x8e\xbe\xf8\x97Y\xcc\xffL\xae\xc3I\x8f\x7f .\x81\xe0\xce\x1a\x82\xbd\x19\xa0\x16\x10P}\xf0\xda\xdc\xa0>\xc4,\xa1\xcfS`\x85\xc5\x084+QN31b\xd7%L\x9d\xdc' +t2 = _tls_P_MD5("Jefe", "what do ya want for nothing?", 64) == "\xec\x99'|,\xd5gj\x82\xb9\xa0\x12\xdb\x83\xd3\xa3\x93\x19\xa6N\x89g\x99\xc2!9\xd8\xcf\xc1WTi\xc4D \x19l\x03\xa8PCo\x10`-\x98\xd0\xe1\xbc\xefAJkx\x95\x0c\x08*\xd6C\x8fS\x0e\xd9" +t3 = _tls_P_MD5('\xaa'*16,'\xdd'*50, 64) == '\xe5_\xe8.l\xee\xd8AP\xfc$$\xda\tX\x93O\xa7\xd2\xe2\xa2\xa9\x02\xa1\x07t\x19\xd1\xe3%\x80\x19\rV\x19\x0f\xfa\x01\xce\x0eJ\x7fN\xdf\xed\xb5lS\x06\xb5|\x96\xa6\x1cc)h\x88\x8d\x0c@\xfdX\xaa' +t4 = _tls_P_MD5('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', '\xcd'*50, 64) == '\x8e\xa6\x1f\x82\x1e\xad\xbe4q\x93\xf4\x1c\xb7\x87\xb3\x15\x13F\x8b\xfd\x89m\x0e\xa6\xdc\xe9\xceZ\xcdOc>gN\xa4\x9cK\xf89\xfc6\t%T=j\xf0\x0f\xfdl\xbf\xfbj\xc4$zR"\xf4\xa4=\x18\x8b\x8d' +t5 = _tls_P_MD5('\x0c'*16, "Test With Truncation", 64) == '\xb3>\xfaj\xc8\x95S\xcd\xdd\xea\x8b\xee7\xa5ru\xf4\x00\xd6\xed\xd5\x9aH\x1f,F\xb6\x93\r\xc3Z<"\x1e\xf7rx\xf0\xd7\x0f`zy\xe9\r\xb4\xf4}\xab2\xa5\xfe\xd0z@\x87\xc1c\x8b\xa0\xc8\xf5\x0bd' +t6 = _tls_P_MD5('\xaa'*80, "Test Using Larger Than Block-Size Key - Hash Key First", 64) == ';\xcf\xa4\xd8\xccH\xa0\xa4\xf1\x10d\xfa\xd4\xb1\x7f\xda\x80\xf6\xe2\xb9\xf4\xd3WtS\x1c\x83\xb4(\x94\xfe\xa7\xb9\xc1\xcd\xf9\xe7\xae\xbc\x0c\x0f\xbae\xc3\x9e\x11\xe2+\x11\xe9\xd4\x8fK&\x99\xfe[\xfa\x02\x85\xb4\xd8\x8e\xdf' +t7 = _tls_P_MD5('\xaa'*80, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 64) == '\x12\x06EI1\x81fP\x8dn\xa6WC\xfb\xbf\x1e\xefC[|\x0f\x05w\x14@\xfc\xa5 \xeak\xc9\xb9\x1c&\x80\x81.\x85#\xa9\x0ff\xea\xaa\x01"v\'\xd8X"\xbd\xa2\x86\xbd\xe3?6\xc7|\xc6WNO' +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test _tls_P_SHA1 += Crypto - _tls_P_SHA1 behavior on test vectors borrowed from RFC 2202 (+ errata) +from scapy.layers.tls.crypto.prf import _tls_P_SHA1 +t1 = _tls_P_SHA1('\x0b'*20, "Hi There", 80) == '\x13\r\x11Q7(\xc1\xad\x7f>%m\xfc\x08\xb6\xb9$\xb1MG\xe4\x9c\xcdY\x0e\\T\xd0\x8f\x1a-O@`\xd2\x9eV_\xfd\xed\x1f\x93V\xfb\x18\xb6\xbclq3A\xa2\x87\xb1u\xfc\xb3RQ\x19;#\n(\xd2o%lB\x8b\x01\x89\x1c6m"\xc3\xe2\xa0\xe7' +t2 = _tls_P_SHA1('Jefe', "what do ya want for nothing?", 80) == '\xba\xc4i\xf1\xa0\xc5eO\x844\xb6\xbd%L\xe1\xfe\xef\x08\x00\x1c^l\xaf\xbbN\x9f\xd8\xe5}\x87U\xc1\xd2&4zu\x9a1\xef\xd6M+\x1e\x84\xb4\xcb\xc9\xa7\n\x90f\x8aJ\xde\xd5\xa4\x8f,D\xe8.\x98\x9c)\xc7hlct\x1em(\xb73b[L\x96c' +t3 = _tls_P_SHA1('\xaa'*20, '\xdd'*50, 80) == 'Lm\x848}\xe8?\x88\x82\x85\xc3\xe6\xc9\x1f\x80Z\xf5D\xeeI\xa1m\x08h)\xea<zk{\x9b\x9b\xe1;H\xa4\xf5\x93r\x87\x07J0\n\xb9\xdd\\~j\xd0\x98R|C\x89\x131\x12u%\x90\xb2\x05\xb4}\xad}\xc4MP\x8cmb\x0c\x88\xfd{)\x9b\xc0' +t4 = _tls_P_SHA1('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', '\xcd'*50, 80) == '\xd6\xe4\x8a\x91\xb3\xac\xe16\x9d\x10s\xf1\x1bu\x96(6f\xed\xd8x\x19\xcd<:\x15\xb2z\xc1\xa9\xdf\x89=\xeb!\xfb\n\x0e\xdf0\xb9\xb5\xa96\xcf\x9b\xd4\xcaD\x12Y1[p\xb9\xf9\xbb=\xa9\xcd\xb7\xe0L\xb00\xafK\xc4\x9c\xc6?#\xb6$\xebM\x1a\xba;3' +t5 = _tls_P_SHA1('\x0c'*20, "Test With Truncation", 80) == '`\x1d\xe4\x98Q\xa1\xdbW\xc5a\xa9@\x8fQ\x86\xfc\x17\xca\xda\x1a\xdd\xb8\xab\x94M_Y\xd1%Pj\xfc\xd4\xca\x82\x88\xdb\x04\xf9F\xbe\xbf\xecR\xa4\x0c}[\x8e\xc7\xdf\x88I:\xea2v\xbe\x06\x8fcx\xf1Q\xb7z1\x1455?\xc0_\xda\xbb;\xa6Q\xb3\xc5' +t6 = _tls_P_SHA1('\xaa'*80, "Test Using Larger Than Block-Size Key - Hash Key First", 80) == '\x00W\xbaq>^\x047;\xcezY}\x16\xc6\xf10\x80:\xe2K\x87i{\xc7V\xad2\xda=\xf3d7\x047\xf7r\xf1&\x04\xb1\xd1\xf8\x88H\'\r\x08\xc4\x81\xa3\xa1Q\xa5\x90\xed\xef\xd8\x9c\x14\xdc\x80\xab){3\xde\x87\x8a\x1e"\x1e\xad54rM\x94\xe1\xb8' +t7 = _tls_P_SHA1('\xaa'*80, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == 'N/PKC\x1d\xb5[}gUk\xc7\xaf\xb4-\xef\x9e\xe63$E=\xfc\xc4\xd0l]EA\x84\xb0\x1e\x91]\xcc[\x0e-\xec\xd5\x90\x19,\xc6\xffn\xf8\xbe1Ck\xe6\x9cF*\x8c"_\x05\x14%h\x98\xa1\xc2\xf1bCt\xd4S\xc1:{\x96\xa4\x14c ' +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test _tls_PRF() += Crypto - _tls_PRF behavior on test vectors borrowed from RFC 2202 (+ errata) +from scapy.layers.tls.crypto.prf import _tls_PRF +t1 = _tls_PRF('\x0b'*20, "Test Label XXXX", "Hi There", 80) == 'E\xcc\xeb\x12\x0b<\xbfh\x1f\xc3\xd3%J\x85\xdeQ\t\xbc[\xcd.\xbe\x170\xf2\xebm\xe6g\x05x\xad\x86V\x0b\xb3\xb7\xe5i\x7fh}T\xe5$\xe4\xba\xa0\xc6\xf0\xf1\xb1\xe1\x8a\xf5\xcc\x9ab\x1c\xc9\x10\x82\x93\x82Q\xd2\x80\xf0\xf8\x0f\x03\xe2\xbe\xc3\x94T\x05\xben\x9e' +t2 = _tls_PRF('Jefe', "Test Label YYYYYYY", "what do ya want for nothing?", 80) == 'n\xbet\x06\x82\x87\xcd\xea\xd9\x8b\xf8J\x17\x07\x84\xbc\xf3\x07\x9a\x99\n\xa6,\x97\xe6CRO\x7f\x0e[,\xa9\x83\xe6\xce?6\x12x\xc8Q\x00kO\x06s\xc5\xd7\xda\x1fd_\xe8\xad\xd4\xea\xfe\xd8\xc8 \x92e\x80\x8a\xafxF\xd6-/\x14\x94\x05a\x94\x0b\x1d\xf83' +t3 = _tls_PRF('\xaa'*20, "Test Label ZZ", '\xdd'*50, 80) == "Ad\xe2B\xa0\xb0+G#\x0f%\x19\xae\xdd\xb1d\xa0\x99\x15\x98\xa43c?\xaa\xd1\xc0\xf7\xc39V\xcb\x9b}\x95T\xd9\xde \xecr{/\xfb\x018\xeeR \x18Awi\x86=\xb4rg\x13\\\xaf<\x17\xd3_\xc5'U[\xa5\x83\xfa<\xa6\xc9\xdd\x85l\x1a\xdb" +t4 = _tls_PRF('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', "Test Label UUUUUUUUUUUUUUU", '\xcd'*50, 80) == '<\xf0\xe9\xaa\x95w\t\xa7\xb0!w\xf1EoC\x8fJ\x1f\xec\x80.\x89X\xe3O4Vl\xd1\xb7]\xa1\xb9o\xdf/&!\xb8n\xeb\x04"\xeftxs 6E+\xf1\xb3\xb6/vd\xd1h\xa3\x80>\x83Y\xbd]\xda\xab\xb8\xd8\x01\xc5b3K\xe7\x08\r\x12\x14' +t5 = _tls_PRF('\x0c'*20, "Test Label KKKKKKKKK", "Test With Truncation", 80) == "gq\xa5\xc4\xf5\x86z.\x03\n\xa3\x85\x87\xbc\xabm\xf1\xd2\x06\xf6\xbc\xc8\xab\xf0\xee\xd2>e'!\xd3zW\x81\x10|^(\x8d~\xa5s&p\xef]\rDa\x113\xa6z\x9f\xf2\xe2_}\xd8.u\xbe\xb1\x7fx\xe0r~\xdc\xa2\x0f\xcd\xcd\x1d\x81\x1a`#\xc6O" +t6 = _tls_PRF('\xaa'*80, "Test Label PPPPPPPPP", "Test Using Larger Than Block-Size Key - Hash Key First", 80) == '\x994^fx\x17\xbaaj\xc0"\xd1g\xbfh#uE\xee\xd8\xf1,\xab\xe7w\xfa\xc8\x0c\xf9\xcd\xbb\xbb\xa71U\xbe\xeb@\x90\xc2\x04\x93\xa5\xcf\x8e\xda\xbb\x93n\x99^\xa2{\x8b{\x18\xd7\xf7e\x8a~\xfbA\xdd\xc3\xd9\x9b\x1c\x82$\xf5YX{\xaa\xb4\xf2\x04\xb3%' +t7 = _tls_PRF('\xaa'*80, "Test Label MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM", "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == '\xd6N\x12S\x18]\x87\x19\xacD\x1b4\xc3"\xc2\xd9J\xb8\xee/\xb0?\xc2_\x10\xb2\x196\xdaXC\xe0Ft\xd3:a\xcd\xb8\xdd\x8a\xb6\xb1\xc6sx\xb8\x87\x8a\x93\xf8~\xad\xc7\xd1\xa7I=\xceVW\x0f\x9a\xcc-\x8cv^o\x12\xa4\xcd\x10\xb1\xb0\x1f\xdd\x94,\x03' +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test _ssl_PRF() += Crypto - _ssl_PRF behavior on test vectors +from scapy.layers.tls.crypto.prf import _ssl_PRF +t1 = _ssl_PRF('\x0b'*20, "Hi There", 80) == '\x0fo\xbe9\x83>~Bc\xaea^\x86\xd2b\x94X\xfd9Be\xe799\xf2\x00\xfcS\xd6\x1c=\xe5\x7fin\x1e\xf9r\xc8\xe6k\x19K\x8a\x85SK\xe5\xb7;A\x19b\x86F3M\x8d=\xcf\x15\xeedo\xd3\xae\xa2\x95\x8e\x80\x13\xabG\x8d\x1c,\x8c\xab\xf7\xd4' +t2 = _ssl_PRF('Jefe', "what do ya want for nothing?", 80) == '\x19\x9f\xb9{\x87.\xd0\xf5\xc4\t.\xb6#\xae\x95\xe0S~\x15\xce\xe6\xb7oe\xad\x127\xb8\xc2C?\r\x87\xa6\x7f\x86y\xfa\xae\xcf\x0e\xb9\x01\xa5B\x07\x9d\x95\xf1]\xdc\x1bCb&T\xa0\xb0\x8a3\xcf\\\xaf\xe8j/\xbdx\x13\\\x91\xc8\xdfZ\xde"R`K\xd6' +t3 = _ssl_PRF('\xaa'*20, '\xdd'*50, 80) == '\xe3*\xce\xdc?k{\x10\x80\x8dt\x0e\xdaA\xf9}\x1d\x8e|\xc9Ux\x88\\\xf1a\xcfJ\xedi\xc1[C-\xf3\xa4\xcc\xf9\xce\xa3P\xe3\x9ai\x0b\xb7\xce\x8bar\x93\xc5\x93\x1a\x82\xc8{\x1c\xf2\x87\x9d\xe1\xf5\x9e\x0c\xf6\xa6\x91\xb9\x97\x17Y,\x11\x00\rs\xdd\xcf]' +t4 = _ssl_PRF('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19', '\xcd'*50, 80) == "\x8c\x83!h\x1b\xf2\x96f\x04\x15\x80H\x88\xcb\x80\x03\xc0\xfc\x05\xe5q\x93]\xeb\t\xd4B\xbc\xa4{\xb9\xd8\xb6IF\xc2\x80\x87\x9e2*\x82\x0ef\xc8\xbbBi\xb15\x90\xd6MW\xebM\xd7\xf9u\xd5+\xa8\x81\x11'\x8c\x88]b\r,\xde\xd9d[t\t\x199\x0b" +t5 = _ssl_PRF('\x0c'*20, "Test With Truncation", 80) == "\x85\xf5\xe8\xd2\xddW$\x14\xde\x84\x08@\xca\x86\x8bZn\x07\x87AKg\x18\xc3\x1a'\xc2\xb9\xdd\x17\xb5K1\xb9\x9a=\xe4\x1f/\xfe\xa6\x96\x10\x0c\x15@:z\xbf\x1dM\xa3\x90\x01\xb67\x07Z\xe0\xfe}U=\x81\xb2~\xc6\x1a\xcb\xe7\x9b\x90+\xa0\x86\xb2\x8b\xae\xc7\x9f" +t6 = _ssl_PRF('\xaa'*80, "Test Using Larger Than Block-Size Key - Hash Key First", 80) == '\x99\x11\x92\x8dw\xf1\xab\xdfr\x96S\xf5\xc1\x96\xc0\x16W*=\xa49\xd0\xf0\xf15\x91le\xda\x16\xfe8\x834kC3\x1b\xdf\xfc\xd8\x82\xe1\x9c\xfe9(4\xf9\x9c\x12\xc5~\xd1\xdc\xf3\xe5\x91\xbd\xbb\xb5$\x1c\xe4fs\xf2\xedM\xb7pO\x17\xdf\x01K\xf8\xed2-' +t7 = _ssl_PRF('\xaa'*80, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 80) == "\x8esl|C\x81\x80vv\xe1\x89H\xc9'oC\x1b\xbe\xc3\xbbE\x04)\xed\x1c\x84\xa9)\x08\xf5\xeb-\x93\xe9\x0f}\xeb[\xc4w\xd53y$\x07\xdc\x0f\\\xfc\xb2\x05r+\x13\xd8\xc3\xe7Lsz\xa1\x03\x93\xdd-\xf9l\xb7\xe6\xb3\x7fM\xfa\x90\xadeo\xcer*" +t1 and t2 and t3 and t4 and t5 and t6 and t7 + + ++ Test PRF for TLS 1.2 += Crypto - _tls12_*_PRF behavior, using SHA-256, SHA-384 and SHA-512 +# https://www.ietf.org/mail-archive/web/tls/current/msg03416.html + +from scapy.layers.tls.crypto.prf import PRF +class _prf_tls12_sha256_test: + h= "SHA256" + k= "\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35" + s= "\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c" + o=("\xe3\xf2\x29\xba\x72\x7b\xe1\x7b\x8d\x12\x26\x20\x55\x7c\xd4\x53" + + "\xc2\xaa\xb2\x1d\x07\xc3\xd4\x95\x32\x9b\x52\xd4\xe6\x1e\xdb\x5a") + +class _prf_tls12_sha384_test: + h= "SHA384" + k= "\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf" + s= "\xcd\x66\x5c\xf6\xa8\x44\x7d\xd6\xff\x8b\x27\x55\x5e\xdb\x74\x65" + o=("\x7b\x0c\x18\xe9\xce\xd4\x10\xed\x18\x04\xf2\xcf\xa3\x4a\x33\x6a" + + "\x1c\x14\xdf\xfb\x49\x00\xbb\x5f\xd7\x94\x21\x07\xe8\x1c\x83\xcd") + +class _prf_tls12_sha512_test: + h= "SHA512" + k= "\xb0\x32\x35\x23\xc1\x85\x35\x99\x58\x4d\x88\x56\x8b\xbb\x05\xeb" + s= "\xd4\x64\x0e\x12\xe4\xbc\xdb\xfb\x43\x7f\x03\xe6\xae\x41\x8e\xe5" + o=("\x12\x61\xf5\x88\xc7\x98\xc5\xc2\x01\xff\x03\x6e\x7a\x9c\xb5\xed" + + "\xcd\x7f\xe3\xf9\x4c\x66\x9a\x12\x2a\x46\x38\xd7\xd5\x08\xb2\x83") + +def _all_prf_tls12_tests(): + res = True + for t in [ _prf_tls12_sha256_test, + _prf_tls12_sha384_test, + _prf_tls12_sha512_test ]: + p = PRF(tls_version=0x303, hash_name=t.h) + tmp = p.prf(t.k, "test label", t.s, 32) == t.o + res = res and tmp + return res + +_all_prf_tls12_tests() + + ++ Test compute_master_secret() for SSL += Crypto - compute_master_secret() in SSL mode +f = PRF(tls_version=0x300) +t1 = f.compute_master_secret("A"*48, "B"*32, "C"*32) == '\xe8\xb5O68e\x8c\x1e\xd0hD!\xc1Zk\x9e\xc7x3\xfc".\xf9\x17\xd5B\xfc\xef\x8d\xed\x9fP\xcer\x83|6\x02\xe0\x86\xda\xab-G\x8c\xa9H5' +t2 = f.compute_master_secret("A"*48, "C"*32, "B"*32) == 'Ts/q\x83\x88\x10\x9c1Y\xff\xf3vo\xe3\x8aM\x9b\xa3k[J\xeeWXs\xcfTe\x19\xc6\xb1\x0ebj1}\x0c\xca\x97=|\x88W\xd8q\xfb|' +t3 = f.compute_master_secret("C"*48, "A"*32, "B"*32) == 'Q\xde\x06L\xdb\xe9\x9dC\x19\x8a:m@\xce\xbf\xc0\n\xd8\xd4H!#\x06\xad\x929\x85\xc9@\x1f\xb5\xe2)^{c\x94\x06&\xad\xb56\x13^\xd6\xa5\x19\xe7' +t4 = f.compute_master_secret("D"*48, "B"*32, "A"*32) == '\xbe\x9a\xc8)\xb5{.H1\x8382\xc2\xdff\xdf@\xda\xde\x88\xe1\xf3\xad9\xcc\x14\xb1\x7f\x90\x00;B)\x8c\xdb\xdbH\xfe=%^\xe9\x83\x0eV\x86\x83\x8d' +t1 and t2 and t3 and t4 + + ++ Test derive_key_block() for SSL += Crypto - derive_key_block() in SSL mode +t1 = f.derive_key_block("A"*48, "B"*32, "C"*32, 72) == '\xe8\xb5O68e\x8c\x1e\xd0hD!\xc1Zk\x9e\xc7x3\xfc".\xf9\x17\xd5B\xfc\xef\x8d\xed\x9fP\xcer\x83|6\x02\xe0\x86\xda\xab-G\x8c\xa9H5\xdf\x14\xa9\xcfV\r\xea}\x98\x04\x8dK,\xb6\xf7;\xaa\xa8\xa5\xad\x7f\x0fCY' +t2 = f.derive_key_block("A"*48, "C"*32, "B"*32, 72) == 'Ts/q\x83\x88\x10\x9c1Y\xff\xf3vo\xe3\x8aM\x9b\xa3k[J\xeeWXs\xcfTe\x19\xc6\xb1\x0ebj1}\x0c\xca\x97=|\x88W\xd8q\xfb|\x17\x99\nH;\xec\xd2\x15\xabd\xed\xc3\xe0p\xd8\x1eS\xb5\xf4*8\xceE^' +t3 = f.derive_key_block("C"*48, "A"*32, "B"*32, 72) == 'Q\xde\x06L\xdb\xe9\x9dC\x19\x8a:m@\xce\xbf\xc0\n\xd8\xd4H!#\x06\xad\x929\x85\xc9@\x1f\xb5\xe2)^{c\x94\x06&\xad\xb56\x13^\xd6\xa5\x19\xe7\xed\xd6\x92\xe0O\x0e\xbf\xc6\x97\x9f~\x95\xcf\xb0\xe7a\x1d\xbc]\xf4&Z\x81J' +t4 = f.derive_key_block("D"*48, "B"*32, "A"*32, 72) == '\xbe\x9a\xc8)\xb5{.H1\x8382\xc2\xdff\xdf@\xda\xde\x88\xe1\xf3\xad9\xcc\x14\xb1\x7f\x90\x00;B)\x8c\xdb\xdbH\xfe=%^\xe9\x83\x0eV\x86\x83\x8d\xeal\x8ea\x08\x9d\xb3\xf3\xf4\xa6[\'j\xda\rT"\x10\xa5Z\n\xc0r\xf3' +t1 and t2 and t3 and t4 + + ++ Test compute_master_secret() for TLS 1.0 += Crypto - compute_master_secret() in TLS 1.0 mode +from scapy.layers.tls.crypto.prf import PRF +f = PRF(tls_version=0x301) +t1 = f.compute_master_secret("A"*48, "B"*32, "C"*32) == "k\\[e\x11\xab\xfe6\trN\x9e\x8d\xb09{\x17\x8d\x9f\xc6_' G\x05\x08}\xf7Q\x8e\xcb\xff\x00\xfc7\xd0\xf0z\xea\x8b\x98%\x90\x89sd\x98\xa1" +t2 = f.compute_master_secret("A"*48, "C"*32, "B"*32) == 'k\xd2\xf7\x1aqt\xa4~\x9bqf\x0f:\xc4%\x9a\x07\x17\x14\xf4\xdf&)*\x1c\x9c8\x8em\xe1\x13\x17\xa7\xd2\x051Q<M~\xc2a\x85\x82\xe6\xd7.[' +t3 = f.compute_master_secret("C"*48, "A"*32, "B"*32) == '\xe57\xae.,B\xeb(/?\xf4tR#\xd0\xa9"\xf7-\x9d\x0e\xd7\xd9\x1c\x1f\x9b\x95\xe6\xd0\x0e(\x06W7s(^"x\xbb\xdb\xb6\xae\xf75J\x0f\xbf' +t4 = f.compute_master_secret("D"*48, "B"*32, "A"*32) == '\xeb3\xf5Ty\x08xqP\x01p\x12\x95\xd4\xf5y{\xe7\xea5\nS\xb1T\xea\xe3d\x8b\xd7\xb89\xcf\xb9\xe0l\x95d\xbd-\x97\xea\xf20n\x96t\xfe\xff' +t1 and t2 and t3 and t4 + + ++ Test derive_key_block() for TLS 1.0 += Crypto - derive_key_block() in TLS 1.0 mode +t1 = f.derive_key_block("A"*48, "B"*32, "C"*32, 72) == '\x06\xccA\xd5\xf3\x9dT`ZC!/\xa0\xbe\x95\x86m\xdb@\x18\xfb\x95\xad\xcd\xac<(K\x88\xacB\x92s\x8d7AVG\xf04\x0be\x8dv\x02\xd6\x03\x7f\xe4\x8eYe\x88\xb7YI\xc2\xf0!\x1dSx\x86\xdeY\x81\x89\x11\xa6\xd9\xd1\xed' +t2 = f.derive_key_block("A"*48, "C"*32, "B"*32, 72) == "\\@d\x1d9V\xae\xe2'\xf6Q\xc9\xd7\x8beu\xe8u\xd9\xe8\r\x18a\x8c|\xde\x95H\xec\xc5}I\xf9s(e\xe4\x87*s\x98=\x96wsj\xfe\x0euo\x1f\\1hh-\x0f\xda9\x9etk\x0fW\x03\xe2k\xb0\x87Pb3" +t3 = f.derive_key_block("C"*48, "A"*32, "B"*32, 72) == '\x9c\xaate\x07\x12K\xb2\xc3zT1\xf4\x1fN\xa8\x03\xbd\xcfF_\x0c\x0bF\x14\x8f\xcf\x08c\xa6\x80\x1d\xd8Wh.E\xf5\x9a\xfd\x1d\x8a6\xf7\x950\xf4\xbcm\x89\xa6!\x7fc\x19D\xb4\xcc\x8f\xf7x\x12\xe0q\x17\x84-\xcc[\x7f@p' +t4 = f.derive_key_block("D"*48, "B"*32, "A"*32, 72) == 't{P+k\xe1\xe5O\xbe]L?$\x8d7O.\xe6\xd6\xa8\x19U\x87\x04%\x13m+_\xb9\x99\x03\xe1\xfd1]*7\x8d\xa0Xx\xa1\xd1\xfe\x0c\xb1\xb1\xa8\xdd\x0c\xb20@v\xb6\xdc\x86d\n\x8a-\x95\xaeL\x97\xfaFjl\xfb^' +t1 and t2 and t3 and t4 + + +############################################################################### +### Ciphers ### +############################################################################### + ++ Test RC4 += Crypto - RC4 stream cipher, encryption/decryption checks from RFC 6229 + +class _rc4_40_test: + k= "\x01\x02\x03\x04\x05" + s=("\xb2\x39\x63\x05\xf0\x3d\xc0\x27\xcc\xc3\x52\x4a\x0a\x11\x18\xa8" + + "\x69\x82\x94\x4f\x18\xfc\x82\xd5\x89\xc4\x03\xa4\x7a\x0d\x09\x19") + s_1024= "\x30\xab\xbc\xc7\xc2\x0b\x01\x60\x9f\x23\xee\x2d\x5f\x6b\xb7\xdf" + +class _rc4_128_test: + k= "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + s=("\x9a\xc7\xcc\x9a\x60\x9d\x1e\xf7\xb2\x93\x28\x99\xcd\xe4\x1b\x97" + "\x52\x48\xc4\x95\x90\x14\x12\x6a\x6e\x8a\x84\xf1\x1d\x1a\x9e\x1c") + s_1024="\xbd\xf0\x32\x4e\x60\x83\xdc\xc6\xd3\xce\xdd\x3c\xa8\xc5\x3c\x16" + +def _all_rc4_tests(): + from scapy.layers.tls.crypto.cipher_stream import (Cipher_RC4_40, + Cipher_RC4_128) + res = True + t = _rc4_40_test + c = Cipher_RC4_40(t.k).encrypt("\x00"*(1024+16)) + res = res and (c[:32] == t.s) and (c[-16:] == t.s_1024) + res = res and Cipher_RC4_40(t.k).decrypt(t.s) == "\x00"*32 + t = _rc4_128_test + c = Cipher_RC4_128(t.k).encrypt("\x00"*(1024+16)) + res = res and (c[:32] == t.s) and (c[-16:] == t.s_1024) + res = res and Cipher_RC4_128(t.k).decrypt(t.s) == "\x00"*32 + return res + +_all_rc4_tests() + + ++ Test AES-CBC += Crypto - AES cipher in CBC mode, checks from RFC 3602 + +class _aes128cbc_test_1: + k= "\x06\xa9\x21\x40\x36\xb8\xa1\x5b\x51\x2e\x03\xd5\x34\x12\x00\x06" + p= "Single block msg" + c= "\xe3\x53\x77\x9c\x10\x79\xae\xb8\x27\x08\x94\x2d\xbe\x77\x18\x1a" + iv="\x3d\xaf\xba\x42\x9d\x9e\xb4\x30\xb4\x22\xda\x80\x2c\x9f\xac\x41" + +class _aes128cbc_test_2: + k= "\x56\xe4\x7a\x38\xc5\x59\x89\x74\xbc\x46\x90\x3d\xba\x29\x03\x49" + p=("\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" + + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" + + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" + + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf") + c=("\xc3\x0e\x32\xff\xed\xc0\x77\x4e\x6a\xff\x6a\xf0\x86\x9f\x71\xaa" + + "\x0f\x3a\xf0\x7a\x9a\x31\xa9\xc6\x84\xdb\x20\x7e\xb0\xef\x8e\x4e" + + "\x35\x90\x7a\xa6\x32\xc3\xff\xdf\x86\x8b\xb7\xb2\x9d\x3d\x46\xad" + + "\x83\xce\x9f\x9a\x10\x2e\xe9\x9d\x49\xa5\x3e\x87\xf4\xc3\xda\x55") + iv="\x8c\xe8\x2e\xef\xbe\xa0\xda\x3c\x44\x69\x9e\xd7\xdb\x51\xb7\xd9" + +class _aes256cbc_test_1: + k=("\x60\x3d\xeb\x10\x15\xca\x71\xbe\x2b\x73\xae\xf0\x85\x7d\x77\x81" + + "\x1f\x35\x2c\x07\x3b\x61\x08\xd7\x2d\x98\x10\xa3\x09\x14\xdf\xf4") + p= "\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96\xe9\x3d\x7e\x11\x73\x93\x17\x2a" + c= "\xf5\x8c\x4c\x04\xd6\xe5\xf1\xba\x77\x9e\xab\xfb\x5f\x7b\xfb\xd6" + iv="\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + +class _aes256cbc_test_2: + k=("\x60\x3d\xeb\x10\x15\xca\x71\xbe\x2b\x73\xae\xf0\x85\x7d\x77\x81" + + "\x1f\x35\x2c\x07\x3b\x61\x08\xd7\x2d\x98\x10\xa3\x09\x14\xdf\xf4") + p= "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17\xad\x2b\x41\x7b\xe6\x6c\x37\x10" + c= "\xb2\xeb\x05\xe2\xc3\x9b\xe9\xfc\xda\x6c\x19\x07\x8c\x6a\x9d\x1b" + iv="\x39\xF2\x33\x69\xA9\xD9\xBA\xCF\xA5\x30\xE2\x63\x04\x23\x14\x61" + +def _all_aes_cbc_tests(): + from scapy.layers.tls.crypto.cipher_block import (Cipher_AES_128_CBC, + Cipher_AES_256_CBC) + res = True + for t in [_aes128cbc_test_1, _aes128cbc_test_2]: + tmp = (Cipher_AES_128_CBC(t.k, t.iv).encrypt(t.p) == t.c and + Cipher_AES_128_CBC(t.k, t.iv).decrypt(t.c) == t.p) + res = res and tmp + for t in [_aes256cbc_test_1, _aes256cbc_test_2]: + tmp = (Cipher_AES_256_CBC(t.k, t.iv).encrypt(t.p) == t.c and + Cipher_AES_256_CBC(t.k, t.iv).decrypt(t.c) == t.p) + res = res and tmp + return res + +_all_aes_cbc_tests() + + ++ Test AES-GCM += Crypto - AES cipher in GCM mode, auth_encrypt() and auth_decrypt() checks +~ combined_modes +#https://tools.ietf.org/html/draft-mcgrew-gcm-test-01 + +class _aes128gcm_test_1: + k= "\x4c\x80\xcd\xef\xbb\x5d\x10\xda\x90\x6a\xc7\x3c\x36\x13\xa6\x34" + n= "\x22\x43\x3c\x64\x48\x55\xec\x7d\x3a\x23\x4b\xfd" + p=("\x08\x00\xc6\xcd\x02\x00\x07\x00\x61\x62\x63\x64\x65\x66\x67\x68" + + "\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x01\x02\x02\x01") + a= "\x00\x00\x43\x21\x87\x65\x43\x21\x00\x00\x00\x07" + ct=("\x74\x75\x2e\x8a\xeb\x5d\x87\x3c\xd7\xc0\xf4\xac\xc3\x6c\x4b\xff" + + "\x84\xb7\xd7\xb9\x8f\x0c\xa8\xb6\xac\xda\x68\x94\xbc\x61\x90\x69" + + "\xef\x9c\xbc\x28\xfe\x1b\x56\xa7\xc4\xe0\xd5\x8c\x86\xcd\x2b\xc0") + +class _aes128gcm_test_2: + k= "\x3d\xe0\x98\x74\xb3\x88\xe6\x49\x19\x88\xd0\xc3\x60\x7e\xae\x1f" + n= "\x57\x69\x0e\x43\x4e\x28\x00\x00\xa2\xfc\xa1\xa3" + p=("\x45\x00\x00\x30\xda\x3a\x00\x00\x80\x01\xdf\x3b\xc0\xa8\x00\x05" + + "\xc0\xa8\x00\x01\x08\x00\xc6\xcd\x02\x00\x07\x00\x61\x62\x63\x64" + + "\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74" + + "\x01\x02\x02\x01") + a= "\x3f\x7e\xf6\x42\x10\x10\x10\x10\x10\x10\x10\x10" + ct=("\xfb\xa2\xca\xa8\xc6\xc5\xf9\xf0\xf2\x2c\xa5\x4a\x06\x12\x10\xad" + + "\x3f\x6e\x57\x91\xcf\x1a\xca\x21\x0d\x11\x7c\xec\x9c\x35\x79\x17" + + "\x65\xac\xbd\x87\x01\xad\x79\x84\x5b\xf9\xfe\x3f\xba\x48\x7b\xc9" + + "\x63\x21\x93\x06\x84\xee\xca\xdb\x56\x91\x25\x46\xe7\xa9\x5c\x97" + + "\x40\xd7\xcb\x05") + +class _aes256gcm_test_1: + k=("\x6c\x65\x67\x61\x6c\x69\x7a\x65\x6d\x61\x72\x69\x6a\x75\x61\x6e" + + "\x61\x61\x6e\x64\x64\x6f\x69\x74\x62\x65\x66\x6f\x72\x65\x69\x61") + n= "\x74\x75\x72\x6e\x33\x30\x21\x69\x67\x65\x74\x6d" + p=("\x45\x00\x00\x30\xda\x3a\x00\x00\x80\x01\xdf\x3b\xc0\xa8\x00\x05" + + "\xc0\xa8\x00\x01\x08\x00\xc6\xcd\x02\x00\x07\x00\x61\x62\x63\x64" + + "\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74" + + "\x01\x02\x02\x01") + a= "\x79\x6b\x69\x63\xff\xff\xff\xff\xff\xff\xff\xff" + ct=("\xf9\x7a\xb2\xaa\x35\x6d\x8e\xdc\xe1\x76\x44\xac\x8c\x78\xe2\x5d" + + "\xd2\x4d\xed\xbb\x29\xeb\xf1\xb6\x4a\x27\x4b\x39\xb4\x9c\x3a\x86" + + "\x4c\xd3\xd7\x8c\xa4\xae\x68\xa3\x2b\x42\x45\x8f\xb5\x7d\xbe\x82" + + "\x1d\xcc\x63\xb9\xd0\x93\x7b\xa2\x94\x5f\x66\x93\x68\x66\x1a\x32" + + "\x9f\xb4\xc0\x53") + +class _aes256gcm_test_2: + # this funny plaintext is not our deed + k=("\xab\xbc\xcd\xde\xf0\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a\xab" + + "\xab\xbc\xcd\xde\xf0\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a\xab") + n= "\x73\x61\x6c\x74\x61\x6e\x64\x01\x69\x76\x65\x63" + p=("\x63\x69\x73\x63\x6f\x01\x72\x75\x6c\x65\x73\x01\x74\x68\x65\x01" + + "\x6e\x65\x74\x77\x65\x01\x64\x65\x66\x69\x6e\x65\x01\x74\x68\x65" + + "\x74\x65\x63\x68\x6e\x6f\x6c\x6f\x67\x69\x65\x73\x01\x74\x68\x61" + + "\x74\x77\x69\x6c\x6c\x01\x64\x65\x66\x69\x6e\x65\x74\x6f\x6d\x6f" + + "\x72\x72\x6f\x77\x01\x02\x02\x01") + a= "\x17\x40\x5e\x67\x15\x6f\x31\x26\xdd\x0d\xb9\x9b" + ct=("\xd4\xb7\xed\x86\xa1\x77\x7f\x2e\xa1\x3d\x69\x73\xd3\x24\xc6\x9e" + + "\x7b\x43\xf8\x26\xfb\x56\x83\x12\x26\x50\x8b\xeb\xd2\xdc\xeb\x18" + + "\xd0\xa6\xdf\x10\xe5\x48\x7d\xf0\x74\x11\x3e\x14\xc6\x41\x02\x4e" + + "\x3e\x67\x73\xd9\x1a\x62\xee\x42\x9b\x04\x3a\x10\xe3\xef\xe6\xb0" + + "\x12\xa4\x93\x63\x41\x23\x64\xf8\xc0\xca\xc5\x87\xf2\x49\xe5\x6b" + + "\x11\xe2\x4f\x30\xe4\x4c\xcc\x76") + +def _all_aes_gcm_tests(): + from scapy.layers.tls.crypto.cipher_aead import (Cipher_AES_128_GCM, + Cipher_AES_256_GCM) + res = True + ciphers = [] + for t in [_aes128gcm_test_1, _aes128gcm_test_2]: + c = Cipher_AES_128_GCM(key=t.k, salt=t.n[:4], + nonce_explicit=pkcs_os2ip(t.n[4:])) + ne = t.n[-c.nonce_explicit_len:] + tup = ne, t.p, t.ct[-c.tag_len:] + tmp1 = c.auth_decrypt(t.a, ne + t.ct, add_length=False) == tup + tmp2 = c.auth_encrypt(t.p, t.a) == (ne + t.ct) + res = res and tmp1 and tmp2 + for t in [_aes256gcm_test_1, _aes256gcm_test_2]: + c = Cipher_AES_256_GCM(key=t.k, salt=t.n[:4], + nonce_explicit=pkcs_os2ip(t.n[4:])) + ne = t.n[-c.nonce_explicit_len:] + tup = ne, t.p, t.ct[-c.tag_len:] + tmp1 = c.auth_decrypt(t.a, ne + t.ct, add_length=False) == tup + tmp2 = c.auth_encrypt(t.p, t.a) == (ne + t.ct) + res = res and tmp1 and tmp2 + return res + +_all_aes_gcm_tests() + + +#XXX CCM remains to be added to the cryptography library +#+ Test AES-CCM +#= Crypto - AES cipher in CCM mode, checks from IEEE P1619.1 +#~ combined_modes +# +#class _aes256ccm_test_1: +# k= "\0"*32 +# n= "\0"*12 +# p= "\0"*16 +# a= "" +# ct=("\xc1\x94\x40\x44\xc8\xe7\xaa\x95\xd2\xde\x95\x13\xc7\xf3\xdd\x8c" + +# "\x4b\x0a\x3e\x5e\x51\xf1\x51\xeb\x0f\xfa\xe7\xc4\x3d\x01\x0f\xdb") +# +#class _aes256ccm_test_2: +# k=("\xfb\x76\x15\xb2\x3d\x80\x89\x1d\xd4\x70\x98\x0b\xc7\x95\x84\xc8" + +# "\xb2\xfb\x64\xce\x60\x97\x87\x8d\x17\xfc\xe4\x5a\x49\xe8\x30\xb7") +# n= "\xdb\xd1\xa3\x63\x60\x24\xb7\xb4\x02\xda\x7d\x6f" +# p= "\xa9" +# a= "\x36" +# ct="\x9d\x32\x61\xb1\xcf\x93\x14\x31\xe9\x9a\x32\x80\x67\x38\xec\xbd\x2a" +# +#class _aes256ccm_test_3: +# k=("\xfb\x76\x15\xb2\x3d\x80\x89\x1d\xd4\x70\x98\x0b\xc7\x95\x84\xc8" + +# "\xb2\xfb\x64\xce\x60\x97\x8f\x4d\x17\xfc\xe4\x5a\x49\xe8\x30\xb7") +# n= "\xdb\xd1\xa3\x63\x60\x24\xb7\xb4\x02\xda\x7d\x6f" +# p= "\xa8\x45\x34\x8e\xc8\xc5\xb5\xf1\x26\xf5\x0e\x76\xfe\xfd\x1b\x1e" +# a= "" +# ct=("\xcc\x88\x12\x61\xc6\xa7\xfa\x72\xb9\x6a\x17\x39\x17\x6b\x27\x7f" + +# "\x34\x72\xe1\x14\x5f\x2c\x0c\xbe\x14\x63\x49\x06\x2c\xf0\xe4\x23") +# +#class _aes256ccm_test_4: +# k=("\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" + +# "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f") +# n= "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b" +# p=("\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + +# "\x30\x31\x32\x33\x34\x35\x36\x37") +# a=("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + +# "\x10\x11\x12\x13") +# ct=("\x04\xf8\x83\xae\xb3\xbd\x07\x30\xea\xf5\x0b\xb6\xde\x4f\xa2\x21" + +# "\x20\x34\xe4\xe4\x1b\x0e\x75\xe5\x9b\xba\x3f\x3a\x10\x7f\x32\x39" + +# "\xbd\x63\x90\x29\x23\xf8\x03\x71") +# +#def _all_aes_ccm_tests(): +# from scapy.layers.tls.crypto.cipher_aead import Cipher_AES_256_CCM +# res = True +# ciphers = [] +# for t in [_aes256ccm_test_1, _aes256ccm_test_2, +# _aes256ccm_test_3, _aes256ccm_test_4]: +# c = Cipher_AES_256_CCM(key=t.k, salt=t.n[:4], +# nonce_explicit_init=pkcs_os2ip(t.n[4:])) +# ne = t.n[-c.nonce_explicit_len:] +# tup = ne, t.p, t.ct[-c.tag_len:] +# tmp1 = c.auth_decrypt(t.a, ne + t.ct, add_length=False) == tup +# tmp2 = c.auth_encrypt(t.p, t.a) == (ne + t.ct) +# res = res and tmp1 and tmp2 +# return res +# +#_all_aes_ccm_tests() + + ++ Test camellia += Crypto - Camellia cipher, encryption/decryption checks + +class _Camellia128_test: + k= "\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10" + p= "\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10" + c= "\x67\x67\x31\x38\x54\x96\x69\x73\x08\x57\x06\x56\x48\xea\xbe\x43" + iv="\0"*16 + +class _Camellia256_test: + k=("\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10" + + "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff") + p= "\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10" + c= "\x9a\xcc\x23\x7d\xff\x16\xd7\x6c\x20\xef\x7c\x91\x9e\x3a\x75\x09" + iv="\0"*16 + +def _all_camellia_tests(): + from scapy.layers.tls.crypto.cipher_block import (Cipher_CAMELLIA_128_CBC, + Cipher_CAMELLIA_256_CBC) + res = True + t = _Camellia128_test + tmp = (Cipher_CAMELLIA_128_CBC(t.k, t.iv).encrypt(t.p) == t.c and + Cipher_CAMELLIA_128_CBC(t.k, t.iv).decrypt(t.c) == t.p) + res = res and tmp + t = _Camellia256_test + tmp = (Cipher_CAMELLIA_256_CBC(t.k, t.iv).encrypt(t.p) == t.c and + Cipher_CAMELLIA_256_CBC(t.k, t.iv).decrypt(t.c) == t.p) + res = res and tmp + return res + +_all_camellia_tests() + + +############################################################################### +############################ Reading test session ############################# +############################################################################### + +# These packets come from a random TLS thread captured +# during a github connection from a Mozilla Firefox client. + ++ Test strings += Reading test session - Loading unparsed TLS records +p1_ch = '\x16\x03\x01\x00\xd5\x01\x00\x00\xd1\x03\x03\x17\xf2M\xc3|\x19\xdb\xc3<\xb5J\x0b\x8d5\x81\xc5\xce\t 2\x08\xd8\xec\xd1\xf8"B\x9cW\xd0\x16v\x00\x00\x16\xc0+\xc0/\xc0\n\xc0\t\xc0\x13\xc0\x14\x003\x009\x00/\x005\x00\n\x01\x00\x00\x92\x00\x00\x00\x1f\x00\x1d\x00\x00\x1acamo.githubusercontent.com\xff\x01\x00\x01\x00\x00\n\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00#\x00\x003t\x00\x00\x00\x10\x00)\x00\'\x05h2-16\x05h2-15\x05h2-14\x02h2\x08spdy/3.1\x08http/1.1\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\r\x00\x16\x00\x14\x04\x01\x05\x01\x06\x01\x02\x01\x04\x03\x05\x03\x06\x03\x02\x03\x04\x02\x02\x02' +p2_sh = '\x16\x03\x03\x00T\x02\x00\x00P\x03\x03F\x07n\xe2\x0c\x97g\xb7o\xb6\x9b\x14\x19\xbd\xdd1\x80@\xaaQ+\xc2,\x19\x15"\x82\xe8\xc5,\xe8\x12\x00\xc0/\x00\x00(\x00\x00\x00\x00\xff\x01\x00\x01\x00\x00\x0b\x00\x04\x03\x00\x01\x02\x00#\x00\x00\x00\x05\x00\x00\x00\x10\x00\x0b\x00\t\x08http/1.1' +p3_cert = '\x16\x03\x03\nu\x0b\x00\nq\x00\nn\x00\x05\xb30\x82\x05\xaf0\x82\x04\x97\xa0\x03\x02\x01\x02\x02\x10\x07z]\xc36#\x01\xf9\x89\xfeT\xf7\xf8o>d0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000p1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1/0-\x06\x03U\x04\x03\x13&DigiCert SHA2 High Assurance Server CA0\x1e\x17\r160120000000Z\x17\r170406120000Z0j1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x13\nCalifornia1\x160\x14\x06\x03U\x04\x07\x13\rSan Francisco1\x150\x13\x06\x03U\x04\n\x13\x0cFastly, Inc.1\x170\x15\x06\x03U\x04\x03\x13\x0ewww.github.com0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xfb\xd5\x94\n\n\xe0P\xdc\x0f\xfc\x90\xb7qG\x9f,\x05\xde\x0e\x9a\xbc*\x8f\xd4\xf2\x9f\x08F\xf9\xf2\xd1\x18\xb4#\xa5*\xd2\xdf\x91?\xf9\xc5\xd0\xb2@\xbd\xd6\xbc@v.\x8d\xd8\x1e\r7\x8fz\x90W\xef\xe3\xa2\xc0\x11a\x03F\x0e\xfa\xb37\x0bf|!\x16\x8d\xfe/^.Y\xfec\':\xf3\xeds\xf8Mt\xb3Q\x17u\x9a\xed\x0ck\xcd\xe8\xc1\xea\xca\x01\xacu\xf9\x17)\xf0KP\x9dAdHl\xf6\xc0g}\xc8\xea\xdeHy\x81\x97A\x02\xb7F\xf6^M\xa5\xd9\x90\x86\xd7\x1ehQ\xac>%\xae\'\x11\xb1G4\xb8\x8b\xdeoyA\xd6\x92\x13)\x11\x80\xc4\x10\x17\\\x0clj\x02\xbb\xd0\n\xfc\xd2\x96x\x1d\xb6\xd4\x02\x7f\x1f\x0eR@Sop@\xda\x89)O\x0c\t~\xa3\xec\xc5W\xad\x03\xaa\x91\xedC\\\xf9\xf5[\xe8\xa1\xf0\xbem\x1b\xce-\xabC|p\xdc?\xec\xc9\x11\xf0t\xc9)\xa1P\xd0<)8\xdc\x7fV\xb9\xf8\x1f\x04\xa4^\x9f\xce\xdd\x17\x02\x03\x01\x00\x01\xa3\x82\x02I0\x82\x02E0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14d\xbfD\xb3F\t\x9b\xcfZ\x1dqI\xa2\x04r\x8b\x884\x84#0{\x06\x03U\x1d\x11\x04t0r\x82\x0ewww.github.com\x82\x0c*.github.com\x82\ngithub.com\x82\x0b*.github.io\x82\tgithub.io\x82\x17*.githubusercontent.com\x82\x15githubusercontent.com0\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa00\x1d\x06\x03U\x1d%\x04\x160\x14\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x020u\x06\x03U\x1d\x1f\x04n0l04\xa02\xa00\x86.http://crl3.digicert.com/sha2-ha-server-g5.crl04\xa02\xa00\x86.http://crl4.digicert.com/sha2-ha-server-g5.crl0L\x06\x03U\x1d \x04E0C07\x06\t`\x86H\x01\x86\xfdl\x01\x010*0(\x06\x08+\x06\x01\x05\x05\x07\x02\x01\x16\x1chttps://www.digicert.com/CPS0\x08\x06\x06g\x81\x0c\x01\x02\x020\x81\x83\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04w0u0$\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x18http://ocsp.digicert.com0M\x06\x08+\x06\x01\x05\x05\x070\x02\x86Ahttp://cacerts.digicert.com/DigiCertSHA2HighAssuranceServerCA.crt0\x0c\x06\x03U\x1d\x13\x01\x01\xff\x04\x020\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00O\x16\xd1t\xf8>\xa3\x8f~\xf7\xaf\xcf\xfa\xb6\xdd\xa7\x88\x9e\xf8!\xad|(\x14\xb9\xb4\xffg\xd0\xb9\xe2O\x81}\x03\xb4\x9d\xbcU\x80$\x8c\xe5fP\xb8\xb8(\xd9\x0f\xb4\x95\xccb\xb2\x87|\xcf\x16^SH\xf9\xc2\xf8\x90 \xdc\x0e\x96\x7f\xe27\xcfA\xc7uf\r\x1c\xa7M\xee\x02\xaa\x1b\x00\xc0\xea\x0e\xd4Df\x08\t\xac\x00\x90pc\xfa\xcd\xaf\x89\x8a\xdbj|z\xb0k\xa8\xc5\xb4\x9d\x85\xd8S\x93E\xcar>\xa4\xd4\xe3\xa28J\x0f\x82\x08\xf0\xf3U\xf0m\xb21l\x189\xbf\xee\xe3\xe5\x8f\xcd@\x07\x0b\xd0\xe9e\xda\xd6LA\xff[\xafB\xaf\xf2\xb1F\xa1\xacX\xfc)\x80\xcb\xf6Z\xa6\xaf\xf26\x93\xdf\x92q\xa95\xe3:XP\xab::|\xd9\xf7y\x83\x9e\t\xfe\x0f\x90,Y+\x07$Z<\xb5\xd2\xa0\xdaE\xb8\xe1\xc0\x03\x07\x00h\xf6L\xfa\xe2v[\xce\x8f\xfe\xd0\xcb%\xf9\x9b\xcb\xa9\xffU\x12\xf3=_En2\xa0$\x8e\xb7\xa5vo\x0b\x87\xe9\x00\x04\xb50\x82\x04\xb10\x82\x03\x99\xa0\x03\x02\x01\x02\x02\x10\x04\xe1\xe7\xa4\xdc\\\xf2\xf3m\xc0+B\xb8]\x15\x9f0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000l1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1+0)\x06\x03U\x04\x03\x13"DigiCert High Assurance EV Root CA0\x1e\x17\r131022120000Z\x17\r281022120000Z0p1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\x0cDigiCert Inc1\x190\x17\x06\x03U\x04\x0b\x13\x10www.digicert.com1/0-\x06\x03U\x04\x03\x13&DigiCert SHA2 High Assurance Server CA0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xb6\xe0/\xc2$\x06\xc8m\x04_\xd7\xef\nd\x06\xb2}"&e\x16\xaeB@\x9b\xce\xdc\x9f\x9fv\x07>\xc30U\x87\x19\xb9O\x94\x0eZ\x94\x1fUV\xb4\xc2\x02*\xaf\xd0\x98\xee\x0b@\xd7\xc4\xd0;r\xc8\x14\x9e\xef\x90\xb1\x11\xa9\xae\xd2\xc8\xb8C:\xd9\x0b\x0b\xd5\xd5\x95\xf5@\xaf\xc8\x1d\xedM\x9c_W\xb7\x86Ph\x99\xf5\x8a\xda\xd2\xc7\x05\x1f\xa8\x97\xc9\xdc\xa4\xb1\x82\x84-\xc6\xad\xa5\x9c\xc7\x19\x82\xa6\x85\x0f^DX*7\x8f\xfd5\xf1\x0b\x08\'2Z\xf5\xbb\x8b\x9e\xa4\xbdQ\xd0\'\xe2\xdd;B3\xa3\x05(\xc4\xbb(\xcc\x9a\xac+#\rx\xc6{\xe6^q\xb7J>\x08\xfb\x81\xb7\x16\x16\xa1\x9d#\x12M\xe5\xd7\x92\x08\xacu\xa4\x9c\xba\xcd\x17\xb2\x1eD5e\x7fS%9\xd1\x1c\n\x9ac\x1b\x19\x92th\n7\xc2\xc2RH\xcb9Z\xa2\xb6\xe1]\xc1\xdd\xa0 \xb8!\xa2\x93&o\x14J!A\xc7\xedm\x9b\xf2H/\xf3\x03\xf5\xa2h\x92S/^\xe3\x02\x03\x01\x00\x01\xa3\x82\x01I0\x82\x01E0\x12\x06\x03U\x1d\x13\x01\x01\xff\x04\x080\x06\x01\x01\xff\x02\x01\x000\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x01\x860\x1d\x06\x03U\x1d%\x04\x160\x14\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x0204\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04(0&0$\x06\x08+\x06\x01\x05\x05\x070\x01\x86\x18http://ocsp.digicert.com0K\x06\x03U\x1d\x1f\x04D0B0@\xa0>\xa0<\x86:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl0=\x06\x03U\x1d \x0460402\x06\x04U\x1d \x000*0(\x06\x08+\x06\x01\x05\x05\x07\x02\x01\x16\x1chttps://www.digicert.com/CPS0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xb1>\xc3i\x03\xf8\xbfG\x01\xd4\x98&\x1a\x08\x02\xefcd+\xc30\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x18\x8a\x95\x89\x03\xe6m\xdf\\\xfc\x1dh\xeaJ\x8f\x83\xd6Q/\x8dkD\x16\x9e\xacc\xf5\xd2nl\x84\x99\x8b\xaa\x81q\x84[\xed4N\xb0\xb7y\x92)\xcc-\x80j\xf0\x8e \xe1y\xa4\xfe\x03G\x13\xea\xf5\x86\xcaYq}\xf4\x04\x96k\xd3YX=\xfe\xd31%\\\x188\x84\xa3\xe6\x9f\x82\xfd\x8c[\x981N\xcdx\x9e\x1a\xfd\x85\xcbI\xaa\xf2\'\x8b\x99r\xfc>\xaa\xd5A\x0b\xda\xd56\xa1\xbf\x1cnGI\x7f^\xd9H|\x03\xd9\xfd\x8bI\xa0\x98&B@\xeb\xd6\x92\x11\xa4d\nWT\xc4\xf5\x1d\xd6\x02^k\xac\xee\xc4\x80\x9a\x12r\xfaV\x93\xd7\xff\xbf0\x85\x060\xbf\x0b\x7fN\xffW\x05\x9d$\xed\x85\xc3+\xfb\xa6u\xa8\xac-\x16\xef}y\'\xb2\xeb\xc2\x9d\x0b\x07\xea\xaa\x85\xd3\x01\xa3 (AYC(\xd2\x81\xe3\xaa\xf6\xec{;w\xb6@b\x80\x05AE\x01\xef\x17\x06>\xde\xc03\x9bg\xd3a.r\x87\xe4i\xfc\x12\x00W@\x1ep\xf5\x1e\xc9\xb4' +p4_certstat_ske_shd = '\x16\x03\x03\x01\xdf\x16\x00\x01\xdb\x01\x00\x01\xd70\x82\x01\xd3\n\x01\x00\xa0\x82\x01\xcc0\x82\x01\xc8\x06\t+\x06\x01\x05\x05\x070\x01\x01\x04\x82\x01\xb90\x82\x01\xb50\x81\x9e\xa2\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x18\x0f20160914121000Z0s0q0I0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xcf&\xf5\x18\xfa\xc9~\x8f\x8c\xb3B\xe0\x1c/j\x10\x9e\x8e_\n\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x02\x10\x07z]\xc36#\x01\xf9\x89\xfeT\xf7\xf8o>d\x80\x00\x18\x0f20160914121000Z\xa0\x11\x18\x0f20160921112500Z0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x90\xef\xf9\x15U\x88\xac@l\xf6n\x04C/\x1a\xf5\xbc[Xi\xd9U\xbe\'\xd3\xb7\xf5\xbb\t\xd8\xb1Tw\x9c2\xac\x7f\x88\xba\x98\xe4\xa13\xf4\xdc\xea\xf3\xacX\xe4,E\xf5\xa9\xc3\xf4B-N\xe0\x89D[\xbe\n\xc2h\x9ar\xfd\'.\xc8,\xed\x83\xc2\xf0\x89_\x8c\xc3\xe7\x8a\xad\xa4\x14\x03\x96\x02\xc4\xa8\xc8\x90\x96%X\x80\x95\x02\x9d_\xc82;m\xe9\x15\x00\xa8\x00\xb9\x01\xe3aN&\xe4\xd5\x8a\xc4w7\x0b\xc3~\xc5\xb1M\x10~T\x9e\x1d\xf6\x06\xf8\x12sTg\x14b_\xe7\xc04\xb4\xa3\xd2\x8f\xe6\xa6\xc4\x01q\x03j\xc8\xd4\xc7\x89\xdde\x99\x1a\xd9\x02\xe7\x17\xd1\xf40P\xef\xf6$\xee\xfad\xf4\xeb\xc8\xf7\x0bRL\x8b\xa5x\xe4R2\xe9\xc2\xfcB\nh\x93\xf7\x0ep4h\xeb\x17\x83\xc8\x88!\xc3W\x94WG\xfe3\x15C0qE&A\x99\xa8}\x1a\xda"\xa9O\xba\x90W_W\xado\x1c\xf0`g7\xbb$\x91o\xec\xdd\xbd\x9e\x8bb\xfc\x16\x03\x03\x01M\x0c\x00\x01I\x03\x00\x17A\x04\xc3\x9d\x1cD\xcb\x85?dU\x9eg\xc9\x90\xd8\x80N|F\x98\x0cA\x07\xdfg\xa2\xfb_z\xe4\x9b\xf6\x06\xf3L\x82KJ8\x0e\x1a\x13\x97;:\x12\rdeu\xb5\x9f\x8d\xaa\xfc\x0f\xacb\x0e\xadVX\x19\x03u\x06\x01\x01\x00y\x8aQ\x11\x94\x91\x7f\xf7\xa3#o.\x11\x1d\xb3K\xede~0\xfb\xaf\x92\xfb\xfdY\x98n\x17$\xae\xf6\x16\x14\x13J;\x1cm7\xfa;\xc8G\xa6\x1a}{\xc2\xa5\x1b\xc5\x1c\xb5\x86\x18\x18Z\xa71\x86\x0b-\xa7/q\x89+\xc7$\xbb\xf2 \x17\xc8`\xbbt[j\x9f\x83\x88\xc0\x8d\xcf4fu1\xc3\xea:B\r\xc6\xc9\x12jP\x0c- \x17\x17t\x10\x17)e\xbe\xaao\xe5@\xd2\xcc\xa5\x89mRy\xfapc~\xa6\x84\x80\xbc4\xb4B\xcb\x92\x86\xad\xf6`9j\xf0\x8ee\xc0|\xfd\xdb\xde!\xceH\x0e\x9c\xfb\x85#\x9f\xb7\xccT\x96\xe0 \xfet-\xd8yUs\xe7m\x94\x07\xbc]~\x99\xd3\x93\xfb\\\xfc@B\x14w\xce\xe8n\x14\xd4\xcc\x07\xe5\xb5@j\x17IQ\xcfub\xcf\xa2\xde\xcaU\xb3 \x8b\xdb\x10Y\x0cS\xc7\x0b\xd8BP\xfeX!\x17\x94\x80\xedu\xf8M\xa7r\xc3\x04\xf4\xd6\xb7\x99\xd1=\x922\xf9\x0b\x9f\xe7\x1b\x932`15\xef\x16\x03\x03\x00\x04\x0e\x00\x00\x00' +p5_cke_ccs_fin = "\x16\x03\x03\x00F\x10\x00\x00BA\x04\xd2\x07\xce\xa9v\xd8\x1d\x18\x9bN\xe1\x83U\x8c\x8f\xd5a\x0f\xe5_\x9d\x0f\x8c\x9dT\xf6\xa9\x18'a\x8fHH@\x0c\xd4D\x801\x92\x07\xf3\x95\xa9W\x18\xfc\xb7J\xe6j\xbb\xac\x0f\x86\xae\n+\xd5\xb9\xdc\x86[\xe7\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00(\x00\x00\x00\x00\x00\x00\x00\x00\xd9\xcb,\x8cM\xfd\xbc9\xaa\x05\xf3\xd3\xf3Z\x8a-\xc7^\xc1\x8e\x81M\xff\x00\x0f}G\xf2\x8c\xab\n=" +p6_tick_ccs_fin = "\x16\x03\x03\x00\xca\x04\x00\x00\xc6\x00\x00\x04\xb0\x00\xc0c\xccwJ\x00\xdb,B.\x8fv#\xdd\xa9\xaeS\x90S \xb7(^\x0c\xed\n\xaeM\x0bN\xba\xb4\x8a4d\x85\x88 iN\xc9\xd1\xbe\xac\xe2Wb\xc9N\xf3\x85\xbf\xb7j\xa4IB\x8a\x1b\xe4\x8d\x1f\x148%\xd7R3\x0f4\rh\x8f\xccBj\xb5\r\xfa\xc1f\r?f\xc4\x0f_q9\xe1\x07B\x038\xb4}\xbb\xb0\xfc\x0eG\xf2\t&\x13\x98\xcb\xfc\xf6\xf4\xeb\x99!\t]\xe2\xd9-J\xe4\xdbK\xa1\xe5\xf0\t\xdfX\x0c\xb3\r\xf9\x18\xfb}\xd9\nhW1\xfc\x1c\x08DJ,\xa6#\xb0\x15\x16(&\xfdP\x8a%\xeb\xc2\xdd\xd8\xa2/\xbd$\xc3\x14\xfb\xf3\x86\xa3\xceO\x18\x9f\xfdS|'\x11\x02\xc8\xa6eW\xbdo*y\xf3.\xcf\x04\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00(\xd8m\x92\t5YZ:7\\)`\xaa`\x7ff\xcd\x10\xa9v\xa3*\x17\x1a\xecguD\xa8\x87$<7+\n\x94\x1e9\x96\xfa" +p7_data = "\x17\x03\x03\x01\xf6\x00\x00\x00\x00\x00\x00\x00\x01?\x04iy\x00\x04 \\\xd0\xd4\x9eG\x1f\xbf\xa3k\xfe=\xee\xce\x15\xa0%%\x06c}\xf6\xd4\xfb\xa6\xf0\xf6\x0cO\x1c\x9c\x91\xa9\x0b\x88J\xe0z\x94\xcaT\xeb\xc7\xad\x02j\x10\r\xc6\x12\xb9\xb9\x7f<\x84V\xab\x1e\xfc\xe5\x01\xda\xd6G\xf5\xb7\xf2I6\x8b\xc9\xc4a\xd3\x19\xeat\xfc\x9b\xfa\x1e\xe7\x8c\xaa\xb3\xce\xd0\x86G\x9b\x90\xf7\xde\xb1\x8bwM\x93\xa2gS>\xf3\x97\xf1CB\xfb\x8fs\x1e\xff\x83\xf9\x8b\xc0]\xbd\x80Mn3\xff\xa9\xf3)'\xc3S\xc8\xcd:\xbe\xd72B~$\xb2;\xeb+\xa4\xbd\xa9A\xd9 \n\x87\xe9\xe2\xe9\x82\x83M\x19Q\xf2n\x0e\x15\xdf\xb3;0\xdd&R\xb7\x15\x89\xe9O\xd8G7\x7f\xc3\xb8f\xc7\xd3\xc90R\x83\xf3\xd4\x1cd\xe8\xc5\x8d\xe4N(k7\xf0\xb7\xbd\x01\xb3\x9b\x86\xbaC.\x17\x8d\xd0g\xc9\xb1\x01\xfa\x01\xbe\xdbt\xb1u/\x19V\xc6\x08@\xff\xa8n\xe8\xd0\xd6n,\x05\xc9\xc2\xd8g\x19\x03.l\xb4)\xa09\xf9\xe7\x83\x01-\xe8\xf8\xffy\xbf\xf7\xe6\x11\xc5\xf5\x9aG\xb3e \xd85\x0f\x8f\x85H\xea\xc2n\x1eR\xbe\x01\xef\xef\x93\xe7*>\xbd\x84\x8b9HDI\x90\xc4$\x9a\x9aK\x88Ki\n\xa3\xab\xed\x91\xcd\xe8\xb1\xd4\x8e\xbcE\x88\xe8\x05\x16\xd5\xed\x18\x16g>\x04\xd8\x1dB}\x91\x90\xd1\xda\x03\xe1\x972CxtD\x85\xafF|~7D9*U\xad\x0b\xc4#\x06}\xec\xd6\xd3?y\x96\xa4\xb5\xa3\x1d\x1c\xbd\xc9\xc9g\xb12\xc9\x0f\xa1\x03\x12N\x0b\xec\x14\xc9vJ\nM\xa7\xc8h\xd0|(1(\xa3\x98@nH\n\x0b\xa80\x00\x02\xb7\x06Z\xd4M\xdc!AV\xe2\xa7*\xc3\x90U\xee\xd0\xb2\x05\xa3w\xe1\xe2\xbe\x1e\xbe\xd4u\xb1\xa1z\x1e\x1c\x15%7\xdd\xf9\xb9~\x02\xf9s\x0c1\xfb;\xab\xf1\x1e\xaf\x06\x8c\xafe\x00\x15e5\xac\xd7]>\x1dLb5\x8e+\x01n\xcb\x19\xcc\x17Ey\xc8" + + ++ Test TLS += Reading test session - TLS parsing (no encryption) does not throw any error +# We will need to distinguish between connection ends. See next XXX below. +t1 = TLS(p1_ch) +t2 = TLS(p2_sh, tls_session=t1.tls_session) +t3 = TLS(p3_cert, tls_session=t1.tls_session) +t4 = TLS(p4_certstat_ske_shd, tls_session=t1.tls_session) + + ++ Test TLS Record header += Reading test session - TLS Record header +# We leave the possibility for some attributes to be either '' or None. +assert(t1.type == 0x16) +assert(t1.version == 0x0301) +assert(t1.len == 213) +assert(len(t1.msg) == 1) +assert(not t1.iv) +assert(not t1.mac) +assert(not t1.pad and not t1.padlen) +len(t1.msg) == 1 + + ++ Test TLS Record __getitem__ += Reading test session - TLS Record __getitem__ +TLSClientHello in t1 + ++ Test TLS ClientHello += Reading test session - ClientHello +ch = t1.msg[0] +assert(isinstance(ch, TLSClientHello)) +assert(ch.msgtype == 1) +assert(ch.msglen == 209) +assert(ch.version == 0x0303) +assert(ch.gmt_unix_time == 0x17f24dc3) +assert(ch.random_bytes == '|\x19\xdb\xc3<\xb5J\x0b\x8d5\x81\xc5\xce\t 2\x08\xd8\xec\xd1\xf8"B\x9cW\xd0\x16v') +assert(ch.sidlen == 0) +assert(not ch.sid) +assert(ch.cipherslen == 22) +assert(ch.ciphers == [49195, 49199, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10]) +assert(ch.complen == 1) +assert(ch.comp == [0]) + + ++ Test TLS ClientHello Extensions += Reading test session - ClientHello extensions +assert(ch.extlen == 146) +ext = ch.ext +assert(len(ext) == 9) +assert(isinstance(ext[0], TLS_Ext_ServerName)) +assert(ext[0].type == 0) +assert(ext[0].len == 31) +assert(ext[0].servernameslen == 29) +assert(len(ext[0].servernames) == 1) +assert(ext[0].servernames[0].nametype == 0) +assert(ext[0].servernames[0].namelen == 26) +assert(ext[0].servernames[0].servername == "camo.githubusercontent.com") +assert(isinstance(ext[1], TLS_Ext_RenegotiationInfo)) +assert(not ext[1].renegotiated_connection) +assert(isinstance(ext[2], TLS_Ext_SupportedEllipticCurves)) +assert(ext[2].ecl == [0x17, 0x18, 0x19]) +assert(isinstance(ext[3], TLS_Ext_SupportedPointFormat)) +assert(ext[3].ecpl == [0]) +assert(isinstance(ext[4], TLS_Ext_SessionTicket)) +assert(not ext[4].ticket) +assert(isinstance(ext[5], TLS_Ext_NPN)) +assert(ext[5].protocols == []) +assert(isinstance(ext[6], TLS_Ext_ALPN)) +assert(len(ext[6].protocols) == 6) +assert(ext[6].protocols[-1].protocol == "http/1.1") +assert(isinstance(ext[7], TLS_Ext_CSR)) +assert(isinstance(ext[7].req[0], OCSPStatusRequest)) +assert(isinstance(ext[8], TLS_Ext_SignatureAlgorithms)) +assert(len(ext[8].sig_algs) == 10) +ext[8].sig_algs[-1] == 0x0202 + + ++ Test TLS ServerHello += Reading test session - ServerHello +assert(TLSServerHello in t2) +sh = t2.msg[0] +assert(isinstance(sh, TLSServerHello)) +assert(sh.gmt_unix_time == 0x46076ee2) +assert(sh.random_bytes == '\x0c\x97g\xb7o\xb6\x9b\x14\x19\xbd\xdd1\x80@\xaaQ+\xc2,\x19\x15"\x82\xe8\xc5,\xe8\x12') +assert(sh.cipher == 0xc02f) +assert(len(sh.ext) == 6) +sh.ext[-1].protocols[-1].protocol == "http/1.1" + + ++ Test TLS Certificate += Reading test session - Certificate +cert = t3.msg[0] +assert(cert.certslen == 2670) +assert(len(cert.certs) == 2) +srv_cert = cert.certs[0][1] +assert(isinstance(srv_cert, Cert)) +assert(srv_cert.serial == 0x077a5dc3362301f989fe54f7f86f3e64) +srv_cert.subject['commonName'] == 'www.github.com' + + ++ Test TLS CertStat-SKE-SHD += Reading test session - Multiple TLS layers +cert_stat = t4.msg[0] +ske = t4.payload.msg[0] +shd = t4.payload.payload.msg[0] +isinstance(t4.payload.payload.payload, NoPayload) + + ++ Test TLS CertificateStatus += Reading test session - CertificateStatus +assert(isinstance(cert_stat, TLSCertificateStatus)) +assert(cert_stat.responselen == 471) +cert_stat.response[0].responseStatus == 0 +# we leave the remaining OCSP tests to x509.uts + + ++ Test TLS ServerKeyExchange += Reading test session - ServerKeyExchange +assert(isinstance(ske, TLSServerKeyExchange)) +p = ske.params +assert(isinstance(p, ServerECDHNamedCurveParams)) +assert(p.named_curve == 0x0017) +assert(p.point[0] == '\x04' and p.point[1:5] == '\xc3\x9d\x1cD' and p.point[-4:] == 'X\x19\x03u') +assert(ske.sig.sig_alg == 0x0601) +ske.sig.sig_val[:4] == 'y\x8aQ\x11' and ske.sig.sig_val[-4:] == '`15\xef' + + ++ Test TLS ServerHelloDone += Reading test session - ServerHelloDone +assert(isinstance(shd, TLSServerHelloDone)) +shd.msglen == 0 + ++ Test tls_session += Reading test session - Context checks after 1st RTT +t = shd.tls_session +assert(len(t.handshake_messages) == 6) +assert(t.handshake_messages_parsed[-1] is shd) +assert(t.tls_version == 0x0303) +assert(t.client_kx_ffdh_params is None) +assert(t.client_kx_ecdh_params is not None) +pn = t.server_kx_pubkey.public_numbers() +x = pkcs_i2osp(pn.x, pn.curve.key_size/8) +y = pkcs_i2osp(pn.y, pn.curve.key_size/8) +assert(x[:4] == '\xc3\x9d\x1cD' and y[-4:] == 'X\x19\x03u') +assert(t.rcs.row == "read") +assert(t.wcs.row == "write") +t.rcs.ciphersuite.val == 0 + + ++ Test TLS += Reading test session - TLS parsing (with encryption) does not throw any error +# XXX Something should be done, as for instance the reading of the 1st CCS +# will mess up the reading state of the other side (even before the 2nd CCS). +t5 = TLS(p5_cke_ccs_fin, tls_session=t1.tls_session) + + ++ Test TLS ClientKeyExchange += Reading test session - ClientKeyExchange +cke = t5.msg[0] +ccs = t5.payload.msg[0] +rec_fin = t5.payload.payload +fin = t5.payload.payload.msg[0] +isinstance(t5.payload.payload.payload, NoPayload) +assert(isinstance(cke, TLSClientKeyExchange)) +k = cke.exchkeys +assert(isinstance(k, ClientECDiffieHellmanPublic)) +assert(k.ecdh_Yclen == 65) +assert(k.ecdh_Yc[:4] == '\x04\xd2\x07\xce' and k.ecdh_Yc[-4:] == '\xdc\x86[\xe7') + + ++ Test TLS ChangeCipherSpec += Reading test session - ChangeCipherSpec +assert(isinstance(ccs, TLSChangeCipherSpec)) +ccs.msgtype == 1 + + ++ Test TLS Finished += Reading test session - Finished +assert(rec_fin.version == 0x0303) +assert(rec_fin.len == 16) +assert(rec_fin.iv == '\x00\x00\x00\x00\x00\x00\x00\x00') +assert(rec_fin.mac == '\xc7^\xc1\x8e\x81M\xff\x00\x0f}G\xf2\x8c\xab\n=') +assert(not rec_fin.pad and not rec_fin.padlen) +from scapy.layers.tls.record import _TLSEncryptedContent +assert(isinstance(fin, _TLSEncryptedContent)) +fin.load == '\xd9\xcb,\x8cM\xfd\xbc9\xaa\x05\xf3\xd3\xf3Z\x8a-' + + ++ Test TLS Tick-CCS-Fin += Reading test session - Ticket, CCS & Finished +t6 = TLS(p6_tick_ccs_fin) # empty tls_session +tick = t6.msg[0] +assert(isinstance(tick, TLSNewSessionTicket)) +assert(tick.msgtype == 4) +assert(tick.lifetime == 1200) +assert(tick.ticketlen == 192) +assert(tick.ticket[:4] == 'c\xccwJ' and tick.ticket[-4:] == '\xf3.\xcf\x04') +ccs = t6.payload.msg[0] +assert(isinstance(ccs, TLSChangeCipherSpec)) +rec_fin = TLS(str(t6.payload.payload), tls_session=t1.tls_session) +assert(rec_fin.iv == '\xd8m\x92\t5YZ:') +assert(rec_fin.mac == '\xecguD\xa8\x87$<7+\n\x94\x1e9\x96\xfa') +assert(isinstance(rec_fin.msg[0], _TLSEncryptedContent)) +rec_fin.msg[0].load == '7\\)`\xaa`\x7ff\xcd\x10\xa9v\xa3*\x17\x1a' + + ++ Test TLS ApplicationData += Reading test session - ApplicationData +t7 = TLS(p7_data, tls_session=t1.tls_session) +assert(t7.iv == '\x00\x00\x00\x00\x00\x00\x00\x01') +assert(t7.mac == '>\x1dLb5\x8e+\x01n\xcb\x19\xcc\x17Ey\xc8') +assert(not t7.pad and not t7.padlen) +assert(isinstance(t7.msg[0], _TLSEncryptedContent)) +len(t7.msg[0].load) == 478 + + +############################################################################### +############################## Building packets ############################### +############################################################################### + ++ Test Build TLS empty records += Building packets - Various default records +str(TLS()) +str(TLSClientHello()) +str(TLSServerHello()) +str(TLSCertificate()) +str(TLSServerKeyExchange()) +str(TLSClientKeyExchange()) +str(TLSAlert()) +str(TLSChangeCipherSpec()) +str(TLSApplicationData()) == '' + + ++ Test Build TLS ClientHello += Building packets - ClientHello with automatic length computation +ch = TLSClientHello() +ch.msgtype = 'client_hello' +ch.version = 'TLS 1.2' +ch.gmt_unix_time = 0x26ee2ddd +ch.random_bytes = 'X\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf' +ch.ciphers = [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA] +ch.comp = 'null' +ext1 = TLS_Ext_ServerName(servernames=ServerName(servername='mn.scapy.wtv')) +ext2 = TLS_Ext_RenegotiationInfo() +ext3 = TLS_Ext_SupportedEllipticCurves(ecl=['secp256r1', 'secp384r1', 'secp521r1']) +ext4 = TLS_Ext_SupportedPointFormat(ecpl='uncompressed') +ext5 = TLS_Ext_SessionTicket() +ext6 = TLS_Ext_NPN() +ext7 = TLS_Ext_ALPN(protocols=[ProtocolName(protocol='h2-16'), ProtocolName(protocol='h2-15'), ProtocolName(protocol='h2-14'), ProtocolName(protocol='h2'), ProtocolName(protocol='spdy/3.1'), ProtocolName(protocol='http/1.1')]) +ext8 = TLS_Ext_CSR(stype='ocsp', req=OCSPStatusRequest()) +ext9 = TLS_Ext_SignatureAlgorithms(sig_algs=['sha256+rsa', 'sha384+rsa', 'sha512+rsa', 'sha1+rsa', 'sha256+ecdsa', 'sha384+ecdsa', 'sha512+ecdsa', 'sha1+ecdsa', 'sha256+dsa', 'sha1+dsa']) +ch.ext = [ext1, ext2, ext3, ext4, ext5, ext6, ext7, ext8, ext9] +t = TLS(type='handshake', version='TLS 1.0', msg=ch) +str(t) == '\x16\x03\x01\x00\xc7\x01\x00\x00\xc3\x03\x03&\xee-\xddX\xe1\xb1T\xaa\xb1\x0b\xa0zlg\xf8\xd14]%\xa9\x91d\x08\xc7t\xcd6\xd4"\x9f\xcf\x00\x00\x16\xc0+\xc0/\xc0\n\xc0\t\xc0\x13\xc0\x14\x003\x009\x00/\x005\x00\n\x01\x00\x00\x84\x00\x00\x00\x11\x00\x0f\x00\x00\x0cmn.scapy.wtv\xff\x01\x00\x01\x00\x00\n\x00\x08\x00\x06\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00#\x00\x003t\x00\x00\x00\x10\x00)\x00\'\x05h2-16\x05h2-15\x05h2-14\x02h2\x08spdy/3.1\x08http/1.1\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\r\x00\x16\x00\x14\x04\x01\x05\x01\x06\x01\x02\x01\x04\x03\x05\x03\x06\x03\x02\x03\x04\x02\x02\x02' + + ++ Test Build TLS ServerKeyExchange += Building packets - ServerHello context linking +from scapy.layers.tls.crypto.kx_algs import KX_ECDHE_RSA +from scapy.layers.tls.crypto.cipher_block import Cipher_AES_256_CBC +sh = TLSServerHello(gmt_unix_time=0x41414141, random_bytes='B'*28, cipher='TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA') +t = TLS(msg=sh) +str(t) +assert(isinstance(t.tls_session.pwcs.ciphersuite, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)) +assert(isinstance(t.tls_session.pwcs.key_exchange, KX_ECDHE_RSA)) +assert(isinstance(t.tls_session.pwcs.cipher, Cipher_AES_256_CBC)) +assert(isinstance(t.tls_session.pwcs.hmac, Hmac_SHA)) +t.tls_session.server_random == 'A'*4+'B'*28 + + ++ Test Build TLS wrong record += Building packets - ChangeCipherSpec with forged, forbidden field values +t = TLS(msg=TLSChangeCipherSpec()) +assert(str(t) == '\x14\x03\x03\x00\x01\x01') +t.len = 0 +assert(str(t) == '\x14\x03\x03\x00\x00\x01') +t.type = 0xde +t.version = 0xadbe +t.len = 0xefff +str(t) == '\xde\xad\xbe\xef\xff\x01' + + +############################################################################### +############################ Automaton behaviour ############################## +############################################################################### + +# see test/run_openssl_tests + + diff --git a/test/tls/__init__.py b/test/tls/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1b5e2a931c9dd57492caac736d151419500654d4 --- /dev/null +++ b/test/tls/__init__.py @@ -0,0 +1,8 @@ +## This file is part of Scapy +## Copyright (C) 2016 Maxence Tury <maxence.tury@ssi.gouv.fr> +## This program is published under a GPLv2 license + +""" +Examples and test PKI for the TLS module. +""" + diff --git a/test/tls/example_client.py b/test/tls/example_client.py new file mode 100755 index 0000000000000000000000000000000000000000..3c5f9794c6b5f7183061e150d1e8725f1a3a165f --- /dev/null +++ b/test/tls/example_client.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +## This file is part of Scapy +## This program is published under a GPLv2 license + +""" +Basic TLS client. A ciphersuite may be commanded via a first argument. + +For instance, "sudo ./client_simple.py c014" will try to connect to any TLS +server at 127.0.0.1:4433, with suite TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA. +""" + +import os +import sys + +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) +sys.path=[basedir]+sys.path + +from scapy.layers.tls.automaton import TLSClientAutomaton +from scapy.layers.tls.handshake import TLSClientHello + + +if len(sys.argv) == 2: + ch = TLSClientHello(ciphers=int(sys.argv[1], 16)) +else: + ch = None + +t = TLSClientAutomaton(client_hello=ch) +t.run() + + diff --git a/test/tls/example_server.py b/test/tls/example_server.py new file mode 100755 index 0000000000000000000000000000000000000000..7d4d9664c1ba49386032cca6e47f1a1a6c38cfae --- /dev/null +++ b/test/tls/example_server.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +## This file is part of Scapy +## This program is published under a GPLv2 license + +""" +Basic TLS server. A preferred ciphersuite may be provided as first argument. + +For instance, "sudo ./server_simple.py c014" will start a server accepting +any TLS client connection. If provided, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA +will be preferred to any other suite the client might propose. +""" + +import os +import sys + +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) +sys.path=[basedir]+sys.path + +from scapy.layers.tls.automaton import TLSServerAutomaton + + +if len(sys.argv) == 2: + pcs = int(sys.argv[1], 16) +else: + pcs = None + +t = TLSServerAutomaton(mycert=basedir+'/test/tls/pki/srv_cert.pem', + mykey=basedir+'/test/tls/pki/srv_key.pem', + preferred_ciphersuite=pcs) +t.run() + + diff --git a/test/tls/pki/ca_cert.pem b/test/tls/pki/ca_cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..25b4c52bfbfd9f307ed00668a30c4601d4aa54a7 --- /dev/null +++ b/test/tls/pki/ca_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDiDCCAnCgAwIBAgIJALpa+1bjqpmeMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz +dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAyNjQ1WhcN +MjYwOTE2MTAyNjQ1WjBUMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 +YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRYwFAYDVQQDDA1TY2FweSBUZXN0 +IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gy5LahBQAwiGUrP +ZLrBUIEZAlnsOkCd7Al+wJwaPFhMLy+nga0ubMuUBo4P9DQv729jpiRnu6Q1ScO3 +PcRX5FNdEKxV9fLOWUJp5MIMz5k6snJ5+kEMouNXj/umUN+qHHyvgbDVEw7RroTN +mLqnWs2Al5Rd0NAxp4lLoYdVUclXrlOGY7Ldkq4WAgdlJZQ6PiZyeoz6YNeoRNmR +h4RGKDmzoSEKqsAUlozEg3seC1EbU0TtY9r3O09tEGegQUARIxXV3qpWjFxakax+ +XzzldwuoIWMO7x8RqH9X3Y+ktcLOxiPhKmGqcR3kNyMcARatdIjdV0b3jAeH68of +DVxXoQIDAQABo10wWzAdBgNVHQ4EFgQUZlOU9BXRvWdosFE3MjXhpKreB3wwHwYD +VR0jBBgwFoAUZlOU9BXRvWdosFE3MjXhpKreB3wwDAYDVR0TBAUwAwEB/zALBgNV +HQ8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAJNnhilPvSXjGFSUNp5XG81i44lI +wqsYcWl7cuNjFS8tqciMb1Q8Lr768+CPFYlf3OjwX43SCe621oKtRZV0O3bizSZd +5xuCAEsCe1jkk4d7Nxk13/AB2z6YKvWeud/vLAQpYIwzV/qExAOv+ZLAj46t6S/E +h/A/kNEXqBE5e+yysTUVNz+moI7P8Sw91yXuiPMSWJ4rla+nmfFWaKTP9vjEmEHt +a+LA8VUiR2dEeLcRnVCgVJc0+AS6EjG662AKyNYP4AcmUaFvBKRiJpEgZwNmmOen +PjNAbNxzEk7bJMG8GUmwYJ9cYznBFBOzAJNyMkG8wSmMYN3NgdnD4KYHYVE= +-----END CERTIFICATE----- diff --git a/test/tls/pki/ca_key.pem b/test/tls/pki/ca_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..fead823a80a3b737292d5f991bfbd22349ad4c1f --- /dev/null +++ b/test/tls/pki/ca_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQbLktqEFADCIZ +Ss9kusFQgRkCWew6QJ3sCX7AnBo8WEwvL6eBrS5sy5QGjg/0NC/vb2OmJGe7pDVJ +w7c9xFfkU10QrFX18s5ZQmnkwgzPmTqycnn6QQyi41eP+6ZQ36ocfK+BsNUTDtGu +hM2YuqdazYCXlF3Q0DGniUuhh1VRyVeuU4Zjst2SrhYCB2UllDo+JnJ6jPpg16hE +2ZGHhEYoObOhIQqqwBSWjMSDex4LURtTRO1j2vc7T20QZ6BBQBEjFdXeqlaMXFqR +rH5fPOV3C6ghYw7vHxGof1fdj6S1ws7GI+EqYapxHeQ3IxwBFq10iN1XRveMB4fr +yh8NXFehAgMBAAECggEAXSPpEO0604tYhaL30VTf5MD8Ux+qQFH2ALAxk5Nu6f6v +dPq/yWSB9Z54NQGxQXk83qwRhQKJ1MHKCn/K2HBwsplKYpQRCgsKibrzJYZOQUuB +fpNHzTzaj8Q2siJMLaH2HCrgJ33FinG55Fp2okTvWtWxHIvx7MnNFsh1IuceiqA3 +5tVSR7+kMraJgJ9auRNRQbH273LzntVOzAt339Xd38izsQ7CvYERAQGabTwo6BEC +nDFfhWXPaxRt5JcJWcJNYSxYKigMmDh4oGXSb+mZeuojcXYcaHzyAToAjZ6x2hXU +Vjxo7JMK+gvhJvlOpY2tjMNIkRh1P7gLrBSP8XcxjQKBgQDtSIr8qooaswEQ9H8W +Tkr5ciLU4u6Wfhp26+M6oHPv57bbJI7qN68Z3l+cbtLxSqj0bcop1V+xvQjWOD+/ +ckNPm4aEmoIMP3a0oQARtJeR9uaos64uudc+1gpz9g7w+sO+khy9yAFdY1+9gsEi +WqqbtVQ2e3RABoolr14Pcc222wKBgQDg3XAjcXupfsrDhQP5R45kY/eVFWUk7PMB +jpPl6+ZfoB/vqUuCZBX3UYrFDe4O+n0R6oF9ACXLRsVuaG/IZYDFczsBvEr8WpOG +78lpTbgUwudKlnzSBl01aUZu7zpJ3oFhX1DgZspr72UfHl9+/NwezV4gFd0ZKGLO +s/aLsh3eMwKBgDm3TH9a6A7IfbjnD8aYMqpsNca8kDYw5DUK+ZF4F9tB7HtvcAfO +lZvgODdvyYWBmIkj72mvigBMr8qTkgX6QB8sAFNe1cUu5qvXAZJM8BVEDiT416Rr +9cxF+fLs5gN9q4E+Pxl2fcZ+dno9RMcbcKZBPAOokcVFEfNKrcFp+BTDAoGBAL42 +0ztILgFs/fxysq/V9f+6CJ8WIB8iSVXR1A40hQXzH9DN9s/v9hzl32tdozkMb2wO +YUbqLw5LaYtB0P1Fz643EX0gWJYr0IvenxPy6Hq3fIu9zQyk0Yfy69+/giEmlW9W +/8UzbpvrQDEYslNrdpCfzLV7iTJU1XBhD3eQTm+9AoGAf1EX07Jk7357Y7+l/vB4 +Cd0g+k4yRadDM7tCrdYnZcdwhFoKtCC9y/ChBgHRkayMizJRYC218ou9muP3qYLD +Wd+Sqtxg7LlDRa+m8db0SI40a0AMb0fGKXzy4AxD1EJrBLt8rdaDrqoGF/Vwadjh +sxUMjCPGDmiV+ucqbJR9/NE= +-----END PRIVATE KEY----- diff --git a/test/tls/pki/cli_cert.pem b/test/tls/pki/cli_cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..66f48c2bf01c2d58ebe15ffb65d07dfc9c1da1b7 --- /dev/null +++ b/test/tls/pki/cli_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnjCCAoagAwIBAgIJAP4EVw3HJ+n4MA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz +dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAzMDUyWhcN +MjYwOTE1MTAzMDUyWjBYMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 +YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRowGAYDVQQDDBFTY2FweSBUZXN0 +IENsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMJN5ioDSgj +OufUeWJI7Ali5QPySoD8neeSXkzGlm48SGDNzoKQs0v75gmlOs/mditKsPfu9HQA +Ohd97oor8HQLhEMqF6OpiGyjOo7i3+/X8bPhJJsn6pYmJ5PH8HjduHuGFGt9Or2t +wpQCd1t5ZN3KSZpnEk9K3HS3GJHHsO69UvuIWsksjjetAtD7HpvdeMGrMRmTEgI3 +EFVUigfP1y8uaoq648TPG31MFx8cpxfKhNtstmRPZNpyl2NpzoZQFXskbMO8pVYc +QxTwA6/AgDBYoaYRdV4hq5MSfcGloy5OIsR/8toamJ0EjbypulnC0+F78goP/Puw +isuHi2YYJxUCAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0O +BBYEFAPVvKSOtt3Hj63JN8ZtzEGSZZ5IMB8GA1UdIwQYMBaAFGZTlPQV0b1naLBR +NzI14aSq3gd8MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IB +AQAhzb8LBydZ+ulSyIiF4/PgTh7/sQOC1LroP0GhVsOLNZ07qvw8teXJO9HwsAqm +14GeLJh5XWhoL4tkq9RMcwuZL2vuVl9FB9inLerA1FU/ErJJOIIC3drIORTTQ2ot +lUkbKOAQFzZfP0d+iuCFi0r0Kcu7BAZETTeG4cAoIoIIhh2AZ8DfT3E6xP7OKUMA +3m1gA4M1hwiAhFvj4iBaG20Sb0RXpDuTToHEfCEnuQdoex3F8gmn88yt22FNakqe +cr9ooif+id4ErdKLozgG1i0PFYCFRj2/fUPTQw3BSgfo+XNcAjA04CowuhPAjsL8 +ybC+9OE5YPSxfOqOPB4sK/w2 +-----END CERTIFICATE----- diff --git a/test/tls/pki/cli_key.pem b/test/tls/pki/cli_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..3092d55bffe2cb63df6ed5c9dc11f8bdc761101c --- /dev/null +++ b/test/tls/pki/cli_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDCTeYqA0oIzrn +1HliSOwJYuUD8kqA/J3nkl5MxpZuPEhgzc6CkLNL++YJpTrP5nYrSrD37vR0ADoX +fe6KK/B0C4RDKhejqYhsozqO4t/v1/Gz4SSbJ+qWJieTx/B43bh7hhRrfTq9rcKU +AndbeWTdykmaZxJPStx0txiRx7DuvVL7iFrJLI43rQLQ+x6b3XjBqzEZkxICNxBV +VIoHz9cvLmqKuuPEzxt9TBcfHKcXyoTbbLZkT2Tacpdjac6GUBV7JGzDvKVWHEMU +8AOvwIAwWKGmEXVeIauTEn3BpaMuTiLEf/LaGpidBI28qbpZwtPhe/IKD/z7sIrL +h4tmGCcVAgMBAAECggEBAJU951ocNj0hgEDH+L55uIySLVawv8wmAhqiiSBM0e22 +mVfiBIUqftjE/8kfs3pFCuWjuPlv04U0az9wsOjwKIZUDbhrbD1jTC59VSDjgKKC +ZsTTonRLvhl5Rs2xsFR8rV9wQQ3jfOCKJxulK3pG0SVaVqoc9wjP6xQwy086NCzq +QLnM2BESEED9cfbO7zVE7bJzLmAH9v4eF5hpuQ3SQZGE6oib0uPPnAyl5iOZZHnu +4DTxVHYIYKCegL47VpeL3h80Yzu8bAuqoSsLxbBcAoYB9e9ZMVi8o1NaVM9eT0/m +HSb+rkN39YrrtXDkXxqmwsh8B7R9gRBxMFIzsI83XN0CgYEA+KSoQ3zpEjjFioB+ +006JcATCslLERgDuuaVqVlnt5a983i/3nwvICxfsGrFaQJ4TkhXEYuZro4Zovdc1 +9IAj8DEQRNh7H7mZoWtQnFC32VOdAUVZVpopL6009xKs/II9pRKg4Vm6mpb47xfl +Yk/upy5yV4gHThkMLccJ8PqmIosCgYEAyM6Da1vflkzoKMu7HmJNmi59hnk5N15V +C8BcYNaodiHucrcyzOexaVnIQJ7+CgVen+RF9wwcIQxUsw8Vcs0AZ4ipLDOFZXNk +k0oGjr5oeCIHJvA9W1BSXmcJ5Beo4ASr0GmIQpfsFohFuHXn7ezQWqA6Me8HwFGF +l8goN3VyMN8CgYBJ5u7YOE0yDEuykeSgO6yf7dpMlEsgH3DVHvRPPCV4akNr6sfn +ruHDYlXbzTDtGc7pUazwVFpT3UROgKPZyyhjYMHcJJfb4xdlofbwrxEl+DMnSIx4 +MBPjxtCCSzu9RZy67qGAuWG8RvkwX2LfaLCfYi+8EoNRVCKJjKpIxMcSZwKBgC9E +xZTJDKmxstiflI2DcGcB2JSGBpzs/LIGdvhor0EXnaytSS0IwS9ebhAgHQa42txi +fMG5vQlegLWhsFfUv+qfNcts2VLXRe6R91c0pRzaTbqxxI+xKaKFOMPTefI5x0QJ +A4VBg9aN/3N7dbwBCc67dtd4P+faiMsA186uO9IbAoGAVvh9bC5zoCfenexRe9si +c/baEsOlPjMnM/QKz6Ue/65YTzpJYQ1hVlGqP2DsN28f5yJetHLcUINaPxlegKdL +Ifhmdg5HO7v35NHVghXq+/M8xYRzKD6nqRqB8wEdGb+XOo5QHXsLp3wBQ7QXL4UI +mrJFWi1wtyQOdIO4GJC8Bc0= +-----END PRIVATE KEY----- diff --git a/test/tls/pki/srv_cert.pem b/test/tls/pki/srv_cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..e559f326c9002c52b00263b16acc4bafb72dd194 --- /dev/null +++ b/test/tls/pki/srv_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnjCCAoagAwIBAgIJAP4EVw3HJ+n2MA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAk1OMRQwEgYDVQQHDAtVbGFhbmJhYXRhcjEXMBUGA1UECwwOU2NhcHkgVGVz +dCBQS0kxFjAUBgNVBAMMDVNjYXB5IFRlc3QgQ0EwHhcNMTYwOTE2MTAyODExWhcN +MjYwOTE1MTAyODExWjBYMQswCQYDVQQGEwJNTjEUMBIGA1UEBwwLVWxhYW5iYWF0 +YXIxFzAVBgNVBAsMDlNjYXB5IFRlc3QgUEtJMRowGAYDVQQDDBFTY2FweSBUZXN0 +IFNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzx8ZtgLWCu +8pgNJynZwAlZTA9KMKhS3+WxIZ9Pwz1Wk91fxvez9lWL55Li3vKFSbShLPT9dqhn +ygQgYBEYpvKptqYd2arl2duv5q9VV5//Uoll5oBigCGUvM+BG8tnwp21BXcEpseI +GIB4aJU23pcbtmGHQhp1mEWC6z4yEcibhkI5jU0St1gbGfOdK6GYgsrXOyT7CTmw +vMKVz4IpdRYpP0IgFytNQIxWbK26DzSFsX9AeXF4t6UEu5T3tUGV7nzrjQx5aFnv +y7P6Pnge7mdMet3gme/a5++yCV2+gCAhBYMsRNtdKnYppbAjiHQHVCLWKXqS9W8t +nuf4JiucWGUCAwEAAaNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0O +BBYEFKErIHDSa4DlZbzrAw+In3St3fYTMB8GA1UdIwQYMBaAFGZTlPQV0b1naLBR +NzI14aSq3gd8MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IB +AQCBiJJza5PnldbdQe6OHr2jSFinQTU/e33QN5gOuCyUd8hRNkCtWQkoyFbW6lus +tNg/aLdmyuFWN6kAZepRyeyyaUld+ePA7WFUyRKfxrAKc1XoVTVg7xw28NrRkHdW +BLirOO73CcWlmJAj6h/bFX8yKIGrm4UCS5XnN1F7G0gu+z5Sow20RqmSOhwf1woe +WEr6LlGPKcYeuA4xDnPxJ4gXyshpDPqDzbN5DhSwuJsvOi0J4/wG8Dpu/TY7KxoJ +KuirX4xA5IGyvPeDZxFuTpPqIq//o5p3V3bQCzis+IqUNY7X1GHMAf8ktI9hI7qI +11nk6boqTrUVD5zQ6gaR2d6r +-----END CERTIFICATE----- diff --git a/test/tls/pki/srv_key.pem b/test/tls/pki/srv_key.pem new file mode 100644 index 0000000000000000000000000000000000000000..62248e3e46310d661e486f585d706630308f7df1 --- /dev/null +++ b/test/tls/pki/srv_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM8fGbYC1grvKY +DScp2cAJWUwPSjCoUt/lsSGfT8M9VpPdX8b3s/ZVi+eS4t7yhUm0oSz0/XaoZ8oE +IGARGKbyqbamHdmq5dnbr+avVVef/1KJZeaAYoAhlLzPgRvLZ8KdtQV3BKbHiBiA +eGiVNt6XG7Zhh0IadZhFgus+MhHIm4ZCOY1NErdYGxnznSuhmILK1zsk+wk5sLzC +lc+CKXUWKT9CIBcrTUCMVmytug80hbF/QHlxeLelBLuU97VBle58640MeWhZ78uz ++j54Hu5nTHrd4Jnv2ufvsgldvoAgIQWDLETbXSp2KaWwI4h0B1Qi1il6kvVvLZ7n ++CYrnFhlAgMBAAECggEAIPA9uZAimuhjOwbaJYLGt3nvnIF7AoKXU449biJeqawR +hcHP852r2KHsrRHjbSz45JwG4rUd7gEIWdNuPTEuG9Ak99vSUQIyGnnR5JodxCw/ +8q869aVfHIaQNfV1JyLdB4XBhBhuSaFY9sTjYh/4dGbS0Cfx+titiXZ6InvfmdMD +eLd/ZO35/BwtWN3J2ntRziTTREKLeEYFEe7FtXKGwDGIsvVn7egckefKMnflhMFA +SuoPn2VvTqmhiwSuATdx1TP4XOVdVzuL2wT7brS7qHvabRDBKdVOfrNGOoMdnnua +ursIQjQindNT8kVK8EGxws9eFr/dooYYFR72IusTfQKBgQDuQBzzKEtt86uRCbZX +Y3lu0MJnR5OOodfGBBYF9Ue+W3OJTd9EvXLSgLBLvwirB7lsNXgXf8LHCTQOtF3H +lnB8jE5OFSDGeSKWmUwZS+KVzq8vy7Qylp9i6x4pElwGUeba6AqeZZ+jUUn/HzdB +s2pO8YWqyOp/Zo/m8P+vPZN4fwKBgQDcNqJ4Dutt/i60E9kaktGQVQODRNMhYCEq +E5fhwJiZ0E9FBeuKPKjo7dGIux3KPQPBv3T0zjmB+M5QERVe5/ID8iytgbHGlnsg +916iTN9rvi1Gk518vyFPsYjX9pPiQIayRBQKOXSYIkY+6rj2384XPRlZrN8D9n3Q ++An1JXfdGwKBgDs3YjqpnD3i35S4BkMoLUl2x6rl5m4AGeJUp6ipc0CD+G57FXA/ +aieZ5rec7qmbzOFxVLz6e03/Ipo5CEoQQTsjoF7V74SFHSyzQ2/SJao4aeCGT+52 +83yhlah9sLO9bZShMep2tbvg+3RWrOQ+lMC0VRXCxE4QDtpGsjY7Jsk/AoGAPstV +iOa4O6U/rBn8zpcPKxkS51u42MuQqW7s4HMLENFVyVjm0YR6pfEqztKMrB6584Wk +1Cn6PBW2vx4f+fAqEvX7x340M2y1r7DaS22gSBjy0C1Hu0rFNPRrESo/AUVlI3BG +RqQbm0YqwcYs+DjZi8bgc7HX5kljlzMjo8QLagECgYA1DHAWv4WVrHt4I8V4ZCth +9DZEtEOFpYjuoFh/xSIMZLsnvWRuyYVWcQwAqmK0Ew4m5opHFsQzABeGLVsK5aHX +zmbYiRUuZGVpyc7c5XXomw50X8ajfQ+P21OPPc33h96cdHi2qbJIejZPia6A6ThU +u13D93hAM6bzH6Ds5FPUQw== +-----END PRIVATE KEY----- diff --git a/test/tls/run_tests_tls_netaccess b/test/tls/run_tests_tls_netaccess new file mode 100755 index 0000000000000000000000000000000000000000..0d6a87d24b7c744c578ea901cf0d4901f8f3702a --- /dev/null +++ b/test/tls/run_tests_tls_netaccess @@ -0,0 +1,103 @@ +#!/bin/bash + + +function test_tls_server { + msg=$1 + suite=$2 + version=$3 + EXIT_CODE=test_tls_server_tmp_code + OSSL_STDO=test_tls_server_tmp_openssl_stdout + OSSL_STDE=test_tls_server_tmp_openssl_stderr + SCAPY_STDE=test_tls_server_tmp_scapy_stderr + + echo "1" > $EXIT_CODE + ($TRAVIS_SUDO python travis_test_server.py "$msg" 2>$SCAPY_STDE; echo $? > $EXIT_CODE) & + sleep 2 # wait for the server to start + + echo -n "$msg" | sudo openssl s_client -cipher $suite $version 1>$OSSL_STDO 2>$OSSL_STDE + sleep 0.5 # wait for the connection to be established, then torn down + + if [[ -z $version ]]; + then + version="" + else + version="and option $version" + fi + + if [ $(<$EXIT_CODE) == "0" ]; + then + echo "PASSED - TLS server test with cipher suite $suite $version" + rm -f $EXIT_CODE $OSSL_STDO $OSSL_STDE $SCAPY_STDE + else + echo "FAILED - TLS server test with cipher suite $suite $version" + echo -e "\n###\nHere are scapy writings to stderr:\n" + cat $SCAPY_STDE + echo -e "\nHere are openssl writings to stdout & stderr:\n" + cat $OSSL_STDO + cat $OSSL_STDE + echo -e "\n###\n" + rm -f $EXIT_CODE $OSSL_STDO $OSSL_STDE $SCAPY_STDE + exit 1 + fi +} + + +function test_tls_client { + # we test our TLS client against our own TLS server, because s_server scripting is broken + # be careful, as this might not detect every error (e.g. if key derivation was + # identically non-standard on both sides, this would not be detected) + msg=$1 + suite=$2 + version=$3 + EXIT_CODE=test_tls_client_tmp_code + SRV_STDO=test_tls_client_tmp_server_stdout + SRV_STDE=test_tls_client_tmp_server_stderr + CLI_STDO=test_tls_client_tmp_client_stdout + CLI_STDE=test_tls_client_tmp_client_stderr + + ($TRAVIS_SUDO python travis_test_server.py "$msg" 1>$SRV_STDO 2>$SRV_STDE; echo $? > $EXIT_CODE) & + sleep 1 # wait for the server to start + + $TRAVIS_SUDO python travis_test_client.py "$msg" $suite $version 1>$CLI_STDO 2>$CLI_STDE + sleep 0.5 + + if [[ -z $version ]]; + then + version="" + else + version="and version $version" + fi + + if [ $(<$EXIT_CODE) == "0" ]; + then + echo "PASSED - TLS client test with cipher suite $suite $version" + rm -f $CLI_STDO $CLI_STDE $SRV_STDO $SRV_STDE $EXIT_CODE + else + echo "FAILED - TLS client test with cipher suite $suite $version" + echo -e "\n###\nHere are scapy client writings to stdout & stderr:\n" + cat $CLI_STDO + cat $CLI_STDE + echo -e "\nHere are scapy server writings to stdout & stderr:\n" + cat $SRV_STDO + cat $SRV_STDE + echo -e "\n###\n" + rm -f $CLI_STDO $CLI_STDE $SRV_STDO $SRV_STDE $EXIT_CODE + exit 1 + fi +} + + +echo "TLS server automaton tests" +test_tls_server "Testing TLS server with TLS 1.0 and TLS_RSA_WITH_RC4_128_SHA" "RC4-SHA" "-tls1" +test_tls_server "Testing TLS server with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" "DHE-RSA-AES128-SHA256" "-tls1_2" +test_tls_server "Testing TLS server with TLS 1.2 and TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" "ECDHE-RSA-AES256-GCM-SHA384" "-tls1_2" +echo +sleep 1 + +echo "TLS client automaton tests" +test_tls_client "Testing TLS client with TLS 1.0 and TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" "0088" "0301" +test_tls_client "Testing TLS client with TLS 1.1 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" "c013" "0302" +test_tls_client "Testing TLS server with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" "009e" "0303" +test_tls_client "Testing TLS server with SSLv3 and TLS_RSA_EXPORT_WITH_RC4_40_MD5" "0003" "0300" + + diff --git a/test/tls/travis_test_client.py b/test/tls/travis_test_client.py new file mode 100755 index 0000000000000000000000000000000000000000..7bbdeb9b3404f0f940667112cbe86a6cc95a708a --- /dev/null +++ b/test/tls/travis_test_client.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +## This file is part of Scapy +## This program is published under a GPLv2 license + +""" +TLS client used in unit tests. +Usage: [sudo] ./unit_test_client.py [send_data [cipher_suite_code [version]]] + +Start our TLS client, send our send_data, and terminate session with an Alert. +Optional cipher_cuite_code and version may be provided as hexadecimal strings +(e.g. c09e for TLS_DHE_RSA_WITH_AES_128_CCM and 0303 for TLS 1.2). +Reception of the exact send_data on the server is to be checked externally. +""" + +import os +import sys + +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) +sys.path=[basedir]+sys.path + +from scapy.layers.tls.automaton import TLSClientAutomaton +from scapy.layers.tls.handshake import TLSClientHello + + +send_data = cipher_suite_code = version = None + +if len(sys.argv) >= 2: + send_data = sys.argv[1] + +if len(sys.argv) >= 3: + cipher_suite_code = int(sys.argv[2], 16) + +if len(sys.argv) >= 4: + version = int(sys.argv[3], 16) + + +ch = TLSClientHello(version=version, ciphers=cipher_suite_code) +t = TLSClientAutomaton(client_hello=ch, data=send_data) +t.run() + + diff --git a/test/tls/travis_test_server.py b/test/tls/travis_test_server.py new file mode 100755 index 0000000000000000000000000000000000000000..e262c63ecb1e6d263c7e1af878c5bca02824b2db --- /dev/null +++ b/test/tls/travis_test_server.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +## This file is part of Scapy +## This program is published under a GPLv2 license + +""" +TLS server used in unit tests. +Usage: [sudo] ./unit_test_server.py [expected_data] + +When some expected_data is provided, a TLS client (e.g. openssl s_client) +should send some application data after the handshake. If this data matches our +expected_data, then we leave with exit code 0. Else we leave with exit code 1. +If no expected_data was provided and the handshake was ok, we exit with 0. +""" + +import os +import sys +from contextlib import contextmanager +from StringIO import StringIO + +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) +sys.path=[basedir]+sys.path + +from scapy.layers.tls.automaton import TLSServerAutomaton + + +@contextmanager +def captured_output(): + new_out, new_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err + +def check_output_for_data(out, err, data): + if err.getvalue(): + sys.exit(1) + + output = out.getvalue().strip() + if expected_data: + lines = output.split("\n") + for l in lines: + if l.startswith("Received"): + break + if l == ("Received '%s'" % data): + sys.exit(0) + sys.exit(1) + sys.exit(0) + + +if len(sys.argv) == 2: + expected_data = sys.argv[1] +else: + expected_data = None + +with captured_output() as (out, err): + t = TLSServerAutomaton(mycert=basedir+'/test/tls/pki/srv_cert.pem', + mykey=basedir+'/test/tls/pki/srv_key.pem') + t.run() + +check_output_for_data(out, err, expected_data) + diff --git a/test/x509.uts b/test/x509.uts index d61e2445dd239b0a6b473c70bb4a03c45ccd688e..e481e0ea06bc2eb355691d642d52ef6bad01da22 100644 --- a/test/x509.uts +++ b/test/x509.uts @@ -204,3 +204,45 @@ r = ASN1F_PACKET("otherName", None, X509_OtherName).randval() assert(isinstance(r, X509_OtherName)) str(r.type_id) == '171.184.10.271' + +############ OCSP class ############################################### + += OCSP class : OCSP Response import +s = '0\x82\x01\xd3\n\x01\x00\xa0\x82\x01\xcc0\x82\x01\xc8\x06\t+\x06\x01\x05\x05\x070\x01\x01\x04\x82\x01\xb90\x82\x01\xb50\x81\x9e\xa2\x16\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x18\x0f20160914121000Z0s0q0I0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xcf&\xf5\x18\xfa\xc9~\x8f\x8c\xb3B\xe0\x1c/j\x10\x9e\x8e_\n\x04\x14Qh\xff\x90\xaf\x02\x07u<\xcc\xd9edb\xa2\x12\xb8Yr;\x02\x10\x07z]\xc36#\x01\xf9\x89\xfeT\xf7\xf8o>d\x80\x00\x18\x0f20160914121000Z\xa0\x11\x18\x0f20160921112500Z0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x90\xef\xf9\x15U\x88\xac@l\xf6n\x04C/\x1a\xf5\xbc[Xi\xd9U\xbe\'\xd3\xb7\xf5\xbb\t\xd8\xb1Tw\x9c2\xac\x7f\x88\xba\x98\xe4\xa13\xf4\xdc\xea\xf3\xacX\xe4,E\xf5\xa9\xc3\xf4B-N\xe0\x89D[\xbe\n\xc2h\x9ar\xfd\'.\xc8,\xed\x83\xc2\xf0\x89_\x8c\xc3\xe7\x8a\xad\xa4\x14\x03\x96\x02\xc4\xa8\xc8\x90\x96%X\x80\x95\x02\x9d_\xc82;m\xe9\x15\x00\xa8\x00\xb9\x01\xe3aN&\xe4\xd5\x8a\xc4w7\x0b\xc3~\xc5\xb1M\x10~T\x9e\x1d\xf6\x06\xf8\x12sTg\x14b_\xe7\xc04\xb4\xa3\xd2\x8f\xe6\xa6\xc4\x01q\x03j\xc8\xd4\xc7\x89\xdde\x99\x1a\xd9\x02\xe7\x17\xd1\xf40P\xef\xf6$\xee\xfad\xf4\xeb\xc8\xf7\x0bRL\x8b\xa5x\xe4R2\xe9\xc2\xfcB\nh\x93\xf7\x0ep4h\xeb\x17\x83\xc8\x88!\xc3W\x94WG\xfe3\x15C0qE&A\x99\xa8}\x1a\xda"\xa9O\xba\x90W_W\xado\x1c\xf0`g7\xbb$\x91o\xec\xdd\xbd\x9e\x8bb\xfc' +response = OCSP_Response(s) + += OCSP class : OCSP Response global checks +assert(response.responseStatus.val == 0) +assert(isinstance(response.responseBytes, OCSP_ResponseBytes)) +responseBytes = response.responseBytes +assert(responseBytes.responseType == ASN1_OID("basic-response")) +assert(responseBytes.signatureAlgorithm.algorithm == ASN1_OID("sha256WithRSAEncryption")) +assert(responseBytes.signatureAlgorithm.parameters == ASN1_NULL(0)) +assert(responseBytes.signature.val_readable[:3] == "\x90\xef\xf9" and responseBytes.signature.val_readable[-3:] == "\x8bb\xfc") +responseBytes.certs is None + += OCSP class : OCSP ResponseData checks +responseData = responseBytes.tbsResponseData +assert(responseData.version is None) +rID = responseData.responderID.responderID +assert(isinstance(rID, OCSP_ByKey)) +assert(rID.byKey.val[:3] == "Qh\xff" and rID.byKey.val[-3:] == "Yr;") +assert(responseData.producedAt == ASN1_GENERALIZED_TIME("20160914121000Z")) +assert(len(responseData.responses) == 1) +responseData.responseExtensions is None + += OCSP class : OCSP SingleResponse checks +singleResponse = responseData.responses[0] +assert(singleResponse.certID.hashAlgorithm.algorithm == ASN1_OID("sha1")) +assert(singleResponse.certID.hashAlgorithm.parameters == ASN1_NULL(0)) +assert(singleResponse.certID.issuerNameHash.val[:3] == "\xcf&\xf5" and singleResponse.certID.issuerNameHash.val[-3:] == "\x8e_\n") +assert(singleResponse.certID.issuerKeyHash.val[:3] == "Qh\xff" and singleResponse.certID.issuerKeyHash.val[-3:] == "Yr;") +assert(singleResponse.certID.serialNumber.val == 0x77a5dc3362301f989fe54f7f86f3e64) +assert(isinstance(singleResponse.certStatus.certStatus, OCSP_GoodInfo)) +assert(singleResponse.thisUpdate == ASN1_GENERALIZED_TIME("20160914121000Z")) +assert(singleResponse.nextUpdate == ASN1_GENERALIZED_TIME("20160921112500Z")) +singleResponse.singleExtensions is None + += OCSP class : OCSP Response reconstruction +str(response) == s +