diff --git a/README.md b/README.md index 34f9dd2..a43a17b 100644 --- a/README.md +++ b/README.md @@ -248,31 +248,31 @@ s.close() | | length= 0x12 | |###[ TLS Extension Signature And Hash Algorithm ]### | | length= 0x10 - | | \algorithms\ + | | \algs\ | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= rsa + | | | hash_alg= sha256 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= rsa + | | | hash_alg= sha384 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= rsa + | | | hash_alg= sha1 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha256 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha384 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha1 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= dsa + | | | hash_alg= sha256 + | | | sig_alg= dsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= dsa + | | | hash_alg= sha1 + | | | sig_alg= dsa ``` ##### Full Handshake with Application Data (DHE_RSA_WITH_AES_128_CBC_SHA) @@ -671,10 +671,10 @@ TLS1.0 Session Context based decryption of RSA_WITH_AES_128_CBC_SHA for known pr # python examples/sessionctx_sniffer.py 192.168.220.131 443 tests/files/RSA_WITH_AES_128_CBC_SHA_w_key.pcap tests/files/openssl_1_0_1_f_server.pem * pcap ready! * load servers privatekey for ciphertext decryption (RSA key only): tests/files/openssl_1_0_1_f_server.pem -| 192.168.220.1 :54908 => 192.168.220.131 :443 | >, >, , , , , , , , , ] |>>, >] |>>>] |> +| 192.168.220.1 :54908 => 192.168.220.131 :443 | >, >, , , , , , , , , ] |>>, >] |>>>] |> | 192.168.220.131 :443 => 192.168.220.1 :54908 | >] |>>>, sn= sign_OID['.2.5.4.6']> value= |>, value=}}>]> |>, value= value= not_after= subject=[ value= not found for tag }}>]> |>, not found for tag }}>]> |>, value=}}>]> |>] pubkey_algo= pk_value= pubkey=\xf3I("\xd3\xb9\xfe\xe0\xde\xe48\xce\xee"\x1c\xe9\x91;\x94\xd0r/\x87\x85YKf\xb1\xc5\xf5z\x85]\xc2\x0f\xd3.)X6\xccHk\xa2\xa2\xxfd\xea\xf985+\xf4\xe6\x9a\x0e\xf6\xbb\x12\xab\x87!\xc3/\xbc\xf4\x06\xb8\x8f\x8e\x10\x07\'\x95\xe5B\xcb\xd1\xd5\x10\x8c\x92\xbMW\x06U!"%\xdb\xf3\xaa\xa9`\xbfM\xaay\xd1\xab\x92H\xba\x19\x8e\x12\xech\xd9\xc6\xba\xdf\xecZ\x1c\xd8C\xfe\xe7R\xc9\xcf\x02\xxa2\x13J%\xaf\xe6\x1c\xb1%\xbf\xb4\x99\xa2S\xd3\xa2\x02\xbf\x11\x02\x03\x01\x00\x01']> x509v3ext=[, , ]]> |>, ]]> |>, , , =\x86\xab!\x81\x87\xda\xda']>]]> |>] sign_["\x00\xa9\xbdMW@t\xfe\x96\xe9+\xd6x\xfd\xb3c\xcc\xf4\x0bM\x12\xcaZt\x8d\x9b\xf2a\xe6\xfd\x06\x11C\x84\xfc\x17\xa0\xeccc6\xb9x02\x081\x9a\xf1\xd9\x17\xc5\xe9\xa6\xa5\x96Km@\xa9[e(\xcb\xcb\x00\x03\x82c7\xd3\xad\xb1\x96;v\xf5\x17\x16\x02{\xbdSSFr4\xd6\b3\x10\xf7l\xc6\x85K-'\xad\n \\\xfb\x8d\x19p4\xb9u_|\x87\xd5\xc3\xec\x93\x13A\xfcs\x03\xb9\x8d\x1a\xfe\xf7&\x86I\x03\xa9\xc5\\xc1C\xc7\xe0%\xb6\xf1\xd3\x00\xd7@\xabK\x7f+z>\xa6\x99LT"]> |> |>] |>>>, \xength=0x76 cipher_suites=['ECDHE_RSA_WITH_AES_256_GCM_SHA384', 'ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', 'ECDHE_RSA_WITH_AES_256ECDSA_WITH_AES_256_CBC_SHA', 'DHE_DSS_WITH_AES_256_GCM_SHA384', 'DHE_RSA_WITH_AES_256_GCM_SHA384', 'DHE_RSA_WITH_AES_256_CBC_256_CBC_SHA', 'DHE_RSA_WITH_CAMELLIA_256_CBC_SHA', 'DHE_DSS_WITH_CAMELLIA_256_CBC_SHA', 'ECDH_RSA_WITH_AES_256_GCM_SHA384', '256_CBC_SHA384', 'ECDH_RSA_WITH_AES_256_CBC_SHA', 'ECDH_ECDSA_WITH_AES_256_CBC_SHA', 'RSA_WITH_AES_256_GCM_SHA384', 'RSA_WITHWITH_AES_128_GCM_SHA256', 'ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 'ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'ECDHE_ECDSA_WITH_AES_1_WITH_AES_128_GCM_SHA256', 'DHE_RSA_WITH_AES_128_GCM_SHA256', 'DHE_RSA_WITH_AES_128_CBC_SHA256', 'DHE_DSS_WITH_AES_128_CBC_SHSHA', 'DHE_DSS_WITH_SEED_CBC_SHA', 'DHE_RSA_WITH_CAMELLIA_128_CBC_SHA', 'DHE_DSS_WITH_CAMELLIA_128_CBC_SHA', 'ECDH_RSA_WITH_A'ECDH_ECDSA_WITH_AES_128_CBC_SHA256', 'ECDH_RSA_WITH_AES_128_CBC_SHA', 'ECDH_ECDSA_WITH_AES_128_CBC_SHA', 'RSA_WITH_AES_128_G, 'RSA_WITH_CAMELLIA_128_CBC_SHA', 'ECDHE_RSA_WITH_3DES_EDE_CBC_SHA', 'ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA', 'DHE_RSA_WITH_3DESWITH_3DES_EDE_CBC_SHA', 'RSA_WITH_3DES_EDE_CBC_SHA', 'EMPTY_RENEGOTIATION_INFO_SCSV'] compression_methods_length=0x1 compresslength=0x4 |, , , hAlgorithm hash_alg=sha384 sig_alg=ecdsa |>, , , , , ] |>>, >, > params.negotiated.version=TLS_1_0 params.negotiated.ciphersuite=RSA_WITH_AES_128_CBC_SHA diff --git a/README.rst b/README.rst index 4f7b944..65276fd 100644 --- a/README.rst +++ b/README.rst @@ -292,31 +292,31 @@ Dissect TLSClientHello (pcap) | | length= 0x12 | |###[ TLS Extension Signature And Hash Algorithm ]### | | length= 0x10 - | | \algorithms\ + | | \algs\ | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= rsa + | | | hash_alg= sha256 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= rsa + | | | hash_alg= sha384 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= rsa + | | | hash_alg= sha1 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha256 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha384 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha1 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= dsa + | | | hash_alg= sha256 + | | | sig_alg= dsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= dsa + | | | hash_alg= sha1 + | | | sig_alg= dsa Full Handshake with Application Data (DHE\_RSA\_WITH\_AES\_128\_CBC\_SHA) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' @@ -723,10 +723,10 @@ for known private key # python examples/sessionctx_sniffer.py 192.168.220.131 443 tests/files/RSA_WITH_AES_128_CBC_SHA_w_key.pcap tests/files/openssl_1_0_1_f_server.pem * pcap ready! * load servers privatekey for ciphertext decryption (RSA key only): tests/files/openssl_1_0_1_f_server.pem - | 192.168.220.1 :54908 => 192.168.220.131 :443 | >, >, , , , , , , , , ] |>>, >] |>>>] |> + | 192.168.220.1 :54908 => 192.168.220.131 :443 | >, >, , , , , , , , , ] |>>, >] |>>>] |> | 192.168.220.131 :443 => 192.168.220.1 :54908 | >] |>>>, sn= sign_OID['.2.5.4.6']> value= |>, value=}}>]> |>, value= value= not_after= subject=[ value= not found for tag }}>]> |>, not found for tag }}>]> |>, value=}}>]> |>] pubkey_algo= pk_value= pubkey=\xf3I("\xd3\xb9\xfe\xe0\xde\xe48\xce\xee"\x1c\xe9\x91;\x94\xd0r/\x87\x85YKf\xb1\xc5\xf5z\x85]\xc2\x0f\xd3.)X6\xccHk\xa2\xa2\xxfd\xea\xf985+\xf4\xe6\x9a\x0e\xf6\xbb\x12\xab\x87!\xc3/\xbc\xf4\x06\xb8\x8f\x8e\x10\x07\'\x95\xe5B\xcb\xd1\xd5\x10\x8c\x92\xbMW\x06U!"%\xdb\xf3\xaa\xa9`\xbfM\xaay\xd1\xab\x92H\xba\x19\x8e\x12\xech\xd9\xc6\xba\xdf\xecZ\x1c\xd8C\xfe\xe7R\xc9\xcf\x02\xxa2\x13J%\xaf\xe6\x1c\xb1%\xbf\xb4\x99\xa2S\xd3\xa2\x02\xbf\x11\x02\x03\x01\x00\x01']> x509v3ext=[, , ]]> |>, ]]> |>, , , =\x86\xab!\x81\x87\xda\xda']>]]> |>] sign_["\x00\xa9\xbdMW@t\xfe\x96\xe9+\xd6x\xfd\xb3c\xcc\xf4\x0bM\x12\xcaZt\x8d\x9b\xf2a\xe6\xfd\x06\x11C\x84\xfc\x17\xa0\xeccc6\xb9x02\x081\x9a\xf1\xd9\x17\xc5\xe9\xa6\xa5\x96Km@\xa9[e(\xcb\xcb\x00\x03\x82c7\xd3\xad\xb1\x96;v\xf5\x17\x16\x02{\xbdSSFr4\xd6\b3\x10\xf7l\xc6\x85K-'\xad\n \\\xfb\x8d\x19p4\xb9u_|\x87\xd5\xc3\xec\x93\x13A\xfcs\x03\xb9\x8d\x1a\xfe\xf7&\x86I\x03\xa9\xc5\\xc1C\xc7\xe0%\xb6\xf1\xd3\x00\xd7@\xabK\x7f+z>\xa6\x99LT"]> |> |>] |>>>, \xength=0x76 cipher_suites=['ECDHE_RSA_WITH_AES_256_GCM_SHA384', 'ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', 'ECDHE_RSA_WITH_AES_256ECDSA_WITH_AES_256_CBC_SHA', 'DHE_DSS_WITH_AES_256_GCM_SHA384', 'DHE_RSA_WITH_AES_256_GCM_SHA384', 'DHE_RSA_WITH_AES_256_CBC_256_CBC_SHA', 'DHE_RSA_WITH_CAMELLIA_256_CBC_SHA', 'DHE_DSS_WITH_CAMELLIA_256_CBC_SHA', 'ECDH_RSA_WITH_AES_256_GCM_SHA384', '256_CBC_SHA384', 'ECDH_RSA_WITH_AES_256_CBC_SHA', 'ECDH_ECDSA_WITH_AES_256_CBC_SHA', 'RSA_WITH_AES_256_GCM_SHA384', 'RSA_WITHWITH_AES_128_GCM_SHA256', 'ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 'ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'ECDHE_ECDSA_WITH_AES_1_WITH_AES_128_GCM_SHA256', 'DHE_RSA_WITH_AES_128_GCM_SHA256', 'DHE_RSA_WITH_AES_128_CBC_SHA256', 'DHE_DSS_WITH_AES_128_CBC_SHSHA', 'DHE_DSS_WITH_SEED_CBC_SHA', 'DHE_RSA_WITH_CAMELLIA_128_CBC_SHA', 'DHE_DSS_WITH_CAMELLIA_128_CBC_SHA', 'ECDH_RSA_WITH_A'ECDH_ECDSA_WITH_AES_128_CBC_SHA256', 'ECDH_RSA_WITH_AES_128_CBC_SHA', 'ECDH_ECDSA_WITH_AES_128_CBC_SHA', 'RSA_WITH_AES_128_G, 'RSA_WITH_CAMELLIA_128_CBC_SHA', 'ECDHE_RSA_WITH_3DES_EDE_CBC_SHA', 'ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA', 'DHE_RSA_WITH_3DESWITH_3DES_EDE_CBC_SHA', 'RSA_WITH_3DES_EDE_CBC_SHA', 'EMPTY_RENEGOTIATION_INFO_SCSV'] compression_methods_length=0x1 compresslength=0x4 |, , , hAlgorithm hash_alg=sha384 sig_alg=ecdsa |>, , , , , ] |>>, >, > params.negotiated.version=TLS_1_0 params.negotiated.ciphersuite=RSA_WITH_AES_128_CBC_SHA diff --git a/examples/SCSV_fallback_test.py b/examples/SCSV_fallback_test.py index e1373b6..d5dc368 100644 --- a/examples/SCSV_fallback_test.py +++ b/examples/SCSV_fallback_test.py @@ -20,22 +20,21 @@ import itertools # https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) PROTOS = [p for p in TLS_VERSIONS.values() if p.startswith("TLS_") or p.startswith("SSL_3")] - TESTS = itertools.product(PROTOS,repeat=2) + TESTS = itertools.product(PROTOS, repeat=2) RESULTS = [] TLS_FALLBACK_SCSV_SUPPORTED = False SSLV3_ENABLED = True - for t in TESTS: print ("----------------") print ("TEST : %s" % repr(t)) @@ -43,14 +42,14 @@ print ("connecting..") # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) print ("connected.") # create TLS Handhsake / Client Hello packet - outer,inner = t - p = TLSRecord(version=outer)/TLSHandshake()/TLSClientHello(version=inner, + outer, inner = t + p = TLSRecord(version=outer) / TLSHandshake() / TLSClientHello(version=inner, compression_methods=range(0xff), - cipher_suites=range(0xff)+[0x5600], + cipher_suites=range(0xff) + [0x5600], ) p.show() print ("sending TLS payload") @@ -63,32 +62,35 @@ if resp.haslayer(TLSAlert): v = resp[TLSRecord].version - if resp[TLSAlert].description==86: # INAPPROPRIATE_FALLBACK + if resp[TLSAlert].description == 86: # INAPPROPRIATE_FALLBACK print ("[* ] SUCCESS - server honors TLS_FALLBACK_SCSV") - RESULTS.append((t,"resp: TLSAlert.INAPPROPRIATE_FALLBACK %s"%TLS_VERSIONS.get(v,v))) - TLS_FALLBACK_SCSV_SUPPORTED=True # we've caught the SCSV alert + RESULTS.append((t, "resp: TLSAlert.INAPPROPRIATE_FALLBACK %s" % TLS_VERSIONS.get(v, v))) + TLS_FALLBACK_SCSV_SUPPORTED = True # we've caught the SCSV alert else: print ("[- ] UNKNOWN - server responds with unexpected alert") - a_descr=resp[TLSAlert].description - RESULTS.append((t,"resp: TLSAlert.%s"%TLS_ALERT_DESCRIPTIONS.get(a_descr,a_descr))) + a_descr = resp[TLSAlert].description + RESULTS.append((t, "resp: TLSAlert.%s" % TLS_ALERT_DESCRIPTIONS.get(a_descr, a_descr))) elif resp.haslayer(TLSServerHello): print ("[!!] FAILED - server allows downgrade to %s" % t[1]) v_outer = resp[TLSRecord].version v = resp[TLSServerHello].version - RESULTS.append((t,"resp: TLSServerHello: outer %s inner %s"%(TLS_VERSIONS.get(v_outer,v_outer),TLS_VERSIONS.get(v,v)))) - if t[1]=="TLS_3_0": - SSLV3_ENABLED=False + RESULTS.append( + (t, "resp: TLSServerHello: outer %s inner %s" % + (TLS_VERSIONS.get( + v_outer, v_outer), TLS_VERSIONS.get( + v, v)))) + if t[1] == "TLS_3_0": + SSLV3_ENABLED = False else: print ("[!!] UNKNOWN - unexpected response..") - RESULTS.append((t,"Unexpected response")) + RESULTS.append((t, "Unexpected response")) print ("-----------------------") print ("for: %s" % repr(target)) print (" record hello ") - for t,r in RESULTS: + for t, r in RESULTS: print ("%s ... %s" % (t, r)) print ("overall:") print (" TLS_FALLBACK_SCSV_SUPPORTED ... %s" % repr(TLS_FALLBACK_SCSV_SUPPORTED)) print (" SSLv3_ENABLED ... %s" % repr(SSLV3_ENABLED)) - diff --git a/examples/client_hello_complex_invalid.py b/examples/client_hello_complex_invalid.py index ecfc052..6938a5f 100644 --- a/examples/client_hello_complex_invalid.py +++ b/examples/client_hello_complex_invalid.py @@ -18,34 +18,34 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create TLS Handhsake / Client Hello packet - p = TLSRecord(version='TLS_1_2')/ \ - TLSHandshake()/ \ - TLSClientHello(compression_methods= [TLSCompressionMethod.NULL,TLSCompressionMethod.DEFLATE]+range(255-2), - cipher_suites= [TLSCipherSuite.NULL_WITH_NULL_NULL]+range(0xff-1), - extensions=[ - TLSExtension()/ \ - TLSExtServerNameIndication(server_names= [TLSServerName(data="a"*500,length=16), - TLSServerName(length=222)]), - TLSExtension()/ \ - TLSExtServerNameIndication(server_names=[TLSServerName(length=2)]) - ]) + p = TLSRecord(version='TLS_1_2') / \ + TLSHandshake() / \ + TLSClientHello(compression_methods=[TLSCompressionMethod.NULL, TLSCompressionMethod.DEFLATE] + range(255 - 2), + cipher_suites=[TLSCipherSuite.NULL_WITH_NULL_NULL] + range(0xff - 1), + extensions=[ + TLSExtension() / + TLSExtServerNameIndication(server_names=[TLSServerName(data="a" * 500, length=16), + TLSServerName(length=222)]), + TLSExtension() / + TLSExtServerNameIndication(server_names=[TLSServerName(length=2)]) + ]) p.show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %s" % repr(resp)) SSL(resp).show() diff --git a/examples/client_hello_rsa.py b/examples/client_hello_rsa.py index 09f2dd5..26d8cf4 100644 --- a/examples/client_hello_rsa.py +++ b/examples/client_hello_rsa.py @@ -18,23 +18,23 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create TLS Handhsake / Client Hello packet - p = TLSRecord()/TLSHandshake()/TLSClientHello(cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA]) + p = TLSRecord() / TLSHandshake() / TLSClientHello(cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA]) p.show() print ("sending TLS payload") s.sendall(str(p)) - resp=s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %d -- %s" % (len(resp), repr(resp))) SSL(resp).show() - s.close() \ No newline at end of file + s.close() diff --git a/examples/client_hello_twice.py b/examples/client_hello_twice.py index e50e90d..911a2a8 100644 --- a/examples/client_hello_twice.py +++ b/examples/client_hello_twice.py @@ -18,31 +18,33 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create TLS Handhsake / Client Hello packet - p = TLSRecord(version="SSL_3_0")/TLSHandshake()/TLSClientHello(version="SSL_3_0",compression_methods=range(0xff), cipher_suites=range(0xff)) + p = TLSRecord(version="SSL_3_0") / TLSHandshake() / TLSClientHello(version="SSL_3_0", + compression_methods=range(0xff), + cipher_suites=range(0xff)) p.show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %s" % repr(resp)) SSL(resp).show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %s" % repr(resp)) SSL(resp).show() - s.close() \ No newline at end of file + s.close() diff --git a/examples/client_hello_valid.py b/examples/client_hello_valid.py index c88ae5a..1e8a93d 100644 --- a/examples/client_hello_valid.py +++ b/examples/client_hello_valid.py @@ -18,25 +18,25 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create TLS Handhsake / Client Hello packet - p = TLSRecord()/TLSHandshake()/TLSClientHello(compression_methods=range(0xff)[::-1], cipher_suites=range(0xff)) + p = TLSRecord() / TLSHandshake() / TLSClientHello(compression_methods=range(0xff)[::-1], cipher_suites=range(0xff)) p.show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %s" % repr(resp)) SSL(resp).show() diff --git a/examples/client_hello_valid_w_alpn.py b/examples/client_hello_valid_w_alpn.py index 975bb22..c23e1e1 100644 --- a/examples/client_hello_valid_w_alpn.py +++ b/examples/client_hello_valid_w_alpn.py @@ -18,34 +18,33 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create TLS Handhsake / Client Hello packet - p = TLSRecord()/ \ - TLSHandshake()/ \ + p = TLSRecord() / \ + TLSHandshake() / \ TLSClientHello(compression_methods=range(0xff), cipher_suites=range(0xff), - extensions=[TLSExtension()/ \ - TLSExtALPN(protocol_name_list= \ - [TLSALPNProtocol(data="http/1.1"), - TLSALPNProtocol(data="http/1.3"), - TLSALPNProtocol(data="\x00htt\x01%sp/1.1"), - ])],) + extensions=[TLSExtension() / + TLSExtALPN(protocol_name_list=[TLSALPNProtocol(data="http/1.1"), + TLSALPNProtocol(data="http/1.3"), + TLSALPNProtocol(data="\x00htt\x01%sp/1.1"), + ])],) p.show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(1024*8) + resp = s.recv(1024 * 8) print ("received, %s" % repr(resp)) SSL(resp).show() - s.close() \ No newline at end of file + s.close() diff --git a/examples/client_hello_with_session_ticket.py b/examples/client_hello_with_session_ticket.py index e7685f7..cd54aa9 100644 --- a/examples/client_hello_with_session_ticket.py +++ b/examples/client_hello_with_session_ticket.py @@ -29,7 +29,7 @@ ticket = server_finished[TLSSessionTicket].ticket tls_socket.sendall(to_raw(TLSPlaintext(data="GET / HTTP/1.1\r\nHOST: localhost\r\n\r\n"), tls_ctx)) tls_socket.recvall() - master_secret = tls_ctx.crypto.session.master_secret + master_secret = tls_ctx.master_secret print("First session context: %s" % tls_ctx) @@ -38,7 +38,8 @@ tls_ctx = tls_socket.tls_ctx tls_socket.tls_ctx.resume_session(master_secret) - pkt = TLSRecord() / TLSHandshake() / TLSClientHello(version=version, cipher_suites=[cipher], + pkt = TLSRecord() / TLSHandshake() / TLSClientHello(version=version, + cipher_suites=[cipher], extensions=[TLSExtension() / TLSExtSessionTicketTLS(data=ticket)]) tls_socket.sendall(pkt) resp = tls_socket.recvall() @@ -48,5 +49,3 @@ tls_socket.recvall() print("Resumed session context: %s" % tls_ctx) - - diff --git a/examples/client_rsa_mutual_auth.py b/examples/client_rsa_mutual_auth.py index 2778fea..fcd70a4 100644 --- a/examples/client_rsa_mutual_auth.py +++ b/examples/client_rsa_mutual_auth.py @@ -9,7 +9,7 @@ from Crypto.Hash import SHA256 -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) try: # This import works from the project directory from scapy_ssl_tls.ssl_tls import * @@ -30,13 +30,12 @@ def do_tls_mutual_auth(host): socket_ = socket.socket() tls_socket = TLSSocket(socket_, client=True) tls_socket.connect(host) - tls_socket.tls_ctx.rsa_load_keys_from_file(os.path.join(basedir, - "tests/integration/keys/scapy-tls-client.key.pem"), - client=True) + tls_socket.tls_ctx.client_ctx.load_rsa_keys_from_file(os.path.join( + basedir, "tests/integration/keys/scapy-tls-client.key.pem")) client_hello = TLSRecord(version=tls_version) / TLSHandshake() /\ - TLSClientHello(version=tls_version, compression_methods=[TLSCompressionMethod.NULL, ], - cipher_suites=[TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256, ]) + TLSClientHello(version=tls_version, compression_methods=[TLSCompressionMethod.NULL, ], + cipher_suites=[TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256, ]) tls_socket.sendall(client_hello) server_hello = tls_socket.recvall() server_hello.show() @@ -50,8 +49,8 @@ def do_tls_mutual_auth(host): sig = tls_socket.tls_ctx.get_client_signed_handshake_hash(SHA256.new()) # sig = sig[:128] + chr(ord(sig[128]) ^ 0xff) + sig[129:] client_cert_verify = TLSRecord(version=tls_version) / TLSHandshake() / \ - TLSCertificateVerify(alg=sig_hash_alg, - sig=sig) + TLSCertificateVerify(alg=sig_hash_alg, + sig=sig) tls_socket.sendall(client_cert_verify) client_ccs = TLSRecord(version=tls_version) / TLSChangeCipherSpec() @@ -73,4 +72,3 @@ def do_tls_mutual_auth(host): else: server = ("127.0.0.1", 8443) do_tls_mutual_auth(server) - diff --git a/examples/cve-2014-3466.py b/examples/cve-2014-3466.py index 961d83d..b2c53e9 100644 --- a/examples/cve-2014-3466.py +++ b/examples/cve-2014-3466.py @@ -98,37 +98,37 @@ | | length = 0x16 | |###[ TLS Extension Signature And Hash Algorithm ]### | | length = 0x14 - | | \algorithms\ + | | \algs\ | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= rsa + | | | hash_alg= sha256 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= rsa + | | | hash_alg= sha384 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha512 - | | | signature_algorithm= rsa + | | | hash_alg= sha512 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= rsa + | | | hash_alg= sha1 + | | | sig_alg= rsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha256 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha384 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha384 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha512 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha512 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= ecdsa + | | | hash_alg= sha1 + | | | sig_alg= ecdsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha256 - | | | signature_algorithm= dsa + | | | hash_alg= sha256 + | | | sig_alg= dsa | | |###[ TLS Signature Hash Algorithm Pair ]### - | | | hash_algorithm= sha1 - | | | signature_algorithm= dsa + | | | hash_alg= sha1 + | | | sig_alg= dsa [*] received TLSClientHello, sending malicious server hello (announcing sessid_len=100, sending 250 bytes) ###[ TLS Record ]### content_type= handshake @@ -163,11 +163,11 @@ def main(): - host = sys.argv[1] if len(sys.argv)>1 else "0.0.0.0" - port = int(sys.argv[2]) if len(sys.argv)>2 else 443 - target = host,port + host = sys.argv[1] if len(sys.argv) > 1 else "0.0.0.0" + port = int(sys.argv[2]) if len(sys.argv) > 2 else 443 + target = host, port - sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(target) sock.listen(1) print ("[ ] listening on %s ..." % repr(target)) @@ -186,21 +186,21 @@ def main(): client_sock.close() continue print ( - "[*] received TLSClientHello, sending malicious server hello (announcing sessid_len=100, sending 250 bytes)") + "[*] received TLSClientHello, sending malicious server hello (announcing sessid_len=100, sending 250 bytes)") - cipher = p_client_hello[TLSClientHello].cipher_suites[0] if TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA not in p_client_hello[TLSClientHello].cipher_suites else TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA + cipher = p_client_hello[TLSClientHello].cipher_suites[0] if TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA not in p_client_hello[ + TLSClientHello].cipher_suites else TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA - p_server_hello = TLSRecord(version=p_client_hello[TLSClientHello].version)/TLSHandshake()/ \ - TLSServerHello(version=p_client_hello[TLSClientHello].version, - compression_method = TLSCompressionMethod.NULL, - cipher_suite = cipher, - session_id_length = 100, - session_id = '\xff'*250, # overflow - ) + p_server_hello = TLSRecord(version=p_client_hello[TLSClientHello].version) / TLSHandshake() / \ + TLSServerHello(version=p_client_hello[TLSClientHello].version, + compression_method=TLSCompressionMethod.NULL, + cipher_suite=cipher, + session_id_length=100, + session_id='\xff' * 250, # overflow + ) p_server_hello.show() tlssock.sendall(p_server_hello) -if __name__=="__main__": +if __name__ == "__main__": main() - diff --git a/examples/full_rsa_connection_with_application_data.py b/examples/full_rsa_connection_with_application_data.py index 74bbfd8..e1862ba 100644 --- a/examples/full_rsa_connection_with_application_data.py +++ b/examples/full_rsa_connection_with_application_data.py @@ -18,12 +18,12 @@ def tls_hello(sock): client_hello = TLSRecord(version=tls_version) / TLSHandshake() /\ - TLSClientHello(version=tls_version, compression_methods=[TLSCompressionMethod.NULL, ], - cipher_suites=[TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256, ]) - # cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, ]) - # cipher_suites=[TLSCipherSuite.RSA_WITH_RC4_128_SHA, ]) - # cipher_suites=[TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA, ]) - # cipher_suites=[TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA, ]) + TLSClientHello(version=tls_version, compression_methods=[TLSCompressionMethod.NULL, ], + cipher_suites=[TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256, ]) + # cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, ]) + # cipher_suites=[TLSCipherSuite.RSA_WITH_RC4_128_SHA, ]) + # cipher_suites=[TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA, ]) + # cipher_suites=[TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA, ]) sock.sendall(client_hello) server_hello = sock.recvall() server_hello.show() diff --git a/examples/security_scanner.py b/examples/security_scanner.py index 8a65ec7..36318d9 100644 --- a/examples/security_scanner.py +++ b/examples/security_scanner.py @@ -21,22 +21,24 @@ try: # This import works from the project directory from scapy_ssl_tls.ssl_tls import * - from scapy_ssl_tls.ssl_tls_crypto import x509_extract_pubkey_from_der -except ImportError: + import scapy_ssl_tls.ssl_tls_keystore as tlsk +except ImportError as ie: # If you installed this package via pip, you just need to execute this from scapy.layers.ssl_tls import * - from scapy.layers.ssl_tls_crypto import x509_extract_pubkey_from_der + import scapy.layers.ssl_tls_keystore as tlsk import socket from collections import namedtuple import time + class TCPConnection(object): + def __init__(self, target, starttls=None): last_exception = None - self.target=target + self.target = target self._s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - for t in xrange(1,4): + for t in xrange(1, 4): try: self._s.connect(target) break @@ -46,7 +48,7 @@ def __init__(self, target, starttls=None): if not self._s: raise last_exception if starttls: - self.sendall(starttls.replace("\\r","\r").replace("\\n","\n")) + self.sendall(starttls.replace("\\r", "\r").replace("\\n", "\n")) self.recvall(timeout=2) def sendall(self, pkt, timeout=None): @@ -54,7 +56,7 @@ def sendall(self, pkt, timeout=None): self._s.settimeout(timeout) self._s.sendall(str(pkt)) - def recvall(self, size=8192*4, timeout=None): + def recvall(self, size=8192 * 4, timeout=None): resp = [] if timeout: self._s.settimeout(timeout) @@ -68,33 +70,58 @@ def recvall(self, size=8192*4, timeout=None): break return SSL(''.join(resp)) + class TLSInfo(object): # https://en.wikipedia.org/wiki/RSA_numbers - RSA_MODULI_KNOWN_FACTORED = (1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139, # RSA-100 - 35794234179725868774991807832568455403003778024228226193532908190484670252364677411513516111204504060317568667, # RSA-110 - 227010481295437363334259960947493668895875336466084780038173258247009162675779735389791151574049166747880487470296548479, # RSA-120 - 114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541, #RSA-129 - 1807082088687404805951656164405905566278102516769401349170127021450056662540244048387341127590812303371781887966563182013214880557, #RSA-130 - 21290246318258757547497882016271517497806703963277216278233383215381949984056495911366573853021918316783107387995317230889569230873441936471, # RSA-140 - 155089812478348440509606754370011861770654545830995430655466945774312632703463465954363335027577729025391453996787414027003501631772186840890795964683, #RSA-150 - 10941738641570527421809707322040357612003732945449205990913842131476349984288934784717997257891267332497625752899781833797076537244027146743531593354333897, # RSA-155 - 2152741102718889701896015201312825429257773588845675980170497676778133145218859135673011059773491059602497907111585214302079314665202840140619946994927570407753, # RSA-160 - 26062623684139844921529879266674432197085925380486406416164785191859999628542069361450283931914514618683512198164805919882053057222974116478065095809832377336510711545759, # RSA-170 - 188198812920607963838697239461650439807163563379417382700763356422988859715234665485319060606504743045317388011303396716199692321205734031879550656996221305168759307650257059, # RSA-576 - 191147927718986609689229466631454649812986246276667354864188503638807260703436799058776201365135161278134258296128109200046702912984568752800330221777752773957404540495707851421041, # RSA-180 - 1907556405060696491061450432646028861081179759533184460647975622318915025587184175754054976155121593293492260464152630093238509246603207417124726121580858185985938946945490481721756401423481, # RSA-190 - 3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609, # RSA-640 - 27997833911221327870829467638722601621070446786955428537560009929326128400107609345671052955360856061822351910951365788637105954482006576775098580557613579098734950144178863178946295187237869221823983, # RSA-200 - 245246644900278211976517663573088018467026787678332759743414451715061600830038587216952208399332071549103626827191679864079776723243005600592035631246561218465817904100131859299619933817012149335034875870551067, # RSA-210 - 74037563479561712828046796097429573142593188889231289084936232638972765034028266276891996419625117843995894330502127585370118968098286733173273108930900552505116877063299072396380786710086096962537934650563796359, #R SA-704 - 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413, # RSA-768 - ) + RSA_MODULI_KNOWN_FACTORED = (1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139, # RSA-100 + # RSA-110 + 35794234179725868774991807832568455403003778024228226193532908190484670252364677411513516111204504060317568667, + # RSA-120 + 227010481295437363334259960947493668895875336466084780038173258247009162675779735389791151574049166747880487470296548479, + # RSA-129 + 114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541, + # RSA-130 + 1807082088687404805951656164405905566278102516769401349170127021450056662540244048387341127590812303371781887966563182013214880557, + # RSA-140 + 21290246318258757547497882016271517497806703963277216278233383215381949984056495911366573853021918316783107387995317230889569230873441936471, + # RSA-150 + 155089812478348440509606754370011861770654545830995430655466945774312632703463465954363335027577729025391453996787414027003501631772186840890795964683, + # RSA-155 + 10941738641570527421809707322040357612003732945449205990913842131476349984288934784717997257891267332497625752899781833797076537244027146743531593354333897, + # RSA-160 + 2152741102718889701896015201312825429257773588845675980170497676778133145218859135673011059773491059602497907111585214302079314665202840140619946994927570407753, + # RSA-170 + 26062623684139844921529879266674432197085925380486406416164785191859999628542069361450283931914514618683512198164805919882053057222974116478065095809832377336510711545759, + # RSA-576 + 188198812920607963838697239461650439807163563379417382700763356422988859715234665485319060606504743045317388011303396716199692321205734031879550656996221305168759307650257059, + # RSA-180 + 191147927718986609689229466631454649812986246276667354864188503638807260703436799058776201365135161278134258296128109200046702912984568752800330221777752773957404540495707851421041, + # RSA-190 + 1907556405060696491061450432646028861081179759533184460647975622318915025587184175754054976155121593293492260464152630093238509246603207417124726121580858185985938946945490481721756401423481, + # RSA-640 + 3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609, + # RSA-200 + 27997833911221327870829467638722601621070446786955428537560009929326128400107609345671052955360856061822351910951365788637105954482006576775098580557613579098734950144178863178946295187237869221823983, + # RSA-210 + 245246644900278211976517663573088018467026787678332759743414451715061600830038587216952208399332071549103626827191679864079776723243005600592035631246561218465817904100131859299619933817012149335034875870551067, + # R SA-704 + 74037563479561712828046796097429573142593188889231289084936232638972765034028266276891996419625117843995894330502127585370118968098286733173273108930900552505116877063299072396380786710086096962537934650563796359, + # RSA-768 + 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413, + ) def __init__(self): self.history = [] self.events = [] - self.info = namedtuple("info", ['client','server']) - self.info.client = namedtuple("client", ['versions','ciphers','compressions', 'preferred_ciphers', 'sessions_established', 'heartbeat', 'extensions' ]) + self.info = namedtuple("info", ['client', 'server']) + self.info.client = namedtuple("client", + ['versions', + 'ciphers', + 'compressions', + 'preferred_ciphers', + 'sessions_established', + 'heartbeat', + 'extensions']) self.info.client.versions = set([]) self.info.client.ciphers = set([]) self.info.client.compressions = set([]) @@ -102,7 +129,14 @@ def __init__(self): self.info.client.sessions_established = 0 self.info.client.heartbeat = None self.info.client.extensions = set([]) - self.info.server = namedtuple("server", ['versions','ciphers','compressions','sessions_established', 'fallback_scsv', 'heartbeat', 'extensions']) + self.info.server = namedtuple("server", + ['versions', + 'ciphers', + 'compressions', + 'sessions_established', + 'fallback_scsv', + 'heartbeat', + 'extensions']) self.info.server.versions = set([]) self.info.server.ciphers = set([]) self.info.server.compressions = set([]) @@ -132,23 +166,23 @@ def __str__(self): server.certificates: %s > - """%(len(self.history), - self.info.client.versions, - self.info.client.ciphers, - self.info.client.compressions, - self.info.client.preferred_ciphers, - self.info.client.sessions_established, - self.info.client.heartbeat, - self.info.server.versions, - self.info.server.ciphers, - self.info.server.compressions, - self.info.server.sessions_established, - self.info.server.fallback_scsv, - self.info.server.heartbeat, - repr(self.info.server.certificates)) + """ % (len(self.history), + self.info.client.versions, + self.info.client.ciphers, + self.info.client.compressions, + self.info.client.preferred_ciphers, + self.info.client.sessions_established, + self.info.client.heartbeat, + self.info.server.versions, + self.info.server.ciphers, + self.info.server.compressions, + self.info.server.sessions_established, + self.info.server.fallback_scsv, + self.info.server.heartbeat, + repr(self.info.server.certificates)) def get_events(self): - events=[] + events = [] events.extend(self.events) for tlsinfo in (self.info.client, self.info.server): # test CRIME - compressions offered? @@ -156,72 +190,97 @@ def get_events(self): if 0 in tmp: tmp.remove(0) if len(tmp): - events.append(("CRIME - %s supports compression"%tlsinfo.__name__,tlsinfo.compressions)) + events.append(("CRIME - %s supports compression" % tlsinfo.__name__, tlsinfo.compressions)) # test RC4 - cipher_namelist = [TLS_CIPHER_SUITES.get(c,"SSLv2_%s"%SSLv2_CIPHER_SUITES.get(c,c)) for c in tlsinfo.ciphers] - - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "SSLV2" in c.upper() and "EXP" in c.upper()] + cipher_namelist = [ + TLS_CIPHER_SUITES.get( + c, "SSLv2_%s" % + SSLv2_CIPHER_SUITES.get( + c, c)) for c in tlsinfo.ciphers] + + tmp = [ + c for c in cipher_namelist if isinstance( + c, + basestring) and "SSLV2" in c.upper() and "EXP" in c.upper()] if tmp: - events.append(("DROWN - SSLv2 with EXPORT ciphers enabled",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "EXP" in c.upper()] + events.append(("DROWN - SSLv2 with EXPORT ciphers enabled", tmp)) + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "EXP" in c.upper()] if tmp: - events.append(("CIPHERS - Export ciphers enabled",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "RC4" in c.upper()] + events.append(("CIPHERS - Export ciphers enabled", tmp)) + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "RC4" in c.upper()] if tmp: - events.append(("CIPHERS - RC4 ciphers enabled",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "MD2" in c.upper()] + events.append(("CIPHERS - RC4 ciphers enabled", tmp)) + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "MD2" in c.upper()] if tmp: - events.append(("CIPHERS - MD2 ciphers enabled",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "MD4" in c.upper()] + events.append(("CIPHERS - MD2 ciphers enabled", tmp)) + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "MD4" in c.upper()] if tmp: - events.append(("CIPHERS - MD4 ciphers enabled",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "MD5" in c.upper()] + events.append(("CIPHERS - MD4 ciphers enabled", tmp)) + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "MD5" in c.upper()] if tmp: - events.append(("CIPHERS - MD5 ciphers enabled",tmp)) + events.append(("CIPHERS - MD5 ciphers enabled", tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "RSA_EXP" in c.upper()] + tmp = [c for c in cipher_namelist if isinstance(c, basestring) and "RSA_EXP" in c.upper()] if tmp: # only check DHE EXPORT for now. we might want to add DH1024 here. - events.append(("FREAK - server supports RSA_EXPORT cipher suites",tmp)) - tmp = [c for c in cipher_namelist if isinstance(c,basestring) and "DHE_" in c.upper() and "EXPORT_" in c.upper()] + events.append(("FREAK - server supports RSA_EXPORT cipher suites", tmp)) + tmp = [ + c for c in cipher_namelist if isinstance( + c, + basestring) and "DHE_" in c.upper() and "EXPORT_" in c.upper()] if tmp: # only check DHE EXPORT for now. we might want to add DH1024 here. - events.append(("LOGJAM - server supports weak DH-Group (512) (DHE_*_EXPORT) cipher suites",tmp)) + events.append(("LOGJAM - server supports weak DH-Group (512) (DHE_*_EXPORT) cipher suites", tmp)) tmp = [ext for ext in tlsinfo.extensions if ext.haslayer(TLSExtSignatureAndHashAlgorithm)] # obvious SLOTH check, does not detect impl. errors that allow md5 even though not announced. # makes only sense for client_hello for sighashext in tmp: - for alg in sighashext[TLSExtSignatureAndHashAlgorithm].algorithms: - if alg.signature_algorithm==TLSSignatureAlgorithm.RSA \ - and alg.hash_algorithm in (TLSHashAlgorithm.MD5, TLSHashAlgorithm.SHA1): - events.append(("SLOTH - %s announces capability of signature/hash algorithm: RSA/%s"%(tlsinfo.__name__,TLS_HASH_ALGORITHMS.get(alg.hash_algorithm)),alg)) + for alg in sighashext[TLSExtSignatureAndHashAlgorithm].algs: + if alg.sig_alg == TLSSignatureAlgorithm.RSA \ + and alg.hash_alg in (TLSHashAlgorithm.MD5, TLSHashAlgorithm.SHA1): + events.append( + ("SLOTH - %s announces capability of signature/hash algorithm: RSA/%s" % + (tlsinfo.__name__, + TLS_HASH_ALGORITHMS.get( + alg.hash_alg)), + alg)) try: for certlist in tlsinfo.certificates: for cert in certlist.certificates: - pubkey = x509_extract_pubkey_from_der(str(cert.data)) + keystore = tlsk.RSAKeystore.from_der_certificate(str(cert.data)) + pubkey = keystore.public pubkey_size = pubkey.size() + 1 if pubkey_size < 2048: - events.append(("INSUFFICIENT SERVER CERT PUBKEY SIZE - 2048 >= %d bits"%pubkey_size,cert)) + events.append( + ("INSUFFICIENT SERVER CERT PUBKEY SIZE - 2048 >= %d bits" % + pubkey_size, cert)) if pubkey_size % 2048 != 0: - events.append(("SUSPICIOUS SERVER CERT PUBKEY SIZE - %d not a multiple of 2048 bits"%pubkey_size,cert)) + events.append( + ("SUSPICIOUS SERVER CERT PUBKEY SIZE - %d not a multiple of 2048 bits" % + pubkey_size, cert)) if pubkey.n in self.RSA_MODULI_KNOWN_FACTORED: - events.append(("SERVER CERT PUBKEY FACTORED - trivial private_key recovery possible due to known factors n = p x q. See https://en.wikipedia.org/wiki/RSA_numbers | grep %s"%pubkey.n,cert)) + events.append( + ("SERVER CERT PUBKEY FACTORED - trivial private_key recovery possible due to known factors n = p x q. See https://en.wikipedia.org/wiki/RSA_numbers | grep %s" % + pubkey.n, + cert)) except AttributeError: pass # tlsinfo.client has no attribute certificates if TLSVersion.SSL_2_0 in tlsinfo.versions: - events.append(("PROTOCOL VERSION - SSLv2 supported ",tlsinfo.versions)) + events.append(("PROTOCOL VERSION - SSLv2 supported ", tlsinfo.versions)) if TLSVersion.SSL_3_0 in tlsinfo.versions: - events.append(("PROTOCOL VERSION - SSLv3 supported ",tlsinfo.versions)) + events.append(("PROTOCOL VERSION - SSLv3 supported ", tlsinfo.versions)) if TLSHeartbeatMode.PEER_ALLOWED_TO_SEND == tlsinfo.heartbeat: - events.append(("HEARTBEAT - enabled (non conclusive heartbleed) ",tlsinfo.versions)) + events.append(("HEARTBEAT - enabled (non conclusive heartbleed) ", tlsinfo.versions)) - if self.info.server.fallback_scsv==True: - events.append(("DOWNGRADE / POODLE - FALLBACK_SCSV honored (alert.inappropriate_fallback seen)",self.info.server.fallback_scsv)) + if self.info.server.fallback_scsv: + events.append( + ("DOWNGRADE / POODLE - FALLBACK_SCSV honored (alert.inappropriate_fallback seen)", + self.info.server.fallback_scsv)) return events @@ -259,7 +318,6 @@ def _process(self, pkt, client=None): elif record.haslayer(SSLv2ClientHello): tlsinfo.ciphers.add(record[SSLv2ClientHello].cipher_suites) - if record.haslayer(TLSServerHello): tlsinfo.ciphers.add(record[TLSServerHello].cipher_suite) tlsinfo.compressions.add(record[TLSServerHello].compression_method) @@ -273,18 +331,21 @@ def _process(self, pkt, client=None): tlsinfo.certificates.add(record[TLSCertificateList]) if record.haslayer(TLSFinished): - tlsinfo.session.established +=1 + tlsinfo.session.established += 1 if record.haslayer(TLSHandshake): tlsinfo.versions.add(pkt[TLSRecord].version) elif record.haslayer(SSLv2ServerHello): tlsinfo.versions.add(pkt[SSLv2Record].version) - if not client and record.haslayer(TLSAlert) and record[TLSAlert].description==TLSAlertDescription.INAPPROPRIATE_FALLBACK: - tlsinfo.fallback_scsv=True + if not client and record.haslayer( + TLSAlert) and record[TLSAlert].description == TLSAlertDescription.INAPPROPRIATE_FALLBACK: + tlsinfo.fallback_scsv = True # track packet self.history.append(pkt) + class TLSScanner(object): + def __init__(self, workers=10): self.workers = workers self.capabilities = TLSInfo() @@ -296,16 +357,17 @@ def scan(self, target, starttls=None): def sniff(self, target=None, iface=None): def _process(pkt): - match_ip = pkt.haslayer(IP) and (pkt[IP].src==target[0] or pkt[IP].dst==target[0]) if target else True - match_port = pkt.haslayer(TCP) and (pkt[TCP].sport==target[1] or pkt[TCP].dport==target[1]) if len(target)==2 else True + match_ip = pkt.haslayer(IP) and (pkt[IP].src == target[0] or pkt[IP].dst == target[0]) if target else True + match_port = pkt.haslayer(TCP) and ( + pkt[TCP].sport == target[1] or pkt[TCP].dport == target[1]) if len(target) == 2 else True if match_ip and match_port: self.capabilities.insert(pkt, client=False) events = self.capabilities.get_events() # misuse get_events :/ if events: - strconn = {'src':None, - 'dst':None, - 'sport':None, - 'dport':None} + strconn = {'src': None, + 'dst': None, + 'sport': None, + 'dport': None} if pkt.haslayer(IP): strconn['src'] = pkt[IP].src @@ -318,17 +380,17 @@ def _process(pkt): print ("* EVENT - " + "\n* EVENT - ".join(e[0] for e in events)) return if iface: - conf.iface=iface + conf.iface = iface while True: bpf = None if len(target): - bpf = "host %s"%target[0] - if len(target)==2: - bpf += " and tcp port %d"%target[1] + bpf = "host %s" % target[0] + if len(target) == 2: + bpf += " and tcp port %d" % target[1] sniff(filter=bpf, - prn=_process, - store=0, - timeout=3) + prn=_process, + store=0, + timeout=3) def _scan_poodle2(self, target, starttls=None, version=TLSVersion.TLS_1_0): """taken from poodle2_padding_check""" @@ -341,16 +403,23 @@ def modify_padding(crypto_container): t = TCPConnection(target, starttls=starttls) ts = TLSSocket(t._s, client=True) tls_do_handshake(ts, version, TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA) - ts.sendall(to_raw(TLSPlaintext(data="GET / HTTP/1.1\r\nHOST: %s\r\n\r\n" % target[0]), ts.tls_ctx, pre_encrypt_hook=modify_padding)) + ts.sendall( + to_raw( + TLSPlaintext( + data="GET / HTTP/1.1\r\nHOST: %s\r\n\r\n" % + target[0]), + ts.tls_ctx, + pre_encrypt_hook=modify_padding)) r = ts.recvall() if len(r.records) == 0: - self.capabilities.events.append(("Poodle2 - not vulnerable, but implementation does not send a BAD_RECORD_MAC alert",r)) + self.capabilities.events.append( + ("Poodle2 - not vulnerable, but implementation does not send a BAD_RECORD_MAC alert", r)) elif r.haslayer(TLSAlert) and r[TLSAlert].description == TLSAlertDescription.BAD_RECORD_MAC: # not vulnerable pass else: - self.capabilities.events.append(("Poodle2 - vulnerable",r)) + self.capabilities.events.append(("Poodle2 - vulnerable", r)) except (socket.error, NotImplementedError) as se: print (repr(se)) @@ -359,7 +428,9 @@ def modify_padding(crypto_container): def _scan_compressions(self, target, starttls=None, compression_list=TLS_COMPRESSION_METHODS.keys()): for comp in compression_list: # prepare pkt - pkt = TLSRecord()/TLSHandshake()/TLSClientHello(version=TLSVersion.TLS_1_1, cipher_suites=range(0xfe)[::-1], compression_methods=comp) + pkt = TLSRecord() / TLSHandshake() / TLSClientHello(version=TLSVersion.TLS_1_1, + cipher_suites=range(0xfe)[::-1], + compression_methods=comp) # connect try: t = TCPConnection(target, starttls=starttls) @@ -369,8 +440,8 @@ def _scan_compressions(self, target, starttls=None, compression_list=TLS_COMPRES except socket.error as se: print (repr(se)) - def _check_cipher(self, target, cipher_id, starttls=None,version=TLSVersion.TLS_1_0): - pkt = TLSRecord(version=version)/TLSHandshake()/TLSClientHello(version=version, cipher_suites=[cipher_id]) + def _check_cipher(self, target, cipher_id, starttls=None, version=TLSVersion.TLS_1_0): + pkt = TLSRecord(version=version) / TLSHandshake() / TLSClientHello(version=version, cipher_suites=[cipher_id]) try: t = TCPConnection(target, starttls=starttls) t.sendall(pkt) @@ -380,18 +451,35 @@ def _check_cipher(self, target, cipher_id, starttls=None,version=TLSVersion.TLS return None return resp - def _scan_accepted_ciphersuites(self, target, starttls=None, cipherlist=TLS_CIPHER_SUITES.keys(), version=TLSVersion.TLS_1_0): + def _scan_accepted_ciphersuites( + self, + target, + starttls=None, + cipherlist=TLS_CIPHER_SUITES.keys(), + version=TLSVersion.TLS_1_0): with concurrent.futures.ThreadPoolExecutor(max_workers=self.workers) as executor: - tasks = [executor.submit(self._check_cipher, target, cipher_id, starttls, version) for cipher_id in cipherlist] + tasks = [ + executor.submit( + self._check_cipher, + target, + cipher_id, + starttls, + version) for cipher_id in cipherlist] for future in concurrent.futures.as_completed(tasks): self.capabilities.insert(future.result(), client=False) - - def _scan_supported_protocol_versions(self, target, starttls=None, versionlist=((k,v) for k,v in TLS_VERSIONS.iteritems() if v.startswith("TLS_") or v.startswith("SSL_"))): + def _scan_supported_protocol_versions( + self, + target, + starttls=None, + versionlist=( + (k, + v) for k, + v in TLS_VERSIONS.iteritems() if v.startswith("TLS_") or v.startswith("SSL_"))): for magic, name in versionlist: - pkt = TLSRecord(version=magic)/TLSHandshake()/TLSClientHello(version=magic, - cipher_suites=range(0xfe)[::-1], - extensions=[TLSExtension()/TLSExtHeartbeat(mode=TLSHeartbeatMode.PEER_ALLOWED_TO_SEND)]) + pkt = TLSRecord(version=magic) / TLSHandshake() / TLSClientHello(version=magic, + cipher_suites=range(0xfe)[::-1], + extensions=[TLSExtension() / TLSExtHeartbeat(mode=TLSHeartbeatMode.PEER_ALLOWED_TO_SEND)]) try: # connect t = TCPConnection(target, starttls=starttls) @@ -401,8 +489,8 @@ def _scan_supported_protocol_versions(self, target, starttls=None, versionlist=( except socket.error as se: print (repr(se)) - def _check_cipher_sslv2(self, target, cipher_id, starttls=None, version=TLSVersion.SSL_2_0): - pkt = SSLv2Record()/SSLv2ClientHello(cipher_suites=[cipher_id],challenge='A'*16,session_id='') + def _check_cipher_sslv2(self, target, cipher_id, starttls=None, version=TLSVersion.SSL_2_0): + pkt = SSLv2Record() / SSLv2ClientHello(cipher_suites=[cipher_id], challenge='A' * 16, session_id='') try: t = TCPConnection(target, starttls=starttls) t.sendall(pkt) @@ -412,50 +500,64 @@ def _check_cipher_sslv2(self, target, cipher_id, starttls=None, version=TLSVers return None return resp - def _scan_accepted_ciphersuites_ssl2(self, target, starttls=None, cipherlist=SSLv2_CIPHER_SUITES.keys(), version=TLSVersion.SSL_2_0): + def _scan_accepted_ciphersuites_ssl2( + self, + target, + starttls=None, + cipherlist=SSLv2_CIPHER_SUITES.keys(), + version=TLSVersion.SSL_2_0): with concurrent.futures.ThreadPoolExecutor(max_workers=self.workers) as executor: - tasks = [executor.submit(self._check_cipher_sslv2, target, cipher_id, starttls, version) for cipher_id in cipherlist] + tasks = [ + executor.submit( + self._check_cipher_sslv2, + target, + cipher_id, + starttls, + version) for cipher_id in cipherlist] for future in concurrent.futures.as_completed(tasks): self.capabilities.insert(future.result(), client=False) def _scan_scsv(self, target, starttls=None): - pkt = TLSRecord(version=TLSVersion.TLS_1_1)/TLSHandshake()/TLSClientHello(version=TLSVersion.TLS_1_0, cipher_suites=[TLSCipherSuite.FALLBACK_SCSV]+range(0xfe)[::-1]) + pkt = TLSRecord(version=TLSVersion.TLS_1_1) / TLSHandshake() / TLSClientHello(version=TLSVersion.TLS_1_0, + cipher_suites=[TLSCipherSuite.FALLBACK_SCSV] + range(0xfe)[::-1]) # connect try: t = TCPConnection(target, starttls=starttls) t.sendall(pkt) resp = t.recvall(timeout=2) self.capabilities.insert(resp, client=False) - if not (resp.haslayer(TLSAlert) and resp[TLSAlert].description==TLSAlertDescription.INAPPROPRIATE_FALLBACK): - self.capabilities.events.append(("DOWNGRADE / POODLE - FALLBACK_SCSV - not honored",resp)) + if not (resp.haslayer(TLSAlert) and resp[TLSAlert].description == + TLSAlertDescription.INAPPROPRIATE_FALLBACK): + self.capabilities.events.append(("DOWNGRADE / POODLE - FALLBACK_SCSV - not honored", resp)) except socket.error as se: print (repr(se)) def _scan_heartbleed(self, target, starttls=None, version=TLSVersion.TLS_1_0, payload_length=20): try: t = TCPConnection(target, starttls=starttls) - pkt = TLSRecord(version=version)/TLSHandshake()/TLSClientHello(version=version) + pkt = TLSRecord(version=version) / TLSHandshake() / TLSClientHello(version=version) t.sendall(pkt) resp = t.recvall(timeout=0.5) - pkt = TLSRecord(version=version)/TLSHeartBeat(length=2**14-1,data='bleed...') + pkt = TLSRecord(version=version) / TLSHeartBeat(length=2**14 - 1, data='bleed...') t.sendall(str(pkt)) resp = t.recvall(timeout=0.5) - if resp.haslayer(TLSHeartBeat) and resp[TLSHeartBeat].length>8: - self.capabilities.events.append(("HEARTBLEED - vulnerable",resp)) + if resp.haslayer(TLSHeartBeat) and resp[TLSHeartBeat].length > 8: + self.capabilities.events.append(("HEARTBLEED - vulnerable", resp)) except socket.error as se: print (repr(se)) return None return resp def _scan_secure_renegotiation(self, target, starttls=None, version=TLSVersion.TLS_1_0, payload_length=20): - #todo: also test EMPTY_RENEGOTIATION_INFO_SCSV + # todo: also test EMPTY_RENEGOTIATION_INFO_SCSV try: t = TCPConnection(target, starttls=starttls) - pkt = TLSRecord(version=version)/TLSHandshake()/TLSClientHello(version=version, extensions=TLSExtension()/TLSExtRenegotiationInfo()) + pkt = TLSRecord(version=version) / TLSHandshake() / \ + TLSClientHello(version=version, extensions=TLSExtension() / TLSExtRenegotiationInfo()) t.sendall(pkt) resp = t.recvall(timeout=0.5) if resp.haslayer(TLSExtRenegotiationInfo): - self.capabilities.events.append(("TLS EXTENSION SECURE RENEGOTIATION - not supported",resp)) + self.capabilities.events.append(("TLS EXTENSION SECURE RENEGOTIATION - not supported", resp)) except socket.error as se: print (repr(se)) return None @@ -464,7 +566,7 @@ def _scan_secure_renegotiation(self, target, starttls=None, version=TLSVersion.T def main(): print (__doc__) - if len(sys.argv)<=3: + if len(sys.argv) <= 3: print ("USAGE: [starttls] [num_worker] [interface]") print (" mode ... client | sniff") print (" starttls ... starttls keyword e.g. 'starttls\\n' or 'ssl\\n'") @@ -473,46 +575,49 @@ def main(): print (" * %s" % i) exit(1) mode = sys.argv[1] - starttls = sys.argv[4] if len(sys.argv)>4 else None + starttls = sys.argv[4] if len(sys.argv) > 4 else None host = sys.argv[2] port = int(sys.argv[3]) - num_workers = 10 if not len(sys.argv)>5 else int(sys.argv[5]) - iface = "eth0" if not len(sys.argv)>6 else sys.argv[6] + num_workers = 10 if not len(sys.argv) > 5 else int(sys.argv[5]) + iface = "eth0" if not len(sys.argv) > 6 else sys.argv[6] scanner = TLSScanner(workers=num_workers) - if mode=="sniff": + if mode == "sniff": print ("[*] [passive] Scanning in 'sniff' mode for %s on %s..." % (repr((host, port)), iface)) - scanner.sniff((host,port),iface=iface) + scanner.sniff((host, port), iface=iface) else: print ("[*] [active] Scanning with %s parallel threads..." % num_workers) t_start = time.time() - scanner.scan((host,port), starttls=starttls) + scanner.scan((host, port), starttls=starttls) print ("\n") print ("[*] Capabilities (Debug)") print (scanner.capabilities) print ("[*] supported ciphers: %s/%s" % ( - len(scanner.capabilities.info.server.ciphers), len(TLS_CIPHER_SUITES) + len(SSLv2_CIPHER_SUITES))) + len(scanner.capabilities.info.server.ciphers), len(TLS_CIPHER_SUITES) + len(SSLv2_CIPHER_SUITES))) print (" * " + "\n * ".join( ("%s (0x%0.4x)" % (TLS_CIPHER_SUITES.get(c, "SSLv2_%s" % SSLv2_CIPHER_SUITES.get(c, c)), c) for c in scanner.capabilities.info.server.ciphers))) print ("") print ( - "[*] supported protocol versions: %s/%s" % (len(scanner.capabilities.info.server.versions), len(TLS_VERSIONS))) + "[*] supported protocol versions: %s/%s" % + (len( + scanner.capabilities.info.server.versions), + len(TLS_VERSIONS))) print (" * " + "\n * ".join( ("%s (0x%0.4x)" % (TLS_VERSIONS.get(c, c), c) for c in scanner.capabilities.info.server.versions))) print ("") print ("[*] supported compressions methods: %s/%s" % ( - len(scanner.capabilities.info.server.compressions), len(TLS_COMPRESSION_METHODS))) + len(scanner.capabilities.info.server.compressions), len(TLS_COMPRESSION_METHODS))) print (" * " + "\n * ".join(("%s (0x%0.4x)" % (TLS_COMPRESSION_METHODS.get(c, c), c) for c in scanner.capabilities.info.server.compressions))) print ("") events = scanner.capabilities.get_events() print ("[*] Events: %s" % len(events)) print ("* EVENT - " + "\n* EVENT - ".join(e[0] for e in events)) - t_diff = time.time()-t_start + t_diff = time.time() - t_start print ("") print ("Scan took: %ss" % t_diff) -if __name__=="__main__": +if __name__ == "__main__": main() diff --git a/examples/server_rsa.py b/examples/server_rsa.py index d83ef38..b1e811a 100644 --- a/examples/server_rsa.py +++ b/examples/server_rsa.py @@ -5,7 +5,7 @@ import socket import sys -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) try: # This import works from the project directory from scapy_ssl_tls.ssl_tls import * @@ -25,7 +25,7 @@ tls_socket = TLSSocket(socket_, client=False) tls_socket.bind(("", 8443)) tls_socket.listen(1) -tls_socket.tls_ctx.rsa_load_keys_from_file(os.path.join(basedir, "tests/integration/keys/key.pem")) +tls_socket.tls_ctx.server_ctx.load_rsa_keys_from_file(os.path.join(basedir, "tests/integration/keys/key.pem")) c_socket, _ = tls_socket.accept() r = c_socket.recvall() @@ -47,5 +47,3 @@ c_socket.sendall(to_raw(TLSAlert(), c_socket.tls_ctx)) print(c_socket.tls_ctx) - - diff --git a/examples/sessionctx_sniffer.py b/examples/sessionctx_sniffer.py index 3325666..3859abd 100644 --- a/examples/sessionctx_sniffer.py +++ b/examples/sessionctx_sniffer.py @@ -11,7 +11,8 @@ """ from __future__ import print_function -import sys, os +import sys +import os try: from scapy.all import * except ImportError: @@ -31,7 +32,9 @@ import socket + class L4TcpReassembler(object): + """ WARNING - this is not a valid TCP Stream Reassembler. It is not L5+ aware and only operates at L4 Only works for the assumption that a consecutive stream will be split in segments of the max segment size (mss). It will concat segments == mss until a segment < mss is found. it will then spit out a reassembled (fake) TCP packet with the full payload. @@ -47,21 +50,22 @@ class TCPFlags: CWR = 0x80 class TCPStream(object): + def __init__(self, pkt): self.pktlist = [] self.stream_id = L4TcpReassembler.TCPStream.stream_id(pkt) - + if not pkt[TCP].flags & L4TcpReassembler.TCPFlags.SYN: - raise Exception("NOT THE BEGINNING OF A STREAM: %s"%repr(self.stream_id)) + raise Exception("NOT THE BEGINNING OF A STREAM: %s" % repr(self.stream_id)) self.syn = pkt[TCP] - self.syn.payload=None # strip payload + self.syn.payload = None # strip payload self.initial_seq = pkt[TCP].seq self.last_seq = self.initial_seq self.relative_seq = 0 - self.mss = ( option[1] for option in pkt[TCP].options if option[0]=="MSS").next() + self.mss = (option[1] for option in pkt[TCP].options if option[0] == "MSS").next() @staticmethod def stream_id(pkt): @@ -73,17 +77,18 @@ def process(self, pkt): if payload_size < self.mss: # flush pktlist as [pkt stack, current_pkt] - if len(self.pktlist)>1: + if len(self.pktlist) > 1: # create fake packet p_reassembled = pkt del p_reassembled[IP].len del p_reassembled[IP].chksum del p_reassembled[TCP].chksum #p_reassembled.name = "TCPReassembled" - p_reassembled[TCP].payload = ''.join(str(p[TCP].payload) for p in self.pktlist) + str(p_reassembled[TCP].payload) + p_reassembled[TCP].payload = ''.join(str(p[TCP].payload) + for p in self.pktlist) + str(p_reassembled[TCP].payload) p_reassembled[TCP] = TCP(str(p_reassembled[TCP])) # force re-dissect - self.pktlist=[] + self.pktlist = [] return p_reassembled # otherwise just return current pkt return pkt @@ -92,7 +97,13 @@ def process(self, pkt): return None def __repr__(self, *args, **kwargs): - return "<>"%(repr(self.stream_id),self.mss,self.initial_seq, self.last_seq, self.last_seq-self.initial_seq,self.pktlist ) + return "<>" % (repr( + self.stream_id), + self.mss, + self.initial_seq, + self.last_seq, + self.last_seq - self.initial_seq, + self.pktlist) def __init__(self): # track streams @@ -104,9 +115,9 @@ def get_stream(self, pkt): if not stream_obj: try: stream_obj = L4TcpReassembler.TCPStream(pkt) - self.streams[stream_id]=stream_obj + self.streams[stream_id] = stream_obj except: - pass # not a valid stream, or in the middle of a stream + pass # not a valid stream, or in the middle of a stream return stream_obj def reassemble(self, pktlist): @@ -129,12 +140,15 @@ def reassemble(self, pktlist): # assume stream complete yield p + class Sniffer(object): + """ Sniffer() .rdpcap(pcap) or .sniff() """ + def __init__(self): self.ssl_session_map = {} @@ -145,75 +159,78 @@ def _create_context(self, target, keyfile=None): session = ssl_tls_crypto.TLSSessionCtx() if keyfile: print ("* load servers privatekey for ciphertext decryption (RSA key only): %s" % keyfile) - session.rsa_load_keys_from_file(keyfile) + session.server_ctx.load_rsa_keys_from_file(keyfile) - session.printed=False - self.ssl_session_map[target]=session + session.printed = False + self.ssl_session_map[target] = session else: print ("!! missing private key") def process_ssl(self, p): - if not p.haslayer(SSL): - return - session = self.ssl_session_map.get((p[IP].dst,p[TCP].dport)) or self.ssl_session_map.get((p[IP].src,p[TCP].sport)) - if not session: - print ( + if not p.haslayer(SSL): + return + session = self.ssl_session_map.get( + (p[IP].dst, p[TCP].dport)) or self.ssl_session_map.get( + (p[IP].src, p[TCP].sport)) + if not session: + print ( "| %-16s:%-5d => %-16s:%-5d | %s" % (p[IP].src, p[TCP].sport, p[IP].dst, p[TCP].dport, repr(p[SSL]))) - return - p_ssl = p[SSL] - source = (p[IP].src,p[TCP].sport) + return + p_ssl = p[SSL] + source = (p[IP].src, p[TCP].sport) - if p_ssl.haslayer(SSLv2Record): - print ("SSLv2 not supported - skipping..", repr(p)) - return + if p_ssl.haslayer(SSLv2Record): + print ("SSLv2 not supported - skipping..", repr(p)) + return - if p_ssl.haslayer(TLSServerHello): - session.printed=False - session.crypto.session.master_secret=None - session.match_server = source - #reset the session and print it next time - if p_ssl.haslayer(TLSClientHello): - session.match_client = source + if p_ssl.haslayer(TLSServerHello): + session.printed = False + session.crypto.session.master_secret = None + session.match_server = source + # reset the session and print it next time + if p_ssl.haslayer(TLSClientHello): + session.match_client = source - session.insert(p_ssl) + session.insert(p_ssl) - if session.crypto.session.master_secret and session.printed==False: - print (repr(session)) - session.printed = True + if session.crypto.session.master_secret and session.printed == False: + print (repr(session)) + session.printed = True - print ( + print ( "| %-16s:%-5d => %-16s:%-5d | %s" % (p[IP].src, p[TCP].sport, p[IP].dst, p[TCP].dport, repr(p_ssl))) - if p.haslayer(TLSCiphertext) or (p.haslayer(TLSAlert) and p.haslayer(Raw)): - if source == session.match_client: - session.set_mode(server=True) - elif source == session.match_server: - session.set_mode(client=True) - else: - Exception("src packet mismatch: %s"%repr(source)) - try: - p = SSL(str(p_ssl),ctx=session) - print ("|-> %-48s | %s" % ("decrypted record", repr(p))) - except ValueError as ve: - print ("Exception:", repr(ve)) + if p.haslayer(TLSCiphertext) or (p.haslayer(TLSAlert) and p.haslayer(Raw)): + if source == session.match_client: + session.set_mode(server=True) + elif source == session.match_server: + session.set_mode(client=True) + else: + Exception("src packet mismatch: %s" % repr(source)) + try: + p = SSL(str(p_ssl), ctx=session) + print ("|-> %-48s | %s" % ("decrypted record", repr(p))) + except ValueError as ve: + print ("Exception:", repr(ve)) def sniff(self, target, keyfile=None, iface=None): self._tcp_reassembler = L4TcpReassembler() + def reassemble(p): for rp in (pkt for pkt in self._tcp_reassembler.reassemble([p]) if pkt.haslayer(SSL)): self.process_ssl(rp) if iface: - conf.iface=iface - self._create_context(target=target,keyfile=keyfile) + conf.iface = iface + self._create_context(target=target, keyfile=keyfile) while True: - sniff(filter="host %s and tcp port %d"%(target[0],target[1]),prn=reassemble,store=0,timeout=3) + sniff(filter="host %s and tcp port %d" % (target[0], target[1]), prn=reassemble, store=0, timeout=3) def rdpcap(self, target, keyfile, pcap): - self._create_context(target=target,keyfile=keyfile) + self._create_context(target=target, keyfile=keyfile) for p in (pkt for pkt in L4TcpReassembler().reassemble(rdpcap(pcap)) if pkt.haslayer(SSL)): self.process_ssl(p) -def main(target,pcap=None, iface=None, keyfile=None): +def main(target, pcap=None, iface=None, keyfile=None): sniffer = Sniffer() if pcap: print ("* pcap ready!") @@ -224,8 +241,8 @@ def main(target,pcap=None, iface=None, keyfile=None): # sniffer mainloop sniffer.sniff(target=target, keyfile=keyfile, iface=iface) -if __name__=="__main__": - if len(sys.argv)<=3: +if __name__ == "__main__": + if len(sys.argv) <= 3: print ("USAGE: ") print ("\navailable interfaces:") for i in get_if_list(): @@ -233,19 +250,19 @@ def main(target,pcap=None, iface=None, keyfile=None): print ("* default") exit(1) - pcap=None - iface=None - keyfile=None - if len(sys.argv)>3: + pcap = None + iface = None + keyfile = None + if len(sys.argv) > 3: if os.path.isfile(sys.argv[3]): - pcap=sys.argv[3] + pcap = sys.argv[3] elif sys.argv[3] in get_if_list(): - iface=sys.argv[3] + iface = sys.argv[3] else: raise Exception("Unknown interface or invalid path to pcap.") - if len(sys.argv)>4: + if len(sys.argv) > 4: if not os.path.isfile(sys.argv[4]): - raise Exception("PrivateKey File not Found! %s"%sys.argv[4]) + raise Exception("PrivateKey File not Found! %s" % sys.argv[4]) keyfile = sys.argv[4] - main((sys.argv[1],int(sys.argv[2])), iface=iface, pcap=pcap, keyfile=keyfile) + main((sys.argv[1], int(sys.argv[2])), iface=iface, pcap=pcap, keyfile=keyfile) diff --git a/examples/sslv2_client_hello_valid.py b/examples/sslv2_client_hello_valid.py index 01165ab..89806cb 100644 --- a/examples/sslv2_client_hello_valid.py +++ b/examples/sslv2_client_hello_valid.py @@ -18,26 +18,28 @@ import socket -if __name__=="__main__": - if len(sys.argv)<=2: +if __name__ == "__main__": + if len(sys.argv) <= 2: print ("USAGE: ") exit(1) - target = (sys.argv[1],int(sys.argv[2])) + target = (sys.argv[1], int(sys.argv[2])) # create tcp socket - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(target) # create SSLv2 Handhsake / Client Hello packet - p = SSLv2Record()/SSLv2ClientHello(cipher_suites=SSLv2_CIPHER_SUITES.keys(),challenge='a'*16,session_id='a'*16) + p = SSLv2Record() / SSLv2ClientHello(cipher_suites=SSLv2_CIPHER_SUITES.keys(), + challenge='a' * 16, + session_id='a' * 16) p.show() SSL(str(p)).show() print ("sending TLS payload") s.sendall(str(p)) - resp = s.recv(8*1024) + resp = s.recv(8 * 1024) print ("received, %s" % repr(resp)) SSL(resp).show() diff --git a/examples/tls_client_automata.py b/examples/tls_client_automata.py index 21cceee..791d342 100644 --- a/examples/tls_client_automata.py +++ b/examples/tls_client_automata.py @@ -18,31 +18,31 @@ from scapy.layers.ssl_tls_automata import TLSClientAutomata from scapy.layers.ssl_tls import * -if __name__=='__main__': +if __name__ == '__main__': #conf.prog.dot = r'"path_to_graphviz/dot"' logging.basicConfig(level=logging.DEBUG) log_interactive.setLevel(1) - if len(sys.argv)>2: - target = (sys.argv[1],int(sys.argv[2])) + if len(sys.argv) > 2: + target = (sys.argv[1], int(sys.argv[2])) else: target = ("127.0.0.1", 8443) TLSClientAutomata.graph() auto_cli = TLSClientAutomata(debug=9, - target=target, - tls_version="TLS_1_1", - cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, - TLSCipherSuite.RSA_WITH_RC4_128_SHA, - TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA, - TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA], - request="GET / HTTP/1.1\r\nHOST: localhost\r\n\r\n") - - logger.debug("registered states: %s"%auto_cli.states) # all registered states - logger.debug("registered actions: %s"%auto_cli.actions) # all registered actions - logger.debug("pkt-to-state-mapping: %s"%auto_cli.STATES)# mapped pkts to states - logger.debug("pkt-to-action-mapping: %s"%auto_cli.ACTIONS)# mapped pkts to actions + target=target, + tls_version="TLS_1_1", + cipher_suites=[TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, + TLSCipherSuite.RSA_WITH_RC4_128_SHA, + TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA, + TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA], + request="GET / HTTP/1.1\r\nHOST: localhost\r\n\r\n") + + logger.debug("registered states: %s" % auto_cli.states) # all registered states + logger.debug("registered actions: %s" % auto_cli.actions) # all registered actions + logger.debug("pkt-to-state-mapping: %s" % auto_cli.STATES) # mapped pkts to states + logger.debug("pkt-to-action-mapping: %s" % auto_cli.ACTIONS) # mapped pkts to actions # uncomment next lines to hook into the 'send_server_hello' condition. ''' @@ -51,7 +51,7 @@ def jump_to_random_state(*args, **kwargs): next_state = random.choice(auto_cli.states.keys()) raw_input(" **** -------------> override state, random pick: %s"%next_state) raise getattr(auto_cli,next_state)() - + auto_cli.register_callback(auto_cli.ACTIONS[TLSFinished], jump_to_random_state) ''' print (auto_cli.run()) diff --git a/examples/tls_server_automata.py b/examples/tls_server_automata.py index 51a1082..b0d4a2e 100644 --- a/examples/tls_server_automata.py +++ b/examples/tls_server_automata.py @@ -18,49 +18,49 @@ from scapy.layers.ssl_tls_automata import TLSServerAutomata from scapy.layers.ssl_tls import * -if __name__=='__main__': +if __name__ == '__main__': #conf.prog.dot = r'"path_to_graphviz/dot"' logging.basicConfig(level=logging.DEBUG) log_interactive.setLevel(1) - - if len(sys.argv)>2: - target = (sys.argv[1],int(sys.argv[2])) + + if len(sys.argv) > 2: + target = (sys.argv[1], int(sys.argv[2])) else: target = ("127.0.0.1", 8443) - - server_pem = sys.argv[3] if len(sys.argv)>3 else "../tests/files/openssl_1_0_1_f_server.pem" - + + server_pem = sys.argv[3] if len(sys.argv) > 3 else "../tests/files/openssl_1_0_1_f_server.pem" + TLSServerAutomata.graph() - logger.info("using certificate/keyfile: %s"%server_pem) - with open(server_pem,'r') as f: + logger.info("using certificate/keyfile: %s" % server_pem) + with open(server_pem, 'r') as f: pemcert = f.read() auto_srv = TLSServerAutomata(debug=9, - bind=target, - pemcert=pemcert, - cipher_suite=TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, - response="HTTP/1.1 200 OK\r\n\r\n") - - logger.debug("registered states: %s"%auto_srv.states) # all registered states - logger.debug("registered actions: %s"%auto_srv.actions) # all registered actions - logger.debug("pkt-to-state-mapping: %s"%auto_srv.STATES)# mapped pkts to states - logger.debug("pkt-to-action-mapping: %s"%auto_srv.ACTIONS)# mapped pkts to actions - + bind=target, + pemcert=pemcert, + cipher_suite=TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, + response="HTTP/1.1 200 OK\r\n\r\n") + + logger.debug("registered states: %s" % auto_srv.states) # all registered states + logger.debug("registered actions: %s" % auto_srv.actions) # all registered actions + logger.debug("pkt-to-state-mapping: %s" % auto_srv.STATES) # mapped pkts to states + logger.debug("pkt-to-action-mapping: %s" % auto_srv.ACTIONS) # mapped pkts to actions + # uncomment next line to hook into the 'send_server_hello' condition. ''' def jump_to_server_hello_done(*args, **kwargs): raw_input(" **** -------------> override state, directly jump to SERVER_CERTIFICATES_SENT aka. SERVER_HELLO_DONE") raise auto_srv.SERVER_CERTIFICATES_SENT() - + def jump_to_random_state(*args, **kwargs): import random next_state = random.choice(auto_srv.states.keys()) raw_input(" **** -------------> override state, random pick: %s"%next_state) raise getattr(auto_srv,next_state)() - + def jump_to_bla(*args, **kwargs): raise auto_srv.WAIT_FOR_CLIENT_CONNECTION() - + auto_srv.register_callback(auto_srv.ACTIONS[TLSFinished], jump_to_server_hello_done) auto_srv.register_callback(auto_srv.ACTIONS[TLSFinished], jump_to_random_state) ''' diff --git a/scapy_ssl_tls/pkcs7.py b/scapy_ssl_tls/pkcs7.py index d740418..95aa9ce 100644 --- a/scapy_ssl_tls/pkcs7.py +++ b/scapy_ssl_tls/pkcs7.py @@ -5,7 +5,9 @@ import binascii import StringIO + class PKCS7Encoder(object): + """ RFC 2315: PKCS#7 page 21 Some content-encryption algorithms assume the @@ -30,10 +32,11 @@ class PKCS7Encoder(object): padding method is well-defined if and only if k < 256; methods for larger k are an open issue for further study. """ + def __init__(self, k=16): self.k = k - ## @param text The padded text for which the padding is to be removed. + # @param text The padded text for which the padding is to be removed. # @exception ValueError Raised when the input padding is missing or corrupt. def decode(self, text): """ @@ -47,7 +50,7 @@ def decode(self, text): l = nl - val return text[:l] - ## @param text The text to encode. + # @param text The text to encode. def encode(self, text): """ Pad an input string according to PKCS#7 diff --git a/scapy_ssl_tls/ssl_tls.py b/scapy_ssl_tls/ssl_tls.py index fd10fff..ac7149c 100644 --- a/scapy_ssl_tls/ssl_tls.py +++ b/scapy_ssl_tls/ssl_tls.py @@ -13,8 +13,21 @@ import ssl_tls_registry as registry + class BLenField(LenField): - def __init__(self, name, default, fmt="I", adjust_i2m=lambda pkt, x:x, numbytes=None, length_of=None, count_of=None, adjust_m2i=lambda pkt, x:x): + + def __init__( + self, + name, + default, + fmt="I", + adjust_i2m=lambda pkt, + x: x, + numbytes=None, + length_of=None, + count_of=None, + adjust_m2i=lambda pkt, + x: x): LenField.__init__(self, name, default, fmt) self.name = name self.adjust_i2m = adjust_i2m @@ -37,13 +50,14 @@ def addfield(self, pkt, s, val): if self.numbytes: pack = pack[len(pack) - self.numbytes:] return s + pack + def getfield(self, pkt, s): """Extract an internal value from a string""" upack_data = s[:self.sz] # prepend struct.calcsize()-len(data) bytes to satisfy struct.unpack upack_data = '\x00' * (struct.calcsize(self.fmt) - self.sz) + upack_data - return s[self.sz:], self.m2i(pkt, struct.unpack(self.fmt, upack_data)[0]) + return s[self.sz:], self.m2i(pkt, struct.unpack(self.fmt, upack_data)[0]) def i2m(self, pkt, x): if x is None: @@ -60,22 +74,31 @@ def i2m(self, pkt, x): f = fld.i2count(pkt, fval) x = self.adjust_i2m(pkt, f) return x + def m2i(self, pkt, x): return self.adjust_m2i(pkt, x) + class XBLenField(BLenField): + def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) + class XLenField(LenField): + def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) + class XFieldLenField(FieldLenField): + def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) + class BEnumField(EnumField): + def __init__(self, name, default, enum, fmt="!I", numbytes=None): EnumField.__init__(self, name, default, enum, fmt) self.numbytes = numbytes @@ -95,62 +118,77 @@ def addfield(self, pkt, s, val): if self.numbytes: pack = pack[len(pack) - self.numbytes:] return s + pack + def getfield(self, pkt, s): """Extract an internal value from a string""" upack_data = s[:self.sz] # prepend struct.calcsize()-len(data) bytes to satisfy struct.unpack upack_data = '\x00' * (struct.calcsize(self.fmt) - self.sz) + upack_data - return s[self.sz:], self.m2i(pkt, struct.unpack(self.fmt, upack_data)[0]) + return s[self.sz:], self.m2i(pkt, struct.unpack(self.fmt, upack_data)[0]) def i2repr_one(self, pkt, x): if self not in conf.noenum and not isinstance(x, VolatileValue) and x in self.i2s: return self.i2s[x] return lhex(x) + class XBEnumField(BEnumField): + def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) + class ReprFieldListField(FieldListField): + """ Human Readable FieldListField for Enum type list entries """ + def i2repr(self, pkt, x): - return self.field.i2repr(pkt,x) + return self.field.i2repr(pkt, x) + class StrConditionalField(ConditionalField): + """ Base conditional field that is not restricted to pkt checks + allows conditional checks on the raw_stream 's' + allows conditional checks on the layers build value """ + def _evalcond(self, pkt=None, s=None, val=None): return self.cond(pkt, s, val) def getfield(self, pkt, s): - if self._evalcond(pkt,s): - return self.fld.getfield(pkt,s) + if self._evalcond(pkt, s): + return self.fld.getfield(pkt, s) else: - return s,None + return s, None def addfield(self, pkt, s, val): - if self._evalcond(pkt,s,val): - return self.fld.addfield(pkt,s,val) + if self._evalcond(pkt, s, val): + return self.fld.addfield(pkt, s, val) else: return s + class PacketNoPayload(Packet): + """ This type of packet has no payload/sub-layer (typically used for PacketListFields or leaf layers) """ + def extract_padding(self, s): return '', s + class PacketLengthFieldPayload(Packet): + """ This type of packet provides only up to self.length bytes to the next layer (payload) Applicable when last field is .length and the length describes the next-layer length in bytes Behaves like Packet.extract_padding if self.length is not available to make this Packet type work with all Packets """ + def extract_padding(self, s): if not hasattr(self, 'length'): return Packet.extract_padding(self, s) @@ -158,7 +196,9 @@ def extract_padding(self, s): pad = s[self.length:] return pay, pad + class StackedLenPacket(Packet): + """ Allows stacked packets. Tries to chop layers by layer.length """ @@ -175,31 +215,34 @@ def do_dissect_payload(self, s): # if there is a length field, chop the stream, add the payload # otherwise we'll consume the full length and return if p.length <= s_len: - p = cls(s[:cls_header_len+p.length], _internal=1, _underlayer=self) - s_len = cls_header_len+p.length + p = cls(s[:cls_header_len + p.length], _internal=1, _underlayer=self) + s_len = cls_header_len + p.length except AttributeError: pass self.add_payload(p) s = s[s_len:] + class EnumStruct(object): + def __init__(self, entries): - entries = dict((v.replace(' ','_').upper(),k) for k,v in entries.iteritems()) + entries = dict((v.replace(' ', '_').upper(), k) for k, v in entries.iteritems()) self.__dict__.update(entries) TLS_VERSIONS = { # SSL - 0x0002:"SSL_2_0", - 0x0300:"SSL_3_0", - # TLS - 0x0301:"TLS_1_0", - 0x0302:"TLS_1_1", - 0x0303:"TLS_1_2", + 0x0002: "SSL_2_0", + 0x0300: "SSL_3_0", + # TLS: + 0x0301: "TLS_1_0", + 0x0302: "TLS_1_1", + 0x0303: "TLS_1_2", + 0x0304: "TLS_1_4", # DTLS - 0x0100:"PROTOCOL_DTLS_1_0_OPENSSL_PRE_0_9_8f", - 0xfeff:"DTLS_1_0", - 0xfefd:"DTLS_1_1", - } + 0x0100: "PROTOCOL_DTLS_1_0_OPENSSL_PRE_0_9_8f", + 0xfeff: "DTLS_1_0", + 0xfefd: "DTLS_1_1", +} TLSVersion = EnumStruct(TLS_VERSIONS) TLS_CONTENT_TYPES = registry.TLS_CONTENTTYPE_REGISTRY @@ -209,14 +252,14 @@ def __init__(self, entries): TLSHandshakeType = EnumStruct(TLS_HANDSHAKE_TYPES) TLS_EXTENSION_TYPES = registry.EXTENSIONTYPE_VALUES -TLS_EXTENSION_TYPES.update({0x3374:"next_protocol_negotiation"}) # manually add NPN as it is not in iana registry +TLS_EXTENSION_TYPES.update({0x3374: "next_protocol_negotiation"}) # manually add NPN as it is not in iana registry TLSExtensionType = EnumStruct(TLS_EXTENSION_TYPES) TLS_ALERT_LEVELS = { 0x01: "warning", 0x02: "fatal", 0xff: "unknown", - } +} TLSAlertLevel = EnumStruct(TLS_ALERT_LEVELS) TLS_ALERT_DESCRIPTIONS = registry.TLS_ALERT_REGISTRY @@ -228,7 +271,7 @@ def __init__(self, entries): 0x03: 2 ** 11, 0x04: 2 ** 12, 0xff: 'unknown', - } +} TLS_CIPHER_SUITES = registry.TLS_CIPHER_SUITE_REGISTRY # adding missing ciphers @@ -249,7 +292,7 @@ def __init__(self, entries): 0x00: 'individual_certs', 0x01: 'pkipath', 0xff: 'unknown', - } +} TLSCertChainType = EnumStruct(TLS_CERT_CHAIN_TYPE) TLS_HEARTBEAT_MODE = registry.HEARTBEAT_MODES @@ -261,7 +304,7 @@ def __init__(self, entries): TLS_TYPE_BOOLEAN = { 0x00: 'false', 0x01: 'true', - } +} TLSTypeBoolean = EnumStruct(TLS_TYPE_BOOLEAN) TLS_EC_POINT_FORMATS = registry.EC_POINT_FORMAT_REGISTRY @@ -288,15 +331,17 @@ class TLSKexNames(object): DHE = "DHE" ECDHE = "ECDHE" + class TLSFragmentationError(Exception): pass + class TLSRecord(StackedLenPacket): MAX_LEN = 2**16 - 1 name = "TLS Record" fields_desc = [ByteEnumField("content_type", TLSContentType.APPLICATION_DATA, TLS_CONTENT_TYPES), XShortEnumField("version", TLSVersion.TLS_1_0, TLS_VERSIONS), - XLenField("length", None, fmt="!H"), ] + XLenField("length", None, fmt="!H")] def __init__(self, *args, **fields): self.fragments = [] @@ -313,7 +358,7 @@ def guess_payload_class(self, payload): cls = StackedLenPacket.guess_payload_class(self, payload) p = cls(payload, _internal=1, _underlayer=self) try: - if cls == Raw().__class__ or p.length > len(payload) : + if cls == Raw().__class__ or p.length > len(payload): # length does not fit len raw_bytes, assume its corrupt or encrypted cls = TLSCiphertext except AttributeError: @@ -333,7 +378,7 @@ def do_build(self): for t in self.post_transforms: pkt = t(pkt) pay = self.do_build_payload() - p = self.post_build(pkt,pay) + p = self.post_build(pkt, pay) return p def fragment(self, size=2**14): @@ -342,102 +387,106 @@ def fragment(self, size=2**14): class TLSServerName(PacketNoPayload): name = "TLS Servername" - fields_desc = [ByteEnumField("type", 0x00, {0x00:"host"}), - XFieldLenField("length", None, length_of="data", fmt="H"), - StrLenField("data", "", length_from=lambda x:x.length), - ] + fields_desc = [ByteEnumField("type", 0x00, {0x00: "host"}), + XFieldLenField("length", None, length_of="data", fmt="H"), + StrLenField("data", "", length_from=lambda x: x.length)] + class TLSExtServerNameIndication(PacketNoPayload): name = "TLS Extension Servername Indication" fields_desc = [XFieldLenField("length", None, length_of="server_names", fmt="H"), - PacketListField("server_names", None, TLSServerName, length_from=lambda x:x.length), - ] + PacketListField("server_names", None, TLSServerName, length_from=lambda x:x.length)] + +# https://tools.ietf.org/html/rfc7301 + -#https://tools.ietf.org/html/rfc7301 class TLSALPNProtocol(PacketNoPayload): name = "TLS ALPN Protocol" - fields_desc = [ - XFieldLenField("length", None, length_of="data", fmt="B"), - StrLenField("data", "", length_from=lambda x:x.length), - ] + fields_desc = [XFieldLenField("length", None, length_of="data", fmt="B"), + StrLenField("data", "", length_from=lambda x:x.length)] + class TLSExtALPN(PacketNoPayload): name = "TLS Extension Application-Layer Protocol Negotiation" fields_desc = [XFieldLenField("length", None, length_of="protocol_name_list", fmt="H"), - PacketListField("protocol_name_list", None, TLSALPNProtocol, length_from=lambda x:x.length), - ] + PacketListField("protocol_name_list", None, TLSALPNProtocol, length_from=lambda x:x.length)] + class TLSExtension(PacketLengthFieldPayload): name = "TLS Extension" fields_desc = [XShortEnumField("type", TLSExtensionType.SERVER_NAME, TLS_EXTENSION_TYPES), - XLenField("length", None, fmt="!H"), - ] + XLenField("length", None, fmt="!H")] + -# https://www.ietf.org/rfc/rfc3546.txt class TLSExtMaxFragmentLength(PacketNoPayload): name = "TLS Extension Max Fragment Length" fields_desc = [ByteEnumField("fragment_length", 0xff, TLS_EXT_MAX_FRAGMENT_LENGTH_ENUM)] + class TLSURLAndOptionalHash(PacketNoPayload): name = "TLS Extension Certificate URL/Hash" fields_desc = [XFieldLenField("url_length", None, length_of="url", fmt="H"), - StrLenField("url", "", length_from=lambda x:x.url_length), - ByteEnumField("hash_present", TLSTypeBoolean.FALSE, TLS_TYPE_BOOLEAN), - StrLenField("sha1hash", "", length_from=lambda x:20 if x.hash_present else 0), # opaque SHA1Hash[20]; - ] + StrLenField("url", "", length_from=lambda x:x.url_length), + ByteEnumField("hash_present", TLSTypeBoolean.FALSE, TLS_TYPE_BOOLEAN), + StrLenField("sha1hash", "", length_from=lambda x:20 if x.hash_present else 0)] + class TLSExtCertificateURL(PacketNoPayload): name = "TLS Extension Certificate URL" fields_desc = [ByteEnumField("type", TLSCertChainType.INDIVIDUAL_CERTS, TLS_CERT_CHAIN_TYPE), XFieldLenField("length", None, length_of="certificate_urls", fmt="H"), - PacketListField("certificate_urls", None, TLSURLAndOptionalHash, length_from=lambda x:x.length) - ] + PacketListField("certificate_urls", None, TLSURLAndOptionalHash, length_from=lambda x:x.length)] + class TLSExtECPointsFormat(PacketNoPayload): name = "TLS Extension EC Points Format" - fields_desc = [ - XFieldLenField("length", None, length_of="ec_point_formats", fmt="B"), - ReprFieldListField("ec_point_formats", None, ByteEnumField("ec_point_format", None, TLS_EC_POINT_FORMATS), length_from=lambda x:x.length), - ] + fields_desc = [XFieldLenField("length", None, length_of="ec_point_formats", fmt="B"), + ReprFieldListField("ec_point_formats", None, + ByteEnumField("ec_point_format", None, TLS_EC_POINT_FORMATS), + length_from=lambda x:x.length)] + class TLSExtEllipticCurves(PacketNoPayload): name = "TLS Extension Elliptic Curves" - fields_desc = [ - XFieldLenField("length", None, length_of="elliptic_curves", fmt="H"), - ReprFieldListField("elliptic_curves", None, ShortEnumField("elliptic_curve", None, TLS_ELLIPTIC_CURVES), length_from=lambda x:x.length), - ] + fields_desc = [XFieldLenField("length", None, length_of="elliptic_curves", fmt="H"), + ReprFieldListField("elliptic_curves", None, + ShortEnumField("elliptic_curve", None, TLS_ELLIPTIC_CURVES), + length_from=lambda x:x.length)] + class TLSSignatureHashAlgorithm(PacketNoPayload): name = "TLS Signature Hash Algorithm Pair" - fields_desc = [ - ByteEnumField("hash_alg", None, TLS_HASH_ALGORITHMS), - ByteEnumField("sig_alg", None, TLS_SIGNATURE_ALGORITHMS), - ] + fields_desc = [ByteEnumField("hash_alg", None, TLS_HASH_ALGORITHMS), + ByteEnumField("sig_alg", None, TLS_SIGNATURE_ALGORITHMS)] + class TLSExtSignatureAndHashAlgorithm(PacketNoPayload): name = "TLS Extension Signature And Hash Algorithm" - fields_desc = [ - XFieldLenField("length", None, length_of="algorithms", fmt="H"), - PacketListField("algs", None, TLSSignatureHashAlgorithm, length_from=lambda x:x.length), - ] + fields_desc = [XFieldLenField("length", None, length_of="algs", fmt="H"), + PacketListField("algs", None, TLSSignatureHashAlgorithm, length_from=lambda x:x.length)] + class TLSExtHeartbeat(PacketNoPayload): name = "TLS Extension HeartBeat" fields_desc = [ByteEnumField("mode", TLSHeartbeatMode.PEER_NOT_ALLOWED_TO_SEND, TLS_HEARTBEAT_MODE)] + class TLSExtSessionTicketTLS(PacketNoPayload): name = "TLS Extension SessionTicket TLS" - fields_desc = [StrLenField("data", '', length_from=lambda x:x.underlayer.length),] + fields_desc = [StrLenField("data", '', length_from=lambda x:x.underlayer.length)] + class TLSExtRenegotiationInfo(PacketNoPayload): name = "TLS Extension Renegotiation Info" fields_desc = [XFieldLenField("length", None, length_of="data", fmt="B"), - StrLenField("data", '', length_from=lambda x:x.length),] + StrLenField("data", '', length_from=lambda x:x.length)] + class TLSHelloRequest(Packet): name = "TLS Hello Request" fields_desc = [] + class TLSClientHello(PacketNoPayload): name = "TLS Client Hello" fields_desc = [XShortEnumField("version", TLSVersion.TLS_1_0, TLS_VERSIONS), @@ -445,16 +494,20 @@ class TLSClientHello(PacketNoPayload): StrFixedLenField("random_bytes", os.urandom(28), 28), XFieldLenField("session_id_length", None, length_of="session_id", fmt="B"), StrLenField("session_id", '', length_from=lambda x:x.session_id_length), - XFieldLenField("cipher_suites_length", None, length_of="cipher_suites", fmt="H"), - ReprFieldListField("cipher_suites", [TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA], XShortEnumField("cipher", None, TLS_CIPHER_SUITES), length_from=lambda x:x.cipher_suites_length), - + ReprFieldListField("cipher_suites", [TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA], + XShortEnumField("cipher", None, TLS_CIPHER_SUITES), + length_from=lambda x:x.cipher_suites_length), XFieldLenField("compression_methods_length", None, length_of="compression_methods", fmt="B"), - ReprFieldListField("compression_methods", [TLSCompressionMethod.NULL], ByteEnumField("compression", None, TLS_COMPRESSION_METHODS), length_from=lambda x:x.compression_methods_length), - - StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), lambda pkt,s,val: True if val or pkt.extensions or (s and struct.unpack("!H",s[:2])[0]==len(s)-2) else False), - PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length), - ] + ReprFieldListField("compression_methods", [TLSCompressionMethod.NULL], + ByteEnumField("compression", None, TLS_COMPRESSION_METHODS), + length_from=lambda x:x.compression_methods_length), + StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), + lambda pkt, s, val: True if val or + pkt.extensions or + (s and struct.unpack("!H", s[:2])[0] == len(s) - 2) + else False), + PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length)] class TLSServerHello(PacketNoPayload): @@ -464,30 +517,33 @@ class TLSServerHello(PacketNoPayload): StrFixedLenField("random_bytes", os.urandom(28), 28), XFieldLenField("session_id_length", None, length_of="session_id", fmt="B"), StrLenField("session_id", os.urandom(20), length_from=lambda x:x.session_id_length), - XShortEnumField("cipher_suite", TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, TLS_CIPHER_SUITES), ByteEnumField("compression_method", TLSCompressionMethod.NULL, TLS_COMPRESSION_METHODS), + StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), + lambda pkt, s, val: True if val or + pkt.extensions or + (s and struct.unpack("!H", s[:2])[0] == len(s) - 2) + else False), + PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length)] - StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), lambda pkt,s,val: True if val or pkt.extensions or (s and struct.unpack("!H",s[:2])[0]==len(s)-2) else False), - PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length), - ] class TLSSessionTicket(PacketNoPayload): name = "TLS Session Ticket" fields_desc = [IntField("lifetime", 7200), XFieldLenField("ticket_length", None, length_of="ticket", fmt="!H"), - StrLenField("ticket", '', length_from=lambda x:x.ticket_length), - ] + StrLenField("ticket", '', length_from=lambda x:x.ticket_length)] + class TLSHeartBeat(PacketNoPayload): name = "TLS HeartBeat" fields_desc = [ByteEnumField("type", TLSHeartbeatMessageType.HEARTBEAT_REQUEST, TLS_HEARTBEAT_MESSAGE_TYPE), - FieldLenField("length", None, length_of="data", fmt="H"), - StrLenField("data", "", length_from=lambda x:x.length), - StrLenField("padding", "", length_from=lambda x: 'P' * (16 - x.length))] + FieldLenField("length", None, length_of="data", fmt="H"), + StrLenField("data", "", length_from=lambda x:x.length), + StrLenField("padding", "", length_from=lambda x: 'P' * (16 - x.length))] class TLSKeyExchange(Packet): + def __init__(self, *args, **fields): try: self.tls_ctx = fields["ctx"] @@ -510,7 +566,7 @@ def pre_dissect(self, s): def guess_payload_class(self, raw_bytes): next_layer = Raw if self.tls_ctx is not None: - kex = self.tls_ctx.params.negotiated.key_exchange + kex = self.tls_ctx.negotiated.key_exchange if kex is not None: try: next_layer = self.kex_payload_table[kex] @@ -575,7 +631,7 @@ class TLSServerKeyExchange(TLSKeyExchange): name = "TLS Server Key Exchange" kex_payload_table = {TLSKexNames.DHE: TLSServerDHParams, TLSKexNames.ECDHE: TLSServerECDHParams} - #Add ERSA in the future + # Add ERSA in the future class TLSServerHelloDone(PacketNoPayload): @@ -586,8 +642,8 @@ class TLSServerHelloDone(PacketNoPayload): class TLSCertificate(PacketNoPayload): name = "TLS Certificate" - fields_desc = [ XBLenField("length", None, length_of="data", fmt="!I", numbytes=3), - PacketLenField("data", None, x509.X509Cert, length_from=lambda x:x.length),] + fields_desc = [XBLenField("length", None, length_of="data", fmt="!I", numbytes=3), + PacketLenField("data", None, x509.X509Cert, length_from=lambda x:x.length)] class TLSCertificateList(PacketNoPayload): @@ -612,7 +668,6 @@ class TLSCADistinguishedName(PacketNoPayload): name = "TLS CA Distinguished Name" fields_desc = [XFieldLenField("length", None, length_of="dn", fmt="H"), PacketLenField("ca_dn", None, x509.X509v3Ext, length_from=lambda x:x.length)] - # StrLenField("ca_dn", "", length_from=lambda x: x.length)] class TLSCertificateRequest(Packet): @@ -629,18 +684,17 @@ class TLSDecryptablePacket(PacketLengthFieldPayload): explicit_iv_field = StrField("explicit_iv", "", fmt="H") mac_field = StrField("mac", "", fmt="H") - padding_field = StrLenField("padding", "", length_from=lambda pkt:pkt.padding_len) + padding_field = StrLenField("padding", "", length_from=lambda pkt: pkt.padding_len) padding_len_field = ConditionalField( - XFieldLenField("padding_len", None, length_of="padding", fmt="B"), - lambda pkt: True if pkt and hasattr(pkt, "padding") and pkt.padding != "" else False) + XFieldLenField("padding_len", None, length_of="padding", fmt="B"), + lambda pkt: True if pkt and hasattr(pkt, "padding") and pkt.padding != "" else False) decryptable_fields = [mac_field, padding_field, padding_len_field] def __init__(self, *args, **fields): try: self.tls_ctx = fields["ctx"] del(fields["ctx"]) - self.above_tls10 = self.tls_ctx.params.negotiated.version > TLSVersion.TLS_1_0 - if self.explicit_iv_field not in self.fields_desc and self.above_tls10: + if self.explicit_iv_field not in self.fields_desc and self.tls_ctx.requires_iv: self.fields_desc.append(self.explicit_iv_field) for field in self.decryptable_fields: if field not in self.fields_desc: @@ -660,7 +714,7 @@ def pre_dissect(self, raw_bytes): self.padding_len = ord(raw_bytes[-1]) self.padding = raw_bytes[-self.padding_len - 1:-1] self.mac = raw_bytes[-self.padding_len - hash_size - 1:-self.padding_len - 1] - if self.above_tls10: + if self.tls_ctx.requires_iv: self.explicit_iv = raw_bytes[:iv_size] data = raw_bytes[iv_size:-self.padding_len - hash_size - 1] else: @@ -739,9 +793,11 @@ class TLSAlert(TLSDecryptablePacket): fields_desc = [ByteEnumField("level", TLSAlertLevel.WARNING, TLS_ALERT_LEVELS), ByteEnumField("description", TLSAlertDescription.CLOSE_NOTIFY, TLS_ALERT_DESCRIPTIONS)] + class TLSCiphertext(Packet): name = "TLS Ciphertext" - fields_desc = [ StrField("data", None, fmt="H") ] + fields_desc = [StrField("data", None, fmt="H")] + class DTLSRecord(PacketLengthFieldPayload): name = "DTLS Record" @@ -749,15 +805,15 @@ class DTLSRecord(PacketLengthFieldPayload): XShortEnumField("version", TLSVersion.DTLS_1_0, TLS_VERSIONS), ShortField("epoch", None), XBLenField("sequence", None, fmt="!Q", numbytes=6), - XLenField("length", None, fmt="!H"), ] + XLenField("length", None, fmt="!H")] + class DTLSHandshake(PacketLengthFieldPayload): name = "DTLS Handshake" - fields_desc = TLSHandshake.fields_desc + [ - ShortField("sequence", None), - XBLenField("fragment_offset", None, fmt="!I", numbytes=3), - XBLenField("length", None, fmt="!I", numbytes=3), - ] + fields_desc = TLSHandshake.fields_desc + [ShortField("sequence", None), + XBLenField("fragment_offset", None, fmt="!I", numbytes=3), + XBLenField("length", None, fmt="!I", numbytes=3)] + class DTLSClientHello(PacketNoPayload): name = "DTLS Client Hello" @@ -766,36 +822,38 @@ class DTLSClientHello(PacketNoPayload): StrFixedLenField("random_bytes", os.urandom(28), 28), XFieldLenField("session_id_length", None, length_of="session_id", fmt="B"), StrLenField("session_id", '', length_from=lambda x:x.session_id_length), - XFieldLenField("cookie_length", None, length_of="cookie", fmt="B"), StrLenField("cookie", '', length_from=lambda x:x.cookie_length), - XFieldLenField("cipher_suites_length", None, length_of="cipher_suites", fmt="H"), - ReprFieldListField("cipher_suites", None, XShortEnumField("cipher", None, TLS_CIPHER_SUITES), length_from=lambda x:x.cipher_suites_length), - + ReprFieldListField("cipher_suites", None, XShortEnumField("cipher", None, TLS_CIPHER_SUITES), + length_from=lambda x:x.cipher_suites_length), XFieldLenField("compression_methods_length", None, length_of="compression_methods", fmt="B"), - ReprFieldListField("compression_methods", None, ByteEnumField("compression", None, TLS_COMPRESSION_METHODS), length_from=lambda x:x.compression_methods_length), - - StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), lambda pkt,s,val: True if val or pkt.extensions or (s and struct.unpack("!H",s[:2])[0]==len(s)-2) else False), - PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length), - ] - -SSLv2_CERTIFICATE_TYPES = { 0x01: 'x509' } + ReprFieldListField("compression_methods", None, + ByteEnumField("compression", None, TLS_COMPRESSION_METHODS), + length_from=lambda x:x.compression_methods_length), + StrConditionalField(XFieldLenField("extensions_length", None, length_of="extensions", fmt="H"), + lambda pkt, s, val: True if val or + pkt.extensions or + (s and struct.unpack("!H", s[:2])[0] == len(s) - 2) + else False), + PacketListField("extensions", None, TLSExtension, length_from=lambda x:x.extensions_length)] + +SSLv2_CERTIFICATE_TYPES = {0x01: 'x509'} SSLv2CertificateType = EnumStruct(SSLv2_CERTIFICATE_TYPES) + class DTLSHelloVerify(PacketNoPayload): name = "DTLS Hello Verify" fields_desc = [XShortEnumField("version", TLSVersion.DTLS_1_0, TLS_VERSIONS), XFieldLenField("cookie_length", None, length_of="cookie", fmt="B"), - StrLenField("cookie", '', length_from=lambda x:x.cookie_length), - ] + StrLenField("cookie", '', length_from=lambda x:x.cookie_length)] SSLv2_MESSAGE_TYPES = { - 0x01:'client_hello', + 0x01: 'client_hello', 0x04: 'server_hello', 0x02: 'client_master_key', - } +} SSLv2MessageType = EnumStruct(SSLv2_MESSAGE_TYPES) SSLv2_CIPHER_SUITES = { @@ -806,59 +864,58 @@ class DTLSHelloVerify(PacketNoPayload): 0x60040: 'DES_64_CBC_WITH_MD5', 0x700c0: 'DES_192_EDE3_CBC_WITH_MD5', 0x80080: 'RC4_64_WITH_MD5', - } +} SSLv2CipherSuite = EnumStruct(SSLv2_CIPHER_SUITES) + class SSLv2Record(Packet): name = "SSLv2 Record" - fields_desc = [XBLenField("length", None, fmt="!H", adjust_i2m=lambda pkt, x: x + 0x8000 + 1, adjust_m2i=lambda pkt, x:x - 0x8000), # length=halfbyte+byte with MSB(high(1stbyte)) =1 || +1 for lengt(content_type) - ByteEnumField("content_type", 0xff, SSLv2_MESSAGE_TYPES), - ] + fields_desc = [XBLenField("length", None, fmt="!H", adjust_i2m=lambda pkt, x: x + 0x8000 + 1, + adjust_m2i=lambda pkt, x:x - 0x8000), # length=halfbyte+byte with MSB(high(1stbyte)) =1 || +1 for lengt(content_type) + ByteEnumField("content_type", 0xff, SSLv2_MESSAGE_TYPES)] + class SSLv2ClientHello(Packet): name = "SSLv2 Client Hello" - fields_desc = [ - XShortEnumField("version", TLSVersion.SSL_2_0, TLS_VERSIONS), - + fields_desc = [XShortEnumField("version", TLSVersion.SSL_2_0, TLS_VERSIONS), XFieldLenField("cipher_suites_length", None, length_of="cipher_suites", fmt="H"), XFieldLenField("session_id_length", None, length_of="session_id", fmt="H"), XFieldLenField("challenge_length", None, length_of="challenge", fmt="H"), - - ReprFieldListField("cipher_suites", None, XBEnumField("cipher", None, SSLv2_CIPHER_SUITES, fmt="!I", numbytes=3), length_from=lambda x:x.cipher_suites_length), + ReprFieldListField("cipher_suites", None, + XBEnumField("cipher", None, SSLv2_CIPHER_SUITES, fmt="!I", numbytes=3), + length_from=lambda x:x.cipher_suites_length), StrLenField("session_id", '', length_from=lambda x:x.session_id_length), - StrLenField("challenge", '', length_from=lambda x:x.challenge_length), - ] + StrLenField("challenge", '', length_from=lambda x:x.challenge_length)] + class SSLv2ServerHello(Packet): name = "SSLv2 Server Hello" - fields_desc = [ - ByteEnumField("session_id_hit", TLSTypeBoolean.FALSE, TLS_TYPE_BOOLEAN), + fields_desc = [ByteEnumField("session_id_hit", TLSTypeBoolean.FALSE, TLS_TYPE_BOOLEAN), ByteEnumField("certificate_type", SSLv2CertificateType.X509, SSLv2_CERTIFICATE_TYPES), XShortEnumField("version", TLSVersion.SSL_2_0, TLS_VERSIONS), - XFieldLenField("certificates_length", None, length_of="certificates", fmt="H"), XFieldLenField("cipher_suites_length", None, length_of="cipher_suites", fmt="H"), XFieldLenField("connection_id_length", None, length_of="connection_id", fmt="H"), - StrLenField("certificates", '', length_from=lambda x:x.certificates_length), - ReprFieldListField("cipher_suites", None, XBEnumField("cipher", None, SSLv2_CIPHER_SUITES, fmt="!I", numbytes=3), length_from=lambda x:x.cipher_suites_length), - StrLenField("connection_id", '', length_from=lambda x:x.connection_id_length), - ] + ReprFieldListField("cipher_suites", None, + XBEnumField("cipher", None, SSLv2_CIPHER_SUITES, fmt="!I", numbytes=3), + length_from=lambda x:x.cipher_suites_length), + StrLenField("connection_id", '', length_from=lambda x:x.connection_id_length)] + class SSLv2ClientMasterKey(Packet): name = "SSLv2 Client Master Key" - fields_desc = [ - XBEnumField("cipher_suite", SSLv2CipherSuite.RC4_128_WITH_MD5, SSLv2_CIPHER_SUITES, fmt="!I", numbytes=3), # fixme: 3byte wide - + fields_desc = [XBEnumField("cipher_suite", SSLv2CipherSuite.RC4_128_WITH_MD5, SSLv2_CIPHER_SUITES, fmt="!I", + numbytes=3), + # fixme: 3byte wide XFieldLenField("clear_key_length", None, length_of="clear_key", fmt="H"), XFieldLenField("encrypted_key_length", None, length_of="encrypted_key", fmt="H"), XFieldLenField("key_argument_length", None, length_of="key_argument", fmt="H"), - StrLenField("clear_key", '', length_from=lambda x:x.clear_key_length), StrLenField("encrypted_key", '', length_from=lambda x:x.clear_key_length), - StrLenField("key_argument", '', length_from=lambda x:x.key_argument_length), - ] + StrLenField("key_argument", '', length_from=lambda x:x.key_argument_length)] + class TLSSocket(object): @@ -935,6 +992,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # entry class class SSL(Packet): + """ COMPOUND CLASS for SSL """ @@ -974,13 +1032,13 @@ def do_dissect(self, raw_bytes): # Consume all bytes passed to us by the underlayer. We're expecting no # further payload on top of us. If there is additional data on top of our layer # We will incorrectly parse it - while pos < len(raw_bytes)-record_header_len: - payload_len = record(raw_bytes[pos:pos+record_header_len]).length + while pos < len(raw_bytes) - record_header_len: + payload_len = record(raw_bytes[pos:pos + record_header_len]).length if self.tls_ctx is not None: - payload = record(raw_bytes[pos:pos+record_header_len+payload_len], ctx=self.tls_ctx) + payload = record(raw_bytes[pos:pos + record_header_len + payload_len], ctx=self.tls_ctx) self.tls_ctx.insert(payload) else: - payload = record(raw_bytes[pos:pos+record_header_len+payload_len]) + payload = record(raw_bytes[pos:pos + record_header_len + payload_len]) # Populate our list of found records records.append(payload) # Move to the next record @@ -1017,16 +1075,16 @@ def post_dissect(self, s): if encrypted_payload is not None: try: if self.tls_ctx.client: - cleartext = self.tls_ctx.crypto.server.dec.decrypt(encrypted_payload) + cleartext = self.tls_ctx.server_ctx.dec.decrypt(encrypted_payload) else: - cleartext = self.tls_ctx.crypto.client.dec.decrypt(encrypted_payload) + cleartext = self.tls_ctx.client_ctx.dec.decrypt(encrypted_payload) pkt = layer(cleartext, ctx=self.tls_ctx) original_record = record record[self.guessed_next_layer].payload = pkt # If the encrypted is in the history packet list, update it with the unencrypted version - if original_record in self.tls_ctx.packets.history: - record_index = self.tls_ctx.packets.history.index(original_record) - self.tls_ctx.packets.history[record_index] = record + if original_record in self.tls_ctx.history: + record_index = self.tls_ctx.history.index(original_record) + self.tls_ctx.history[record_index] = record # Decryption failed, raise error otherwise we'll be in inconsistent state with sender except ValueError as ve: raise ValueError("Decryption failed: %s" % ve) @@ -1034,12 +1092,12 @@ def post_dissect(self, s): TLS = SSL -cleartext_handler = { TLSPlaintext: lambda pkt, tls_ctx: (TLSContentType.APPLICATION_DATA, pkt[TLSPlaintext].data), - TLSFinished: lambda pkt, tls_ctx: (TLSContentType.HANDSHAKE, - str(TLSHandshake(type=TLSHandshakeType.FINISHED) / - tls_ctx.get_verify_data())), - TLSChangeCipherSpec: lambda pkt, tls_ctx: (TLSContentType.CHANGE_CIPHER_SPEC, str(pkt)), - TLSAlert: lambda pkt, tls_ctx: (TLSContentType.ALERT, str(pkt)) } +cleartext_handler = {TLSPlaintext: lambda pkt, tls_ctx: (TLSContentType.APPLICATION_DATA, pkt[TLSPlaintext].data), + TLSFinished: lambda pkt, tls_ctx: (TLSContentType.HANDSHAKE, + str(TLSHandshake(type=TLSHandshakeType.FINISHED) / + tls_ctx.get_verify_data())), + TLSChangeCipherSpec: lambda pkt, tls_ctx: (TLSContentType.CHANGE_CIPHER_SPEC, str(pkt)), + TLSAlert: lambda pkt, tls_ctx: (TLSContentType.ALERT, str(pkt))} def to_raw(pkt, tls_ctx, include_record=True, compress_hook=None, pre_encrypt_hook=None, encrypt_hook=None): @@ -1047,7 +1105,7 @@ def to_raw(pkt, tls_ctx, include_record=True, compress_hook=None, pre_encrypt_ho if tls_ctx is None: raise ValueError("A valid TLS session context must be provided") - comp_method = tls_ctx.compression.method + comp_method = tls_ctx.server_ctx.compression content_type, data = None, None for tls_proto, handler in cleartext_handler.iteritems(): @@ -1071,13 +1129,14 @@ def to_raw(pkt, tls_ctx, include_record=True, compress_hook=None, pre_encrypt_ho ciphertext = crypto_container.encrypt() if include_record: - tls_ciphertext = TLSRecord(version=tls_ctx.params.negotiated.version, content_type=content_type)/ciphertext + tls_ciphertext = TLSRecord(version=tls_ctx.negotiated.version, content_type=content_type) / ciphertext else: tls_ciphertext = ciphertext return tls_ciphertext tls_to_raw = to_raw + class TLSProtocolError(Exception): def __init__(self, *args, **kwargs): @@ -1087,24 +1146,25 @@ def __init__(self, *args, **kwargs): self.pkt = None Exception.__init__(self, *args, **kwargs) + def tls_do_handshake(tls_socket, version, ciphers): - client_hello = TLSRecord(version=version)/TLSHandshake()/TLSClientHello(version=version, compression_methods=[TLSCompressionMethod.NULL], - cipher_suites=ciphers) + client_hello = TLSRecord(version=version) / TLSHandshake() / TLSClientHello(version=version, cipher_suites=ciphers) tls_socket.sendall(client_hello) r = tls_socket.recvall() if r.haslayer(TLSAlert): raise TLSProtocolError("Alert returned by server", r) - client_key_exchange = TLSRecord(version=version)/TLSHandshake()/tls_socket.tls_ctx.get_client_kex_data() - client_ccs = TLSRecord(version=version)/TLSChangeCipherSpec() + client_key_exchange = TLSRecord(version=version) / TLSHandshake() / tls_socket.tls_ctx.get_client_kex_data() + client_ccs = TLSRecord(version=version) / TLSChangeCipherSpec() tls_socket.sendall(TLS.from_records([client_key_exchange, client_ccs])) tls_socket.sendall(to_raw(TLSFinished(), tls_socket.tls_ctx)) tls_socket.recvall() + def tls_fragment_payload(pkt, record=None, size=2**14): if size <= 0: raise ValueError("Fragment size must be strictly positive") payload = str(pkt) - payloads = [payload[i: i+size] for i in range(0, len(payload), size)] + payloads = [payload[i: i + size] for i in range(0, len(payload), size)] if record is None: return payloads else: @@ -1132,15 +1192,15 @@ def tls_fragment_payload(pkt, record=None, size=2**14): bind_layers(TLSRecord, TLSHandshake, {'content_type': TLSContentType.HANDSHAKE}) # --> handshake proto -bind_layers(TLSHandshake, TLSHelloRequest, {'type':TLSHandshakeType.HELLO_REQUEST}) -bind_layers(TLSHandshake, TLSClientHello, {'type':TLSHandshakeType.CLIENT_HELLO}) -bind_layers(TLSHandshake, TLSServerHello, {'type':TLSHandshakeType.SERVER_HELLO}) -bind_layers(TLSHandshake, TLSCertificateList, {'type':TLSHandshakeType.CERTIFICATE}) -bind_layers(TLSHandshake, TLSServerKeyExchange, {'type':TLSHandshakeType.SERVER_KEY_EXCHANGE}) -bind_layers(TLSHandshake, TLSServerHelloDone, {'type':TLSHandshakeType.SERVER_HELLO_DONE}) -bind_layers(TLSHandshake, TLSClientKeyExchange, {'type':TLSHandshakeType.CLIENT_KEY_EXCHANGE}) -bind_layers(TLSHandshake, TLSFinished, {'type':TLSHandshakeType.FINISHED}) -bind_layers(TLSHandshake, TLSSessionTicket, {'type':TLSHandshakeType.NEWSESSIONTICKET}) +bind_layers(TLSHandshake, TLSHelloRequest, {'type': TLSHandshakeType.HELLO_REQUEST}) +bind_layers(TLSHandshake, TLSClientHello, {'type': TLSHandshakeType.CLIENT_HELLO}) +bind_layers(TLSHandshake, TLSServerHello, {'type': TLSHandshakeType.SERVER_HELLO}) +bind_layers(TLSHandshake, TLSCertificateList, {'type': TLSHandshakeType.CERTIFICATE}) +bind_layers(TLSHandshake, TLSServerKeyExchange, {'type': TLSHandshakeType.SERVER_KEY_EXCHANGE}) +bind_layers(TLSHandshake, TLSServerHelloDone, {'type': TLSHandshakeType.SERVER_HELLO_DONE}) +bind_layers(TLSHandshake, TLSClientKeyExchange, {'type': TLSHandshakeType.CLIENT_KEY_EXCHANGE}) +bind_layers(TLSHandshake, TLSFinished, {'type': TLSHandshakeType.FINISHED}) +bind_layers(TLSHandshake, TLSSessionTicket, {'type': TLSHandshakeType.NEWSESSIONTICKET}) bind_layers(TLSHandshake, TLSCertificateRequest, {"type": TLSHandshakeType.CERTIFICATE_REQUEST}) bind_layers(TLSHandshake, TLSCertificateVerify, {"type": TLSHandshakeType.CERTIFICATE_VERIFY}) # <--- diff --git a/scapy_ssl_tls/ssl_tls_automata.py b/scapy_ssl_tls/ssl_tls_automata.py index f62582e..02ea854 100644 --- a/scapy_ssl_tls/ssl_tls_automata.py +++ b/scapy_ssl_tls/ssl_tls_automata.py @@ -10,14 +10,15 @@ from scapy.automaton import Automaton, ATMT + def hookable(f): @functools.wraps(f) def wrapped_f(*args, **kwargs): - if args and isinstance(args[0],Automaton): + if args and isinstance(args[0], Automaton): obj = args[0] - cb_f = obj.callbacks.get(f.__name__.rsplit("_wrapper",1)[0], None) + cb_f = obj.callbacks.get(f.__name__.rsplit("_wrapper", 1)[0], None) if cb_f: - obj.debug(1, "*** CALLBACK *** calling '%s' -> %s"%(f.__name__, cb_f)) + obj.debug(1, "*** CALLBACK *** calling '%s' -> %s" % (f.__name__, cb_f)) return cb_f(*args, **kwargs) return f(*args, **kwargs) @@ -34,15 +35,15 @@ def wrapped_f(*args, **kwargs): of the unwrapped function. this way, ATMT.graph() works fine as long as it is called before ATMT.run() - ''' + ''' f.wrapper_f = wrapped_f return f else: return wrapped_f - class TLSClientAutomata(Automaton): + """"A Simple TLS Client Automata TLSClientAutomata.graph() @@ -56,20 +57,21 @@ class TLSClientAutomata(Automaton): request="GET / HTTP/1.1\r\nHOST: localhost\r\n\r\n") auto_cli.run() """ + def __init__(self, *args, **kwargs): - self.callbacks = {} # fname:func + self.callbacks = {} # fname:func Automaton.__init__(self, *args, **kwargs) self.STATES = {TLSClientHello: 'CLIENT_HELLO_SENT', - TLSServerHello: 'SERVER_HELLO_RECV', - TLSCertificate: 'SERVER_HELLO_RECV', - TLSCertificateList: 'SERVER_HELLO_RECV', - TLSServerHelloDone: 'SERVER_HELLO_RECV', - TLSFinished: 'CLIENT_FINISH_SENT', - TLSChangeCipherSpec: 'CLIENT_CHANGE_CIPHERSPEC_SENT', - TLSClientKeyExchange: 'CLIENT_KEY_EXCHANGE_SENT', - #TLSServerKeyExchange: 'xxx', - TLSPlaintext: 'CLIENT_APPDATA_SENT', - TLSDecryptablePacket: 'CLIENT_APPDATA_SENT', + TLSServerHello: 'SERVER_HELLO_RECV', + TLSCertificate: 'SERVER_HELLO_RECV', + TLSCertificateList: 'SERVER_HELLO_RECV', + TLSServerHelloDone: 'SERVER_HELLO_RECV', + TLSFinished: 'CLIENT_FINISH_SENT', + TLSChangeCipherSpec: 'CLIENT_CHANGE_CIPHERSPEC_SENT', + TLSClientKeyExchange: 'CLIENT_KEY_EXCHANGE_SENT', + # TLSServerKeyExchange: 'xxx', + TLSPlaintext: 'CLIENT_APPDATA_SENT', + TLSDecryptablePacket: 'CLIENT_APPDATA_SENT', } self.ACTIONS = {TLSClientHello: 'send_client_hello', TLSServerHello: 'recv_server_hello', @@ -79,11 +81,11 @@ def __init__(self, *args, **kwargs): TLSFinished: 'send_client_finish', TLSChangeCipherSpec: 'send_client_change_cipher_spec', TLSClientKeyExchange: 'send_client_key_exchange', - #TLSServerKeyExchange: 'xxx', + # TLSServerKeyExchange: 'xxx', TLSPlaintext: 'send_client_appdata', TLSDecryptablePacket: 'send_client_appdata', } - + def parse_args(self, target, tls_version='TLS_1_1', @@ -98,19 +100,19 @@ def parse_args(self, self.cipher_suites = cipher_suites self.timeout = timeout self.tlssock = None - + def register_callback(self, fname, f): - self.debug(1,"register callback: %s - %s"%(fname, repr(f))) + self.debug(1, "register callback: %s - %s" % (fname, repr(f))) self.callbacks[fname] = f - + def run(self, *args, **kwargs): """tin: ugly hack Part II: fix {state:condition_funcs} map to use hookable(f) instead of f """ for name in self.conditions: - self.conditions[name] = [getattr(cf,'wrapper_f', cf) for cf in self.conditions[name]] + self.conditions[name] = [getattr(cf, 'wrapper_f', cf) for cf in self.conditions[name]] return Automaton.run(self, *args, **kwargs) - + # GENERIC BEGIN @hookable @ATMT.state(initial=1) @@ -129,15 +131,15 @@ def is_connected(self): def not_connected(self): if not self.tlssock: raise self.CONNECTED() - + @hookable @ATMT.action(not_connected) def do_connect(self): - sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(self.target) - self.debug(1,"connected") + self.debug(1, "connected") self.tlssock = TLSSocket(sock, client=True) - + @hookable @ATMT.state() def CONNECTED(self): @@ -148,15 +150,19 @@ def CONNECTED(self): @ATMT.condition(CONNECTED) def send_client_hello(self): raise self.CLIENT_HELLO_SENT() - + @hookable @ATMT.action(send_client_hello) def do_send_client_hello(self): - client_hello = TLSRecord(version=self.tls_version) / TLSHandshake() / TLSClientHello(version=self.tls_version, - compression_methods=[TLSCompressionMethod.NULL,], - cipher_suites=self.cipher_suites) + client_hello = TLSRecord( + version=self.tls_version) / TLSHandshake() / TLSClientHello( + version=self.tls_version, + compression_methods=[ + TLSCompressionMethod.NULL, + ], + cipher_suites=self.cipher_suites) self.tlssock.sendall(client_hello) - + @hookable @ATMT.state() def CLIENT_HELLO_SENT(self): @@ -169,9 +175,14 @@ def recv_server_hello(self): p = self.tlssock.recvall(timeout=self.timeout) if not p.haslayer(TLSServerHello): raise self.ERROR(p) - self.debug(10,"CIPHER: %s"%TLS_CIPHER_SUITES.get(p[TLSServerHello].cipher_suite,p[TLSServerHello].cipher_suite)) + self.debug( + 10, + "CIPHER: %s" % + TLS_CIPHER_SUITES.get( + p[TLSServerHello].cipher_suite, + p[TLSServerHello].cipher_suite)) raise self.SERVER_HELLO_RECV() - + @hookable @ATMT.state() def SERVER_HELLO_RECV(self): @@ -182,27 +193,28 @@ def SERVER_HELLO_RECV(self): @ATMT.condition(SERVER_HELLO_RECV) def send_client_key_exchange(self): raise self.CLIENT_KEY_EXCHANGE_SENT() - + @hookable @ATMT.action(send_client_key_exchange) def do_send_client_key_exchange(self): - tls_version = self.tlssock.tls_ctx.params.negotiated.version - client_key_exchange = TLSRecord(version=tls_version) / TLSHandshake() / self.tlssock.tls_ctx.get_client_kex_data() + tls_version = self.tlssock.tls_ctx.negotiated.version + client_key_exchange = TLSRecord( + version=tls_version) / TLSHandshake() / self.tlssock.tls_ctx.get_client_kex_data() self.tlssock.sendall(client_key_exchange) @hookable @ATMT.state() def CLIENT_KEY_EXCHANGE_SENT(self): pass - + @hookable @ATMT.condition(CLIENT_KEY_EXCHANGE_SENT) def send_client_change_cipher_spec(self): - tls_version = self.tlssock.tls_ctx.params.negotiated.version + tls_version = self.tlssock.tls_ctx.negotiated.version client_ccs = TLSRecord(version=tls_version) / TLSChangeCipherSpec() self.tlssock.sendall(client_ccs) raise self.CLIENT_CHANGE_CIPHERSPEC_SENT() - + @hookable @ATMT.state() def CLIENT_CHANGE_CIPHERSPEC_SENT(self): @@ -214,7 +226,7 @@ def send_client_finish(self): finished = to_raw(TLSFinished(), self.tlssock.tls_ctx) self.tlssock.sendall(finished) raise self.CLIENT_FINISH_SENT() - + @hookable @ATMT.state() def CLIENT_FINISH_SENT(self): @@ -225,11 +237,11 @@ def CLIENT_FINISH_SENT(self): @ATMT.condition(CLIENT_FINISH_SENT) def recv_server_finish(self): p = self.tlssock.recvall(timeout=self.timeout) - if not (p.haslayer(TLSFinished) or - (p.haslayer(TLSPlaintext) and SSL(str(TLSRecord(content_type='handshake')/p[TLSPlaintext].data)).haslayer(TLSFinished))): + if not (p.haslayer(TLSFinished) or (p.haslayer(TLSPlaintext) and SSL( + str(TLSRecord(content_type='handshake') / p[TLSPlaintext].data)).haslayer(TLSFinished))): raise self.ERROR(p) raise self.SERVER_FINISH_RECV() - + @hookable @ATMT.state() def SERVER_FINISH_RECV(self): @@ -240,7 +252,7 @@ def SERVER_FINISH_RECV(self): @ATMT.condition(SERVER_FINISH_RECV) def send_client_appdata(self): raise self.CLIENT_APPDATA_SENT() - + @hookable @ATMT.action(recv_server_finish) def do_send_client_appdata(self): @@ -256,10 +268,10 @@ def CLIENT_APPDATA_SENT(self): @ATMT.condition(CLIENT_APPDATA_SENT) def recv_server_appdata(self): p = self.tlssock.recvall(timeout=self.timeout) - if not (p.haslayer(TLSRecord) and p[TLSRecord].content_type==TLSContentType.APPLICATION_DATA): + if not (p.haslayer(TLSRecord) and p[TLSRecord].content_type == TLSContentType.APPLICATION_DATA): raise self.ERROR(p) raise self.SERVER_APPDATA_RECV(p) - + @hookable @ATMT.state() def SERVER_APPDATA_RECV(self, p=None): @@ -281,10 +293,12 @@ def END(self, p=None): return ''.join(pkt[TLSPlaintext].data for pkt in p.records) except AttributeError: return p - + + class TLSServerAutomata(Automaton): + """"A Simple TLS Server Automata - + TLSServerAutomata.graph() with open(server_pem,'r') as f: pemcert = f.read() @@ -296,20 +310,21 @@ class TLSServerAutomata(Automaton): response="HTTP/1.1 200 OK\r\n\r\n") auto_srv.run() """ + def __init__(self, *args, **kwargs): - self.callbacks = {} # fname:func + self.callbacks = {} # fname:func Automaton.__init__(self, *args, **kwargs) self.STATES = {TLSClientHello: 'CLIENT_HELLO_RECV', - TLSServerHello: 'SERVER_HELLO_SENT', - TLSCertificate: 'SERVER_CERTIFICATES_SENT', - TLSCertificateList: 'SERVER_CERTIFICATES_SENT', - TLSServerHelloDone: 'SERVER_HELLO_DONE_SENT', - TLSFinished: 'SERVER_FINISH_SENT', - TLSChangeCipherSpec: 'SERVER_CCS_SENT', - TLSClientKeyExchange: 'CLIENT_KEY_EXCHANGE_RECV', - TLSServerKeyExchange: 'SERVER_KEY_EXCHANGE_SENT', - TLSPlaintext: 'SERVER_APPDATA_SENT', - TLSDecryptablePacket: 'SERVER_APPDATA_SENT', + TLSServerHello: 'SERVER_HELLO_SENT', + TLSCertificate: 'SERVER_CERTIFICATES_SENT', + TLSCertificateList: 'SERVER_CERTIFICATES_SENT', + TLSServerHelloDone: 'SERVER_HELLO_DONE_SENT', + TLSFinished: 'SERVER_FINISH_SENT', + TLSChangeCipherSpec: 'SERVER_CCS_SENT', + TLSClientKeyExchange: 'CLIENT_KEY_EXCHANGE_RECV', + TLSServerKeyExchange: 'SERVER_KEY_EXCHANGE_SENT', + TLSPlaintext: 'SERVER_APPDATA_SENT', + TLSDecryptablePacket: 'SERVER_APPDATA_SENT', } self.ACTIONS = {TLSClientHello: 'recv_client_hello', TLSServerHello: 'send_server_hello', @@ -323,14 +338,14 @@ def __init__(self, *args, **kwargs): TLSPlaintext: 'send_server_appdata', TLSDecryptablePacket: 'send_server_appdata', } - - def parse_args(self, - bind, + + def parse_args(self, + bind, pemcert, pemkey=None, - response="HTTP/1.1 200 OK\r\n\r\n", + response="HTTP/1.1 200 OK\r\n\r\n", cipher_suite=TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA, - timeout=4.0, + timeout=4.0, **kwargs): Automaton.parse_args(self, **kwargs) self.bind = bind @@ -343,58 +358,59 @@ def parse_args(self, self.tlssock = None self.srv_sock = None self.peer = None - + pemo = pem_get_objects(self.pemcert) for key_pk in (k for k in pemo.keys() if "CERTIFICATE" in k.upper()): - self.dercert = ''.join(line for line in pemo[key_pk].get("full").strip().split("\n") if not "-" in line).decode("base64") + self.dercert = ''.join( + line for line in pemo[key_pk].get("full").strip().split("\n") if not "-" in line).decode("base64") break - self.debug(1,"parse_args - done") - + self.debug(1, "parse_args - done") + def register_callback(self, fname, f): - self.debug(1,"register callback: %s - %s"%(fname, repr(f))) + self.debug(1, "register callback: %s - %s" % (fname, repr(f))) self.callbacks[fname] = f - + def run(self, *args, **kwargs): """tin: ugly hack Part II: fix {state:condition_funcs} map to use hookable(f) instead of f """ for name in self.conditions: - self.conditions[name] = [getattr(cf,'wrapper_f', cf) for cf in self.conditions[name]] + self.conditions[name] = [getattr(cf, 'wrapper_f', cf) for cf in self.conditions[name]] return Automaton.run(self, *args, **kwargs) # GENERIC BEGIN @hookable @ATMT.state(initial=1) def BEGIN(self): - self.debug(1,"BEGIN") - + self.debug(1, "BEGIN") + @hookable @ATMT.condition(BEGIN) def listen(self): raise self.WAIT_FOR_CLIENT_CONNECTION() - + @hookable @ATMT.action(listen) def do_bind(self): - self.debug(1,"dobind %s "%repr(self.bind)) - self.srv_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + self.debug(1, "dobind %s " % repr(self.bind)) + self.srv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.srv_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) self.srv_tls_sock = TLSSocket(self.srv_sock, client=False) self.srv_tls_sock.bind(self.bind) self.srv_tls_sock.listen(1) - + pemo = pem_get_objects(self.pemkey) for key_pk in (k for k in pemo.keys() if "PRIVATE" in k.upper()): - self.srv_tls_sock.tls_ctx.crypto.server.rsa.privkey, self.srv_tls_sock.tls_ctx.crypto.server.rsa.pubkey = self.srv_tls_sock.tls_ctx._rsa_load_keys(pemo[key_pk].get("full")) + self.srv_tls_sock.tls_ctx.server_ctx.asym_keystore = tlsk.RSAKeystore.from_private(pemo[key_pk].get("full")) break @hookable @ATMT.state() def WAIT_FOR_CLIENT_CONNECTION(self): - self.debug(1,"accept") + self.debug(1, "accept") self.tlssock, self.peer = self.srv_tls_sock.accept() - self.debug(1,"new connection: %s"%repr(self.peer)) - + self.debug(1, "new connection: %s" % repr(self.peer)) + @hookable @ATMT.condition(WAIT_FOR_CLIENT_CONNECTION) def recv_client_hello(self): @@ -405,54 +421,55 @@ def recv_client_hello(self): raise self.ERROR(p) self.tls_version = p[TLSClientHello].version raise self.CLIENT_HELLO_RECV() - + @hookable @ATMT.state() def CLIENT_HELLO_RECV(self): pass - + @ATMT.condition(CLIENT_HELLO_RECV) def send_server_hello(self): raise self.SERVER_HELLO_SENT() - + @hookable @ATMT.action(send_server_hello) def do_send_server_hello(self): rec_hs = TLSRecord(version=self.tls_version) / TLSHandshake() - server_hello = rec_hs/TLSServerHello(version=self.tls_version, - compression_method=TLSCompressionMethod.NULL, - cipher_suite=self.cipher_suite) + server_hello = rec_hs / TLSServerHello(version=self.tls_version, + compression_method=TLSCompressionMethod.NULL, + cipher_suite=self.cipher_suite) server_hello.show() self.tlssock.sendall(server_hello) - + @hookable @ATMT.state() def SERVER_HELLO_SENT(self): pass - + @hookable @ATMT.condition(SERVER_HELLO_SENT) def send_server_certificates(self): raise self.SERVER_CERTIFICATES_SENT() - + @hookable @ATMT.action(send_server_certificates) def do_send_server_certificates(self): rec_hs = TLSRecord(version=self.tls_version) / TLSHandshake() - server_certificates = rec_hs / TLSCertificateList(certificates=[TLSCertificate(data=x509.X509Cert(self.dercert))]) + server_certificates = rec_hs / \ + TLSCertificateList(certificates=[TLSCertificate(data=x509.X509Cert(self.dercert))]) server_certificates.show() self.tlssock.sendall(server_certificates) - + @hookable @ATMT.state() def SERVER_CERTIFICATES_SENT(self): pass - + @hookable @ATMT.condition(SERVER_CERTIFICATES_SENT) def send_server_hello_done(self): raise self.SERVER_HELLO_DONE_SENT() - + @hookable @ATMT.action(send_server_hello_done) def do_send_server_hello_done(self): @@ -460,12 +477,12 @@ def do_send_server_hello_done(self): (rec_hs / TLSServerHelloDone()).show2() server_hello_done = TLSRecord(version=self.tls_version) / TLSHandshake(type=TLSHandshakeType.SERVER_HELLO_DONE) self.tlssock.sendall(server_hello_done) - + @hookable @ATMT.state() def SERVER_HELLO_DONE_SENT(self): pass - + @hookable @ATMT.condition(SERVER_HELLO_DONE_SENT) def recv_client_key_exchange(self): @@ -475,27 +492,27 @@ def recv_client_key_exchange(self): if not p.haslayer(TLSClientKeyExchange): raise self.ERROR(p) raise self.CLIENT_KEY_EXCHANGE_RECV() - + @hookable @ATMT.state() def CLIENT_KEY_EXCHANGE_RECV(self): raise self.CLIENT_CHANGE_CIPHERSPEC_RECV() - + @hookable @ATMT.state() def CLIENT_CHANGE_CIPHERSPEC_RECV(self): raise self.CLIENT_FINISH_RECV() - + @hookable @ATMT.state() def CLIENT_FINISH_RECV(self): pass - + @hookable @ATMT.condition(CLIENT_FINISH_RECV) def send_server_key_exchange(self): raise self.SERVER_KEY_EXCHANGE_SENT() - + @hookable @ATMT.action(send_server_key_exchange) def do_send_server_key_exchange(self): @@ -504,8 +521,8 @@ def do_send_server_key_exchange(self): @hookable @ATMT.state() def SERVER_KEY_EXCHANGE_SENT(self): - pass - + pass + @hookable @ATMT.condition(SERVER_KEY_EXCHANGE_SENT) def send_server_ccs(self): @@ -514,7 +531,7 @@ def send_server_ccs(self): @hookable @ATMT.action(send_server_ccs) def do_send_server_ccs(self): - tls_version = self.tlssock.tls_ctx.params.negotiated.version + tls_version = self.tlssock.tls_ctx.negotiated.version client_ccs = TLSRecord(version=tls_version) / TLSChangeCipherSpec() self.tlssock.sendall(client_ccs) @@ -522,64 +539,64 @@ def do_send_server_ccs(self): @ATMT.state() def SERVER_CCS_SENT(self): pass - + @hookable @ATMT.condition(SERVER_CCS_SENT) def send_server_finish(self): raise self.SERVER_FINISH_SENT() - + @hookable @ATMT.action(send_server_finish) def do_send_server_finish(self): - #TODO: fix server finish calculation + # TODO: fix server finish calculation finished = to_raw(TLSFinished(), self.tlssock.tls_ctx) self.tlssock.sendall(finished) - + @hookable @ATMT.state() def SERVER_FINISH_SENT(self): pass - + @hookable @ATMT.condition(SERVER_FINISH_SENT) def recv_client_appdata(self): chunk_p = True p = SSL() - self.debug(1,"polling in 5sec intervals until data arrives.") + self.debug(1, "polling in 5sec intervals until data arrives.") while chunk_p: chunk_p = self.tlssock.recvall(timeout=5) if chunk_p.haslayer(SSL) and len(chunk_p[SSL].records) > 0: p.records.append(chunk_p) else: - if len(p[SSL].records)>0: + if len(p[SSL].records) > 0: break - + if self.debug_level >= 1: p.show() - if not (p.haslayer(TLSRecord) and p[TLSRecord].content_type ==TLSContentType.APPLICATION_DATA): + if not (p.haslayer(TLSRecord) and p[TLSRecord].content_type == TLSContentType.APPLICATION_DATA): raise self.ERROR(p) raise self.CLIENT_APPDATA_RECV() - + @hookable @ATMT.state() def CLIENT_APPDATA_RECV(self): pass - + @hookable @ATMT.condition(CLIENT_APPDATA_RECV) def send_server_appdata(self): raise self.SERVER_APPDATA_SENT() - + @hookable @ATMT.action(send_server_appdata) def do_send_server_appdata(self): self.tlssock.sendall(to_raw(TLSPlaintext(data=self.response), self.tlssock.tls_ctx)) - + @hookable @ATMT.state() def SERVER_APPDATA_SENT(self): raise self.END() - + # GENERIC ERROR - print received data if available @hookable @ATMT.state(error=1) @@ -587,9 +604,14 @@ def ERROR(self, p=None): if p and self.debug_level >= 1: p.show() return - + # GENERIC END - return server's response @hookable @ATMT.state(final=1) def END(self): - self.tlssock.sendall(to_raw(TLSAlert(level=TLSAlertLevel.WARNING, description=TLSAlertDescription.CLOSE_NOTIFY), self.tlssock.tls_ctx)) + self.tlssock.sendall( + to_raw( + TLSAlert( + level=TLSAlertLevel.WARNING, + description=TLSAlertDescription.CLOSE_NOTIFY), + self.tlssock.tls_ctx)) diff --git a/scapy_ssl_tls/ssl_tls_crypto.py b/scapy_ssl_tls/ssl_tls_crypto.py index 6ddf0fc..a7c9182 100644 --- a/scapy_ssl_tls/ssl_tls_crypto.py +++ b/scapy_ssl_tls/ssl_tls_crypto.py @@ -12,6 +12,7 @@ import warnings import pkcs7 import ssl_tls as tls +import ssl_tls_keystore as tlsk import tinyec.ec as ec import tinyec.registry as ec_reg @@ -20,11 +21,9 @@ from Crypto.Hash import HMAC, MD5, SHA, SHA256, SHA384 from Crypto.PublicKey import DSA, RSA from Crypto.Signature import PKCS1_v1_5 as Sig_PKCS1_v1_5 -from Crypto.Util.asn1 import DerSequence -from scapy.asn1.asn1 import ASN1_SEQUENCE -''' +""" https://tools.ietf.org/html/rfc4346#section-6.3 key_block = PRF(SecurityParameters.master_secret, "key expansion", @@ -35,85 +34,83 @@ server_write_MAC_secret[SecurityParameters.hash_size] client_write_key[SecurityParameters.key_material_length] server_write_key[SecurityParameters.key_material_length] -''' +""" + +REX_PEM = re.compile(r"(\-+BEGIN\s*([^\-]+)\-+(.*?)\-+END[^\-]+\-+)", re.DOTALL) + -REX_PEM = re.compile(r'(\-+BEGIN\s*([^\-]+)\-+(.*?)\-+END[^\-]+\-+)', re.DOTALL) def pem_get_objects(data): d = {} for full, pemtype, pemdata in REX_PEM.findall(data): - d[pemtype]={'data':data, - 'full':full} + d[pemtype] = {"data": data, + "full": full} return d -def x509_extract_pubkey_from_der(der_certificate): - # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280) - try: - # try to extract pubkey from scapy.layers.x509 X509Cert type in case - # der_certificate is of type X509Cert - # Note: der_certificate may not be of type X509Cert if it wasn't - # received completely, in that case, we'll try to extract it anyway - # using the old method. - # TODO: get rid of the old method and always expect X509Cert obj ? - ''' - Rebuild ASN1 SubjectPublicKeyInfo since X509Cert does not provide the full struct - - ASN1F_SEQUENCE( - ASN1F_SEQUENCE(ASN1F_OID("pubkey_algo","1.2.840.113549.1.1.1"), - ASN1F_field("pk_value",ASN1_NULL(0))), - ASN1F_BIT_STRING("pubkey","") - ), - ''' - subjectPublicKeyInfo = ASN1_SEQUENCE([ ASN1_SEQUENCE([der_certificate.pubkey_algo, - der_certificate.pk_value]), - der_certificate.pubkey,]) - return RSA.importKey(str(subjectPublicKeyInfo)) - except AttributeError: - pass - # Fallback method, may pot. allow to extract pubkey from incomplete der streams - cert = DerSequence() - cert.decode(der_certificate) +def int_to_str(int_): + hex_ = "%x" % int_ + return binascii.unhexlify("%s%s" % ("" if len(hex_) % 2 == 0 else "0", hex_)) - tbsCertificate = DerSequence() - tbsCertificate.decode(cert[0]) # first DER SEQUENCE - # search for pubkey OID: rsaEncryption: "1.2.840.113549.1.1.1" - # hex: 06 09 2A 86 48 86 F7 0D 01 01 01 - subjectPublicKeyInfo=None - for seq in tbsCertificate: - if not isinstance(seq,basestring): continue # skip numerics and non sequence stuff - if "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01" in seq: - subjectPublicKeyInfo=seq +def str_to_int(str_): + return int(binascii.hexlify(str_), 16) - if not subjectPublicKeyInfo: - raise ValueError("could not find OID rsaEncryption 1.2.840.113549.1.1.1 in certificate") - # Initialize RSA key - return RSA.importKey(subjectPublicKeyInfo) +def ansi_str_to_point(str_): + if not str_.startswith("\x04"): + raise ValueError("ANSI octet string missing point prefix (0x04)") + str_ = str_[1:] + if len(str_) % 2 != 0: + raise ValueError("Can't parse curve point. Odd ANSI string length") + half = len(str_) // 2 + return str_to_int(str_[:half]), str_to_int(str_[half:]) -def x509_extract_pubkey_from_pem(public_key_string): - #https://github.com/m4droid/U-Pasaporte/blob/7a00b344e97bb05265fd726f4125f0966dca6a5a/upasaporte/__init__.py - # Convert from PEM to DER - lines = public_key_string.replace(" ",'').split() - der = binascii.a2b_base64(''.join(lines[1:-1])) - return x509_extract_pubkey_from_der(der) +def point_to_ansi_str(point): + return "\x04%s%s" % (int_to_str(point.x), int_to_str(point.y)) -def int_to_str(int_): - hex_ = "%x" % int_ - return binascii.unhexlify("%s%s" % ("" if len(hex_) % 2 == 0 else "0", hex_)) +class TLSContext(object): + def __init__(self, name): + self.name = name + self.handshake = None + self.sequence = 0 + self.random = None + self.session_id = None + self.enc = None + self.dec = None + self.hmac = None + self.compression = None + self.asym_keystore = tlsk.EmptyAsymKeystore() + self.kex_keystore = tlsk.EmptyKexKeystore() + self.sym_keystore = tlsk.EmptySymKeyStore() -def str_to_ec_point(ansi_str, ec_curve): - if not ansi_str.startswith("\x04"): - raise ValueError("ANSI octet string missing point prefix (0x04)") - ansi_str = ansi_str[1:] - if len(ansi_str) % 2 != 0: - raise ValueError("Can't parse curve point. Odd ANSI string length") - str_to_int = lambda x: int(binascii.hexlify(x), 16) - x, y = str_to_int(ansi_str[:len(ansi_str) // 2]), str_to_int(ansi_str[len(ansi_str) // 2:]) - return ec.Point(ec_curve, x, y) + def load_rsa_keys_from_file(self, key_file, client=False): + with open(key_file, "r") as f: + # _rsa_load_keys expects one pem/der key per file. + pemo = pem_get_objects(f.read()) + for key_pk in (k for k in pemo.keys() if "PRIVATE" in k.upper()): + try: + self.asym_keystore = tlsk.RSAKeystore.from_private(pemo[key_pk].get("full")) + return + except ValueError: + pass + raise ValueError("Unable to load PRIVATE key from pem file: %s" % key_file) + + def load_rsa_keys(self, private): + self.asym_keystore = tlsk.RSAKeystore.from_private(private) + + def __str__(self): + template = """ + {name}: + random: {random} + session_id: {sess_id} + {asym_ks} + {kex_ks} + {sym_ks}""" + return template.format(name=self.name, random=repr(self.random), sess_id=repr(self.session_id), + asym_ks=self.asym_keystore, kex_ks=self.kex_keystore, sym_ks=self.sym_keystore) class TLSSessionCtx(object): @@ -121,477 +118,296 @@ class TLSSessionCtx(object): def __init__(self, client=True): self.client = client self.server = not self.client - self.packets = namedtuple('packets',['history','client','server']) - self.packets.history=[] #packet history + self.client_ctx = TLSContext("Client TLS context") + self.server_ctx = TLSContext("Server TLS context") + # packet history + self.history = [] + self.requires_iv = False self.sec_params = None - self.packets.client = namedtuple('client',['sequence']) - self.packets.client.sequence=0 - self.packets.server = namedtuple('server',['sequence']) - self.packets.server.sequence=0 - - self.params = namedtuple('params', ['handshake', - 'negotiated',]) - self.params.handshake = namedtuple('handshake',['client','server']) - self.params.handshake.client=None - self.params.handshake.server=None - self.params.negotiated = namedtuple('negotiated', ['ciphersuite', - 'key_exchange', - 'encryption', - 'mac', - 'compression', - "compression_algo", - "version", - "sig" - ]) - self.params.negotiated.ciphersuite=None - self.params.negotiated.key_exchange=None - self.params.negotiated.encryption=None - self.params.negotiated.mac=None - self.params.negotiated.compression=None - self.params.negotiated.compression_algo = None - self.params.negotiated.version = None - self.params.negotiated.sig = None - self.compression = namedtuple("compression", ["method"]) - self.compression.method = None - self.crypto = namedtuple('crypto', ['client','server']) - self.crypto.client = namedtuple('client', ['enc', 'dec', "hmac"]) - self.crypto.client.enc = None - self.crypto.client.dec = None - self.crypto.client.hmac = None - self.crypto.client.rsa = namedtuple("rsa", ["pubkey","privkey"]) - self.crypto.client.rsa.pubkey = None - self.crypto.client.rsa.privkey = None - self.crypto.client.dsa = namedtuple("dsa", ["pubkey","privkey"]) - self.crypto.client.dsa.pubkey = None - self.crypto.client.dsa.privkey = None - self.crypto.client.dh = namedtuple("dh", ["x", "y_c"]) - self.crypto.client.dh.x = None - self.crypto.client.dh.y_c = None - self.crypto.client.ecdh = namedtuple("ecdh", ["curve_name", "priv", "pub"]) - self.crypto.client.ecdh.curve_name = None - self.crypto.client.ecdh.priv = None - self.crypto.client.ecdh.pub = None - self.crypto.server = namedtuple('server', ['enc','dec','rsa', "hmac"]) - self.crypto.server.enc = None - self.crypto.server.dec = None - self.crypto.server.hmac = None - self.crypto.server.rsa = namedtuple("rsa", ["pubkey","privkey"]) - self.crypto.server.rsa.pubkey = None - self.crypto.server.rsa.privkey = None - self.crypto.server.dsa = namedtuple("dsa", ["pubkey","privkey"]) - self.crypto.server.dsa.pubkey = None - self.crypto.server.dsa.privkey = None - self.crypto.server.dh = namedtuple("dh", ["p", "g", "x", "y_s"]) - self.crypto.server.dh.p = None - self.crypto.server.dh.g = None - self.crypto.server.dh.x = None - self.crypto.server.dh.y_s = None - self.crypto.server.ecdh = namedtuple("ecdh", ["curve_name", "priv", "pub"]) - self.crypto.server.ecdh.curve_name = None - self.crypto.server.ecdh.priv = None - self.crypto.server.ecdh.pub = None - self.crypto.session = namedtuple('session', ["encrypted_premaster_secret", - 'premaster_secret', - 'master_secret', - "prf"]) - - self.crypto.session.encrypted_premaster_secret = None - self.crypto.session.premaster_secret = None - self.crypto.session.master_secret = None - self.crypto.session.prf = None - self.crypto.session.resume = False - self.crypto.session.randombytes = namedtuple('randombytes',['client','server']) - self.crypto.session.randombytes.client = None - self.crypto.session.randombytes.server = None - - self.crypto.session.key = namedtuple('key',['client','server']) - self.crypto.session.key.server = namedtuple('server',['mac','encryption','iv', "seq_num"]) - self.crypto.session.key.server.mac = None - self.crypto.session.key.server.encryption = None - self.crypto.session.key.server.iv = None - self.crypto.session.key.server.seq_num = 0 - - self.crypto.session.key.client = namedtuple('client',['mac','encryption','iv', "seq_num"]) - self.crypto.session.key.client.mac = None - self.crypto.session.key.client.encryption = None - self.crypto.session.key.client.iv = None - self.crypto.session.key.client.seq_num = 0 - - self.crypto.session.key.length = namedtuple('length',['mac','encryption','iv']) - self.crypto.session.key.length.mac = None - self.crypto.session.key.length.encryption = None - self.crypto.session.key.length.iv = None - - def __repr__(self): - params = {'id':id(self), - 'params-handshake-client':repr(self.params.handshake.client), - 'params-handshake-server':repr(self.params.handshake.server), - "params-negotiated-version":tls.TLS_VERSIONS[self.params.negotiated.version], - 'params-negotiated-ciphersuite':tls.TLS_CIPHER_SUITES[self.params.negotiated.ciphersuite], - 'params-negotiated-key_exchange':self.params.negotiated.key_exchange, - 'params-negotiated-encryption':self.params.negotiated.encryption, - 'params-negotiated-mac':self.params.negotiated.mac, - 'params-negotiated-compression':tls.TLS_COMPRESSION_METHODS[self.params.negotiated.compression], - - 'crypto-client-enc':repr(self.crypto.client.enc), - 'crypto-client-dec':repr(self.crypto.client.dec), - 'crypto-server-enc':repr(self.crypto.server.enc), - 'crypto-server-dec':repr(self.crypto.server.dec), - - "crypto-client-rsa-pubkey": repr(self.crypto.client.rsa.pubkey), - "crypto-client-rsa-privkey": repr(self.crypto.client.rsa.privkey), - "crypto-server-rsa-pubkey": repr(self.crypto.server.rsa.pubkey), - "crypto-server-rsa-privkey": repr(self.crypto.server.rsa.privkey), - - "crypto-client-dsa-pubkey": repr(self.crypto.client.dsa.pubkey), - "crypto-client-dsa-privkey": repr(self.crypto.client.dsa.privkey), - "crypto-server-dsa-pubkey": repr(self.crypto.server.dsa.pubkey), - "crypto-server-dsa-privkey": repr(self.crypto.server.dsa.privkey), - - "crypto-client-dh-x": repr(self.crypto.client.dh.x), - "crypto-client-dh-y_c": repr(self.crypto.client.dh.y_c), - "crypto-server-dh-p": repr(self.crypto.server.dh.p), - "crypto-server-dh-g": repr(self.crypto.server.dh.g), - "crypto-server-dh-x": repr(self.crypto.server.dh.x), - "crypto-server-dh-y_s": repr(self.crypto.server.dh.y_s), - - "crypto-client-ecdh-curve_name": repr(self.crypto.client.ecdh.curve_name), - "crypto-client-ecdh-priv": repr(self.crypto.client.ecdh.priv), - "crypto-client-ecdh-pub": repr(self.crypto.client.ecdh.pub), - "crypto-server-ecdh-curve_name": repr(self.crypto.server.ecdh.curve_name), - "crypto-server-ecdh-priv": repr(self.crypto.server.ecdh.priv), - "crypto-server-ecdh-pub": repr(self.crypto.server.ecdh.pub), - - 'crypto-session-encrypted_premaster_secret':repr(self.crypto.session.encrypted_premaster_secret), - 'crypto-session-premaster_secret':repr(self.crypto.session.premaster_secret), - 'crypto-session-master_secret':repr(self.crypto.session.master_secret), - - 'crypto-session-randombytes-client':repr(self.crypto.session.randombytes.client), - 'crypto-session-randombytes-server':repr(self.crypto.session.randombytes.server), - - 'crypto-session-key-server-mac':repr(self.crypto.session.key.server.mac), - 'crypto-session-key-server-encryption':repr(self.crypto.session.key.server.encryption), - 'crypto-session-key-server-iv':repr(self.crypto.session.key.server.iv), - - 'crypto-session-key-client-mac':repr(self.crypto.session.key.client.mac), - 'crypto-session-key-client-encryption':repr(self.crypto.session.key.client.encryption), - 'crypto-session-key-client-iv':repr(self.crypto.session.key.client.iv), - - 'crypto-session-key-length-mac':self.crypto.session.key.length.mac, - 'crypto-session-key-length-encryption':self.crypto.session.key.length.encryption, - 'crypto-session-key-length-iv':self.crypto.session.key.length.iv, - } - - - str_ = " tls.TLSVersion.TLS_1_0 else False + self.negotiated.ciphersuite = server_hello.cipher_suite + self.negotiated.compression = server_hello.compression_method + try: + self.negotiated.compression_algo = TLSCompressionParameters.comp_params[self.negotiated.compression]["name"] + self.server_ctx.compression = TLSCompressionParameters.comp_params[self.negotiated.compression]["type"] + self.client_ctx.compression = TLSCompressionParameters.comp_params[self.negotiated.compression]["type"] + except KeyError: + warnings.warn("Compression method 0x%02x not supported. Compression operations will fail" % + self.negotiated.compression) - def _process(self,p): + self.prf = TLSPRF(self.negotiated.version) + try: + self.negotiated.key_exchange = \ + TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["key_exchange"]["name"] + self.negotiated.sig = TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["key_exchange"][ + "sig"] + self.negotiated.encryption = ( + TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["cipher"]["name"], + TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["cipher"]["key_len"], + TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["cipher"]["mode_name"]) + self.negotiated.mac = TLSSecurityParameters.crypto_params[self.negotiated.ciphersuite]["hash"]["name"] + except KeyError: + warnings.warn("Cipher 0x%04x not supported. Crypto operations will fail" % self.negotiated.ciphersuite) + if self.negotiated.resumption: + self.prf = TLSPRF(self.negotiated.version) + self.sec_params = TLSSecurityParameters.from_master_secret(self.prf, + self.negotiated.ciphersuite, + self.master_secret, + self.client_ctx.random, + self.server_ctx.random, + self.requires_iv) + self.__generate_secrets() + + def __handle_cert_list(self, cert_list): + if self.negotiated.key_exchange is not None and ( + self.negotiated.key_exchange == tls.TLSKexNames.RSA or self.negotiated.sig == RSA): + # fetch server pubkey // PKCS1_v1_5 + cert = cert_list.certificates[0].data + # If we have a default keystore, create an RSA keystore and populate it from data on the wire + if isinstance(self.server_ctx.asym_keystore, tlsk.EmptyAsymKeystore): + self.server_ctx.asym_keystore = tlsk.RSAKeystore.from_der_certificate(str(cert)) + # Else keystore was assigned by user. Just add cert from the wire to the store + else: + self.server_ctx.asym_keystore.certificate = str(cert) + # TODO: Handle DSA sig key loading here to allow sig checks + elif self.negotiated.key_exchange is not None and self.negotiated.sig == DSA: + # Pycryptodoesn't currently have an interface to this. + # Filed bug https://github.com/dlitz/pycrypto/issues/137 + # Pycryptodome has this interface. Implement after #74 + pass + # TODO: In the future also handle kex = DH/ECDH and extract static DH/ECDH params from cert + + def __handle_server_kex(self, server_kex): + # DHE case + if server_kex.haslayer(tls.TLSServerDHParams): + if isinstance(self.server_ctx.kex_keystore, tlsk.EmptyKexKeystore): + p = str_to_int(server_kex[tls.TLSServerDHParams].p) + g = str_to_int(server_kex[tls.TLSServerDHParams].g) + public = str_to_int(server_kex[tls.TLSServerDHParams].y_s) + self.server_ctx.kex_keystore = tlsk.DHKeyStore(g, p, public) + elif server_kex.haslayer(tls.TLSServerECDHParams): + if isinstance(self.server_ctx.kex_keystore, tlsk.EmptyKexKeystore): + try: + curve_id = server_kex[tls.TLSServerECDHParams].curve_name + # TODO: DO NOT assume uncompressed EC points! + point = ansi_str_to_point(server_kex[tls.TLSServerECDHParams].p) + curve_name = tls.TLS_ELLIPTIC_CURVES[curve_id] + # Unknown curve case. Just record raw values, but do nothing with them + except KeyError: + self.server_ctx.kex_keystore = tlsk.ECDHKeyStore(None, point) + warnings.warn("Unknown elliptic curve id: %d. Client KEX calculation is up to you" % curve_id) + # We are on a known curve + else: + try: + curve = ec_reg.get_curve(curve_name) + self.server_ctx.kex_keystore = tlsk.ECDHKeyStore(curve, ec.Point(curve, *point)) + except ValueError: + self.server_ctx.kex_keystore = tlsk.ECDHKeyStore(None, point) + warnings.warn("Unsupported elliptic curve: %s" % curve_name) + else: + warnings.warn("Unknown server key exchange") + + def __handle_client_kex(self, client_kex): + if client_kex.haslayer(tls.TLSClientRSAParams): + self.encrypted_premaster_secret = client_kex[tls.TLSClientRSAParams].data + # If we have the private key, let's decrypt the PMS + private = self.server_ctx.asym_keystore.private + if private is not None: + # I have no clue why pycrypto started failing after refactoring, missing this function + # Probably related to https://github.com/dlitz/pycrypto/issues/160 + # TODO: workaround for now... Find root cause of pycrypto bug + from Crypto import Random + private._randfunc = Random.new().read + # End workaround + self.premaster_secret = PKCS1_v1_5.new(private).decrypt(self.encrypted_premaster_secret, None) + elif client_kex.haslayer(tls.TLSClientDHParams): + # Check if we have an unitialized keystore, and if so build a new one + if isinstance(self.client_ctx.kex_keystore, tlsk.EmptyKexKeystore): + server_kex_keystore = self.server_ctx.kex_keystore + # Check if server side is a DH keystore. Something is messed up otherwise + if isinstance(server_kex_keystore, tlsk.DHKeyStore): + client_public = str_to_int(client_kex[tls.TLSClientDHParams].data) + self.client_ctx.kex_keystore = tlsk.DHKeyStore(server_kex_keystore.g, + server_kex_keystore.p, client_public) + else: + raise RuntimeError("Server keystore is not a DH keystore") + # TODO: Calculate PMS + elif client_kex.haslayer(tls.TLSClientECDHParams): + # Check if we have an unitialized keystore, and if so build a new one + if isinstance(self.client_ctx.kex_keystore, tlsk.EmptyKexKeystore): + server_kex_keystore = self.server_ctx.kex_keystore + # Check if server side is a ECDH keystore. Something is messed up otherwise + if isinstance(server_kex_keystore, tlsk.ECDHKeyStore): + curve = server_kex_keystore.curve + point = ansi_str_to_point(client_kex[tls.TLSClientECDHParams].data) + self.client_ctx.kex_keystore = tlsk.ECDHKeyStore(curve, ec.Point(curve, *point)) + # TODO: Calculate PMS + else: + warnings.warn("Unknown client key exchange") + self.sec_params = TLSSecurityParameters.from_pre_master_secret(self.prf, self.negotiated.ciphersuite, + self.premaster_secret, self.client_ctx.random, + self.server_ctx.random, self.requires_iv) + self.__generate_secrets() + + def __generate_secrets(self): + if isinstance(self.client_ctx.sym_keystore, tlsk.EmptySymKeyStore): + self.client_ctx.sym_keystore = self.sec_params.client_keystore + if isinstance(self.server_ctx.sym_keystore, tlsk.EmptySymKeyStore): + self.server_ctx.sym_keystore = self.sec_params.server_keystore + self.master_secret = self.sec_params.master_secret + # Retrieve ciphers used for client/server encryption and decryption + self.client_ctx.enc = self.sec_params.get_client_enc_cipher() + self.client_ctx.dec = self.sec_params.get_client_dec_cipher() + self.client_ctx.hmac = self.sec_params.get_client_hmac() + self.server_ctx.enc = self.sec_params.get_server_enc_cipher() + self.server_ctx.dec = self.sec_params.get_server_dec_cipher() + self.server_ctx.hmac = self.sec_params.get_server_hmac() + + def _process(self, pkt): """ fill context """ - if p.haslayer(tls.TLSHandshake): + if pkt.haslayer(tls.TLSHandshake): # requires handshake messages - if p.haslayer(tls.TLSClientHello): - if not self.params.handshake.client: - - self.params.handshake.client = p[tls.TLSClientHello] - self.params.negotiated.version = p[tls.TLSClientHello].version - # fetch randombytes for crypto stuff - if not self.crypto.session.randombytes.client: - self.crypto.session.randombytes.client = struct.pack("!I", p[tls.TLSClientHello].gmt_unix_time) + p[tls.TLSClientHello].random_bytes - # Generate a random PMS. Overriden at decryption time if private key is provided - if self.crypto.session.premaster_secret is None: - self.crypto.session.premaster_secret = self._generate_random_pms(self.params.negotiated.version) - if p.haslayer(tls.TLSServerHello): - if not self.params.handshake.server: - self.params.handshake.server = p[tls.TLSServerHello] - self.params.negotiated.version = p[tls.TLSServerHello].version - self.crypto.session.prf = TLSPRF(self.params.negotiated.version) - #fetch randombytes - if not self.crypto.session.randombytes.server: - self.crypto.session.randombytes.server = struct.pack("!I", p[tls.TLSServerHello].gmt_unix_time) + p[tls.TLSServerHello].random_bytes - # negotiated params - if not self.params.negotiated.ciphersuite: - self.params.negotiated.ciphersuite = p[tls.TLSServerHello].cipher_suite - self.params.negotiated.compression = p[tls.TLSServerHello].compression_method - try: - self.params.negotiated.compression_algo = TLSCompressionParameters.comp_params[self.params.negotiated.compression]["name"] - self.compression.method = TLSCompressionParameters.comp_params[self.params.negotiated.compression]["type"] - except KeyError: - warnings.warn("Compression method 0x%02x not supported. Compression operations will fail" % - self.params.negotiated.compression) - # Raises RuntimeError if we do not handle the cipher - try: - self.params.negotiated.key_exchange = TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["key_exchange"]["name"] - self.params.negotiated.sig = TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["key_exchange"]["sig"] - self.params.negotiated.encryption = (TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["cipher"]["name"], - TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["cipher"]["key_len"], - TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["cipher"]["mode_name"]) - self.params.negotiated.mac = TLSSecurityParameters.crypto_params[self.params.negotiated.ciphersuite]["hash"]["name"] - except KeyError: - warnings.warn("Cipher 0x%04x not supported. Crypto operations will fail" % - self.params.negotiated.ciphersuite) - - if self.crypto.session.resume: - explicit_iv = True if self.params.negotiated.version > tls.TLSVersion.TLS_1_0 else False - self.crypto.session.prf = TLSPRF(self.params.negotiated.version) - self.sec_params = TLSSecurityParameters.from_master_secret(self.crypto.session.prf, - self.params.negotiated.ciphersuite, - self.crypto.session.master_secret, - self.crypto.session.randombytes.client, - self.crypto.session.randombytes.server, - explicit_iv) - self._assign_crypto_material(self.sec_params) - - if p.haslayer(tls.TLSCertificateList): - # TODO: Probably don't want to do that if rsa_load_priv*() is called - if self.params.negotiated.key_exchange is not None and (self.params.negotiated.key_exchange == tls.TLSKexNames.RSA or self.params.negotiated.sig == RSA): - # fetch server pubkey // PKCS1_v1_5 - cert = p[tls.TLSCertificateList].certificates[0].data - self.crypto.server.rsa.pubkey = x509_extract_pubkey_from_der(str(cert)) - # TODO: In the future also handle kex = DH and extract static DH params from cert - elif self.params.negotiated.key_exchange is not None and self.params.negotiated.sig == DSA: - # TODO: Handle DSA sig key loading here to allow sig checks - # Pycrypto doesn't currently have an interface to this. - # Filed bug https://github.com/dlitz/pycrypto/issues/137 - # Could port the change manually from master - # Could move to cryptography.io which also supports TLS1.2 AES GCM modes - pass - - if p.haslayer(tls.TLSServerKeyExchange): - # DHE case - if p.haslayer(tls.TLSServerDHParams): - self.crypto.server.dh.p = p[tls.TLSServerDHParams].p - self.crypto.server.dh.g = p[tls.TLSServerDHParams].g - self.crypto.server.dh.y_s = p[tls.TLSServerDHParams].y_s - if p.haslayer(tls.TLSServerECDHParams): - try: - self.crypto.server.ecdh.curve_name = tls.TLS_ELLIPTIC_CURVES[p[tls.TLSServerECDHParams].curve_name] - # Unknown cuve case. Just record raw values, but do nothing with them - except KeyError: - self.crypto.server.ecdh.curve_name = p[tls.TLSServerECDHParams].curve_name - self.crypto.server.ecdh.pub = p[tls.TLSServerECDHParams].p - warnings.warn("Unknown elliptic curve. Client KEX calculation is up to you") - # We are on a known curve - else: - # TODO: DO not assume uncompressed EC points! - # Uncompressed EC points are recorded in ANSI format => \x04 + x_point + y_point - ansi_ec_point_str = p[tls.TLSServerECDHParams].p - try: - ec_curve = ec_reg.get_curve(self.crypto.server.ecdh.curve_name) - self.crypto.server.ecdh.pub = str_to_ec_point(ansi_ec_point_str, ec_curve) - except ValueError: - warnings.warn("Unsupported elliptic curve: %s" % self.crypto.server.ecdh.curve_name) - - # calculate key material - if p.haslayer(tls.TLSClientKeyExchange): - if p.haslayer(tls.TLSClientRSAParams): - self.crypto.session.encrypted_premaster_secret = p[tls.TLSClientRSAParams].data - # If we have the private key, let's decrypt the PMS - if self.crypto.server.rsa.privkey is not None: - self.crypto.session.premaster_secret = PKCS1_v1_5.new(self.crypto.server.rsa.privkey).decrypt( - self.crypto.session.encrypted_premaster_secret, None) - elif p.haslayer(tls.TLSClientDHParams): - self.crypto.client.dh.y_c = p[tls.TLSClientDHParams].data - elif p.haslayer(tls.TLSClientECDHParams): - ec_curve = ec_reg.get_curve(self.crypto.server.ecdh.curve_name) - self.crypto.client.ecdh.pub = str_to_ec_point(p[tls.TLSClientECDHParams].data, ec_curve) - - explicit_iv = True if self.params.negotiated.version > tls.TLSVersion.TLS_1_0 else False - self.sec_params = TLSSecurityParameters.from_pre_master_secret(self.crypto.session.prf, - self.params.negotiated.ciphersuite, - self.crypto.session.premaster_secret, - self.crypto.session.randombytes.client, - self.crypto.session.randombytes.server, - explicit_iv) - self._assign_crypto_material(self.sec_params) - - def _assign_crypto_material(self, sec_params): - self.crypto.session.key.length.mac = sec_params.negotiated_crypto_param["hash"]["type"].digest_size - self.crypto.session.key.length.encryption = sec_params.negotiated_crypto_param["cipher"]["key_len"] - self.crypto.session.key.length.iv = sec_params.negotiated_crypto_param["cipher"]["type"].block_size - - self.crypto.session.master_secret = sec_params.master_secret - - self.crypto.session.key.server.mac = sec_params.server_write_MAC_key - self.crypto.session.key.server.encryption = sec_params.server_write_key - self.crypto.session.key.server.iv = sec_params.server_write_IV - - self.crypto.session.key.client.mac = sec_params.client_write_MAC_key - self.crypto.session.key.client.encryption = sec_params.client_write_key - self.crypto.session.key.client.iv = sec_params.client_write_IV - - # Retrieve ciphers used for client/server encryption and decryption - self.crypto.client.enc = sec_params.get_client_enc_cipher() - self.crypto.client.dec = sec_params.get_client_dec_cipher() - self.crypto.client.hmac = sec_params.get_client_hmac() - self.crypto.server.enc = sec_params.get_server_enc_cipher() - self.crypto.server.dec = sec_params.get_server_dec_cipher() - self.crypto.server.hmac = sec_params.get_server_hmac() - - def _rsa_load_keys(self, priv_key): - priv_key = RSA.importKey(priv_key) - pub_key = priv_key.publickey() - return priv_key, pub_key - - def rsa_load_keys_from_file(self, priv_key_file, client=False): - with open(priv_key_file,'r') as f: - # _rsa_load_keys expects one pem/der key per file. - pemo = pem_get_objects(f.read()) - for key_pk in (k for k in pemo.keys() if "PRIVATE" in k.upper()): - try: - if client: - self.crypto.client.rsa.privkey, self.crypto.client.rsa.pubkey = self._rsa_load_keys(pemo[key_pk].get("full")) - else: - self.crypto.server.rsa.privkey, self.crypto.server.rsa.pubkey = self._rsa_load_keys(pemo[key_pk].get("full")) - return - except ValueError: - pass - raise ValueError("Unable to load PRIVATE key from pem file: %s"%priv_key_file) - - def rsa_load_keys(self, priv_key, client=False): - if client: - self.crypto.client.rsa.privkey, self.crypto.client.rsa.pubkey = self._rsa_load_keys(priv_key) - else: - self.crypto.server.rsa.privkey, self.crypto.server.rsa.pubkey = self._rsa_load_keys(priv_key) + if pkt.haslayer(tls.TLSClientHello): + self.__handle_client_hello(pkt[tls.TLSClientHello]) + if pkt.haslayer(tls.TLSServerHello): + self.__handle_server_hello(pkt[tls.TLSServerHello]) + if pkt.haslayer(tls.TLSCertificateList): + self.__handle_cert_list(pkt[tls.TLSCertificateList]) + if pkt.haslayer(tls.TLSServerKeyExchange): + self.__handle_server_kex(pkt[tls.TLSServerKeyExchange]) + if pkt.haslayer(tls.TLSClientKeyExchange): + self.__handle_client_kex(pkt[tls.TLSClientKeyExchange]) def _generate_random_pms(self, version): return "%s%s" % (struct.pack("!H", version), os.urandom(46)) def get_encrypted_pms(self, pms=None): - cleartext = pms or self.crypto.session.premaster_secret - if self.crypto.server.rsa.pubkey is not None: - self.crypto.session.encrypted_premaster_secret = PKCS1_v1_5.new(self.crypto.server.rsa.pubkey).\ - encrypt(cleartext) + cleartext = pms or self.premaster_secret + public = self.server_ctx.asym_keystore.public + if public is not None: + self.encrypted_premaster_secret = PKCS1_v1_5.new(public).encrypt(cleartext) else: raise ValueError("Cannot calculate encrypted MS. No server certificate found in connection") - return self.crypto.session.encrypted_premaster_secret - - def get_client_dh_pubkey(self, priv_key=None): - # ValueError is propagated to caller both if hex or int conversion fail - import math - import random - str_to_int = lambda x: int(binascii.hexlify(x), 16) - nb_bits = lambda x: int(math.ceil(math.log(x) / math.log(2))) - p = str_to_int(self.crypto.server.dh.p) - g = str_to_int(self.crypto.server.dh.g) - y_s = str_to_int(self.crypto.server.dh.y_s) - # Client private key - # Long story short, this provides 128bits of key space (sqrt(2**256)). TLS leaves this up to the implementation. - # Another option is to gather random.randint(0, 2**nb_bits(p) - 1), but has little added security - # In our case, since we don't care about security, it really doesn't matter what we pick - a = priv_key or random.randint(0, 2**256 - 1) - self.crypto.client.dh.x = int_to_str(a) - self.crypto.client.dh.y_c = int_to_str(pow(g, a, p)) + return self.encrypted_premaster_secret + + def get_client_dh_pubkey(self, private=None): + if not isinstance(self.server_ctx.kex_keystore, tlsk.DHKeyStore): + raise RuntimeError("Server keystore is not DH") + g = self.server_ctx.kex_keystore.g + p = self.server_ctx.kex_keystore.p + public = self.server_ctx.kex_keystore.public + self.client_ctx.kex_keystore = tlsk.DHKeyStore.new_keypair(g, p, private) + pms = self.client_ctx.kex_keystore.get_psk(public) # Per RFC 4346 section 8.1.2 # Leading bytes of Z that contain all zero bits are stripped before it is used as the # pre_master_secret. - self.crypto.session.premaster_secret = int_to_str(pow(y_s, a, p)).lstrip("\x00") - return self.crypto.client.dh.y_c - - def get_client_ecdh_pubkey(self, priv_key=None): - # Will raise ValueError for unknown curves - ec_curve = ec_reg.get_curve(self.crypto.server.ecdh.curve_name) - server_keypair = ec.Keypair(ec_curve, pub=self.crypto.server.ecdh.pub) - if priv_key is None: - client_keypair = ec.make_keypair(ec_curve) + self.premaster_secret = int_to_str(pms).lstrip("\x00") + return int_to_str(self.client_ctx.kex_keystore.public) + + def get_client_ecdh_pubkey(self, private=None): + if not isinstance(self.server_ctx.kex_keystore, tlsk.ECDHKeyStore): + raise RuntimeError("Server keystore is not ECDH") + if self.server_ctx.kex_keystore.unknown_curve: + raise RuntimeError("Unknown EC. KEX calculation is up to you") + + curve = self.server_ctx.kex_keystore.curve + server_keypair = self.server_ctx.kex_keystore.keys + if private is None: + client_keypair = ec.make_keypair(curve) else: - client_keypair = ec.Keypair(ec_curve, priv_key) - self.crypto.client.ecdh.priv = int_to_str(client_keypair.priv) - self.crypto.client.ecdh.pub = client_keypair.pub + client_keypair = ec.Keypair(curve, private) + self.client_ctx.kex_keystore = tlsk.ECDHKeyStore.from_keypair(curve, client_keypair) + secret_point = ec.ECDH(client_keypair).get_secret(server_keypair) # PMS is x coordinate of secret - self.crypto.session.premaster_secret = int_to_str(secret_point.x) - return "\x04%s%s" % (int_to_str(client_keypair.pub.x), int_to_str(client_keypair.pub.y)) + self.premaster_secret = int_to_str(secret_point.x) + return point_to_ansi_str(client_keypair.pub) def get_client_kex_data(self, val=None): - if self.params.negotiated.key_exchange == tls.TLSKexNames.RSA: + if self.negotiated.key_exchange == tls.TLSKexNames.RSA: return tls.TLSClientKeyExchange(ctx=self) / tls.TLSClientRSAParams(data=self.get_encrypted_pms(val)) - elif self.params.negotiated.key_exchange == tls.TLSKexNames.DHE: + elif self.negotiated.key_exchange == tls.TLSKexNames.DHE: return tls.TLSClientKeyExchange(ctx=self) / tls.TLSClientDHParams(data=self.get_client_dh_pubkey(val)) - elif self.params.negotiated.key_exchange == tls.TLSKexNames.ECDHE: + elif self.negotiated.key_exchange == tls.TLSKexNames.ECDHE: return tls.TLSClientKeyExchange(ctx=self) / tls.TLSClientECDHParams(data=self.get_client_ecdh_pubkey(val)) else: raise NotImplementedError("Key exchange unknown or currently not supported") def _walk_handshake_msgs(self): - for pkt in self.packets.history: + for pkt in self.history: for handshake in (r[tls.TLSHandshake] for r in pkt if r.haslayer(tls.TLSHandshake)): if not handshake.haslayer(tls.TLSHelloRequest): yield handshake @@ -613,15 +429,15 @@ def get_verify_data(self, data=None): else: verify_data = [data] - if self.params.negotiated.version == tls.TLSVersion.TLS_1_2: - prf_verify_data = self.crypto.session.prf.get_bytes(self.crypto.session.master_secret, label, - SHA256.new("".join(verify_data)).digest(), - num_bytes=12) + if self.negotiated.version == tls.TLSVersion.TLS_1_2: + prf_verify_data = self.prf.get_bytes(self.master_secret, label, + SHA256.new("".join(verify_data)).digest(), + num_bytes=12) else: - prf_verify_data = self.crypto.session.prf.get_bytes(self.crypto.session.master_secret, label, - "%s%s" % (MD5.new("".join(verify_data)).digest(), - SHA.new("".join(verify_data)).digest()), - num_bytes=12) + prf_verify_data = self.prf.get_bytes(self.master_secret, label, + "%s%s" % (MD5.new("".join(verify_data)).digest(), + SHA.new("".join(verify_data)).digest()), + num_bytes=12) return prf_verify_data def get_handshake_hash(self, hash_): @@ -630,21 +446,21 @@ def get_handshake_hash(self, hash_): return hash_ def get_client_signed_handshake_hash(self, hash_=SHA256.new(), pre_sign_hook=None): - if self.crypto.client.rsa.privkey is None: + if self.client_ctx.asym_keystore.private is None: raise RuntimeError("Missing client private key. Can't sign") msg_hash = self.get_handshake_hash(hash_) if pre_sign_hook is not None: msg_hash = pre_sign_hook(msg_hash) # Will throw exception if we can't sign or if data is larger the modulus - return Sig_PKCS1_v1_5.new(self.crypto.client.rsa.privkey).sign(msg_hash) + return Sig_PKCS1_v1_5.new(self.client_ctx.asym_keystore.private).sign(msg_hash) def set_mode(self, client=None, server=None): self.client = client if client else not server self.server = not self.client def resume_session(self, master_secret): - self.crypto.session.master_secret = master_secret - self.crypto.session.resume = True + self.master_secret = master_secret + self.negotiated.resumption = True class TLSPRF(object): @@ -696,25 +512,25 @@ def __init__(self, tls_ctx, data="", content_type=tls.TLSContentType.APPLICATION raise ValueError("Valid TLS session context required") self.tls_ctx = tls_ctx is_cbc = self.tls_ctx.sec_params.negotiated_crypto_param["cipher"]["mode"] != None - if self.tls_ctx.params.negotiated.version > tls.TLSVersion.TLS_1_0 and is_cbc: - self.explicit_iv = os.urandom(self.tls_ctx.crypto.session.key.length.iv) + if self.tls_ctx.negotiated.version > tls.TLSVersion.TLS_1_0 and is_cbc: + self.explicit_iv = os.urandom(self.tls_ctx.server_ctx.sym_keystore.iv_size // 8) else: self.explicit_iv = "" self.data = data - self.version = tls_ctx.params.negotiated.version + self.version = tls_ctx.negotiated.version self.content_type = content_type self.pkcs7 = pkcs7.PKCS7Encoder() if self.tls_ctx.client: # TODO: Needs concurrent safety if this ever goes concurrent - self.hmac_handler = tls_ctx.crypto.client.hmac - self.enc_cipher = tls_ctx.crypto.client.enc - self.seq_number = tls_ctx.crypto.session.key.client.seq_num - self.tls_ctx.crypto.session.key.client.seq_num += 1 + self.hmac_handler = tls_ctx.client_ctx.hmac + self.enc_cipher = tls_ctx.client_ctx.enc + self.seq_number = tls_ctx.client_ctx.sequence + self.tls_ctx.client_ctx.sequence += 1 else: - self.hmac_handler = tls_ctx.crypto.server.hmac - self.enc_cipher = tls_ctx.crypto.server.enc - self.seq_number = tls_ctx.crypto.session.key.server.seq_num - self.tls_ctx.crypto.session.key.server.seq_num += 1 + self.hmac_handler = tls_ctx.server_ctx.hmac + self.enc_cipher = tls_ctx.server_ctx.enc + self.seq_number = tls_ctx.server_ctx.sequence + self.tls_ctx.server_ctx.sequence += 1 # CBC mode self.hmac() if is_cbc: @@ -737,7 +553,7 @@ def pad(self): # "\xff" is a dummy trailing byte, to increase the length of imput # data by one byte. Any byte could do. This is to account for the # trailing padding_length byte in the RFC - self.padding = self.pkcs7.get_padding("%s%s\xff" %(self.data, self.mac)) + self.padding = self.pkcs7.get_padding("%s%s\xff" % (self.data, self.mac)) def __str__(self): if len(self.padding) != 0: @@ -753,7 +569,9 @@ def encrypt(self, data=None): """ return self.enc_cipher.encrypt(data or str(self)) + class NullCipher(object): + """ Implements a pycrypto like interface for the Null Cipher """ @@ -770,7 +588,9 @@ def encrypt(self, cleartext): def decrypt(self, ciphertext): return ciphertext + class NullHash(object): + """ Implements a pycrypto like interface for the Null Hash """ @@ -816,68 +636,68 @@ class ECDSA(object): class TLSSecurityParameters(object): crypto_params = { - tls.TLSCipherSuite.NULL_WITH_NULL_NULL: {"name":tls.TLS_CIPHER_SUITES[0x0000], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":NullHash, "name":"Null"}}, - tls.TLSCipherSuite.RSA_WITH_NULL_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0001], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":MD5, "name":"MD5"}}, - tls.TLSCipherSuite.RSA_WITH_NULL_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0002], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_EXPORT_WITH_RC4_40_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0003], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC4, "name":"RC4", "key_len":5, "mode":None, "mode_name":"Stream"}, "hash":{"type":MD5, "name":"MD5"}}, - tls.TLSCipherSuite.RSA_WITH_RC4_128_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0004], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC4, "name":"RC4", "key_len":16, "mode":None, "mode_name":"Stream"}, "hash":{"type":MD5, "name":"MD5"}}, - tls.TLSCipherSuite.RSA_WITH_RC4_128_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0005], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC4, "name":"RC4", "key_len":16, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_EXPORT_WITH_RC2_CBC_40_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0006], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC2, "name":"RC2", "key_len":5, "mode":ARC2.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":MD5, "name":"MD5"}}, - # 0x0007: RSA_WITH_IDEA_CBC_SHA => IDEA support would require python openssl bindings - tls.TLSCipherSuite.RSA_EXPORT_WITH_DES40_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0008], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":DES, "name":"DES", "key_len":5, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_WITH_DES_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0009], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":DES, "name":"DES", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_WITH_3DES_EDE_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x000a], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":DES3, "name":"DES3", "key_len":24, "mode":DES3.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x002f], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_WITH_AES_256_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0035], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_WITH_NULL_SHA256: {"name":tls.TLS_CIPHER_SUITES[0x003b], "export":False, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":SHA256, "name":"SHA256"}}, - tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC4_56_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0060], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC4, "name":"RC4", "key_len":8, "mode":None, "mode_name":"Stream"}, "hash":{"type":MD5, "name":"MD5"}}, - tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC2_CBC_56_MD5: {"name":tls.TLS_CIPHER_SUITES[0x0061], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC2, "name":"RC2", "key_len":8, "mode":ARC2.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":MD5, "name":"MD5"}}, - tls.TLSCipherSuite.RSA_EXPORT1024_WITH_DES_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0062], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":DES, "name":"DES", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC4_56_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0064], "export":True, "key_exchange":{"type":RSA, "name":tls.TLSKexNames.RSA, "sig":None}, "cipher":{"type":ARC4, "name":"RC4", "key_len":8, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - # 0x0084: RSA_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto - tls.TLSCipherSuite.DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0011], "export":True, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":DES, "name":"DES", "key_len":5, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_WITH_DES_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0012], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":DES, "name":"DES", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_WITH_3DES_EDE_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0013], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":DES3, "name":"DES3", "key_len":24, "mode":DES3.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0014], "export":True, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":RSA}, "cipher":{"type":DES, "name":"DES", "key_len":5, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_RSA_WITH_DES_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0015], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":RSA}, "cipher":{"type":DES, "name":"DES", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_RSA_WITH_3DES_EDE_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0016], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":RSA}, "cipher":{"type":DES3, "name":"DES3", "key_len":24, "mode":DES3.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0032], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0033], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_WITH_AES_256_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0038], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_RSA_WITH_AES_256_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0039], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0063], "export":True, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":DES, "name":"DES", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_EXPORT1024_WITH_RC4_56_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0065], "export":True, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":ARC4, "name":"RC4", "key_len":8, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.DHE_DSS_WITH_RC4_128_SHA: {"name":tls.TLS_CIPHER_SUITES[0x0066], "export":False, "key_exchange":{"type":DHE, "name":tls.TLSKexNames.DHE, "sig":DSA}, "cipher":{"type":ARC4, "name":"RC4", "key_len":16, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_NULL_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc006], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_RC4_128_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc007], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":ARC4, "name":"RC4", "key_len":16, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc008], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":DES3, "name":"DES3", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc009], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc00a], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_NULL_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc010], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":NullCipher, "name":"Null", "key_len":0, "mode":None, "mode_name":""}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_RC4_128_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc011], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":ARC4, "name":"RC4", "key_len":16, "mode":None, "mode_name":"Stream"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc012], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":DES3, "name":"DES3", "key_len":8, "mode":DES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc013], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_256_CBC_SHA: {"name":tls.TLS_CIPHER_SUITES[0xc014], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA, "name":"SHA"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {"name":tls.TLS_CIPHER_SUITES[0xc023], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA256, "name":"SHA256"}}, - tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: {"name":tls.TLS_CIPHER_SUITES[0xc024], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":ECDSA}, "cipher":{"type":AES, "name":"AES", "key_len":32, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA384, "name":"SHA384"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256: {"name":tls.TLS_CIPHER_SUITES[0xc027], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA256, "name":"SHA256"}}, - tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_256_CBC_SHA384: {"name":tls.TLS_CIPHER_SUITES[0xc028], "export":False, "key_exchange":{"type":ECDHE, "name":tls.TLSKexNames.ECDHE, "sig":RSA}, "cipher":{"type":AES, "name":"AES", "key_len":16, "mode":AES.MODE_CBC, "mode_name":"CBC"}, "hash":{"type":SHA384, "name":"SHA384"}}, - - # 0x0087: DHE_DSS_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto - # 0x0088: DHE_RSA_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto - } + tls.TLSCipherSuite.NULL_WITH_NULL_NULL: {"name": tls.TLS_CIPHER_SUITES[0x0000], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": NullHash, "name": "Null"}}, + tls.TLSCipherSuite.RSA_WITH_NULL_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0001], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": MD5, "name": "MD5"}}, + tls.TLSCipherSuite.RSA_WITH_NULL_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0002], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_EXPORT_WITH_RC4_40_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0003], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 5, "mode": None, "mode_name": "Stream"}, "hash": {"type": MD5, "name": "MD5"}}, + tls.TLSCipherSuite.RSA_WITH_RC4_128_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0004], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 16, "mode": None, "mode_name": "Stream"}, "hash": {"type": MD5, "name": "MD5"}}, + tls.TLSCipherSuite.RSA_WITH_RC4_128_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0005], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 16, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_EXPORT_WITH_RC2_CBC_40_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0006], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC2, "name": "RC2", "key_len": 5, "mode": ARC2.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": MD5, "name": "MD5"}}, + # 0x0007: RSA_WITH_IDEA_CBC_SHA => IDEA support would require python openssl bindings + tls.TLSCipherSuite.RSA_EXPORT_WITH_DES40_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0008], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": DES, "name": "DES", "key_len": 5, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_WITH_DES_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0009], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": DES, "name": "DES", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_WITH_3DES_EDE_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x000a], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": DES3, "name": "DES3", "key_len": 24, "mode": DES3.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x002f], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_WITH_AES_256_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0035], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_WITH_NULL_SHA256: {"name": tls.TLS_CIPHER_SUITES[0x003b], "export": False, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": SHA256, "name": "SHA256"}}, + tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC4_56_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0060], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 8, "mode": None, "mode_name": "Stream"}, "hash": {"type": MD5, "name": "MD5"}}, + tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC2_CBC_56_MD5: {"name": tls.TLS_CIPHER_SUITES[0x0061], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC2, "name": "RC2", "key_len": 8, "mode": ARC2.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": MD5, "name": "MD5"}}, + tls.TLSCipherSuite.RSA_EXPORT1024_WITH_DES_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0062], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": DES, "name": "DES", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.RSA_EXPORT1024_WITH_RC4_56_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0064], "export": True, "key_exchange": {"type": RSA, "name": tls.TLSKexNames.RSA, "sig": None}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 8, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + # 0x0084: RSA_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto + tls.TLSCipherSuite.DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0011], "export": True, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": DES, "name": "DES", "key_len": 5, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_WITH_DES_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0012], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": DES, "name": "DES", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_WITH_3DES_EDE_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0013], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": DES3, "name": "DES3", "key_len": 24, "mode": DES3.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0014], "export": True, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": RSA}, "cipher": {"type": DES, "name": "DES", "key_len": 5, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_RSA_WITH_DES_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0015], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": RSA}, "cipher": {"type": DES, "name": "DES", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_RSA_WITH_3DES_EDE_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0016], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": RSA}, "cipher": {"type": DES3, "name": "DES3", "key_len": 24, "mode": DES3.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_WITH_AES_128_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0032], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_RSA_WITH_AES_128_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0033], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_WITH_AES_256_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0038], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_RSA_WITH_AES_256_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0039], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0063], "export": True, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": DES, "name": "DES", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_EXPORT1024_WITH_RC4_56_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0065], "export": True, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 8, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.DHE_DSS_WITH_RC4_128_SHA: {"name": tls.TLS_CIPHER_SUITES[0x0066], "export": False, "key_exchange": {"type": DHE, "name": tls.TLSKexNames.DHE, "sig": DSA}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 16, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_NULL_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc006], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_RC4_128_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc007], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 16, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc008], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": DES3, "name": "DES3", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc009], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc00a], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_NULL_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc010], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": NullCipher, "name": "Null", "key_len": 0, "mode": None, "mode_name": ""}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_RC4_128_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc011], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": ARC4, "name": "RC4", "key_len": 16, "mode": None, "mode_name": "Stream"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc012], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": DES3, "name": "DES3", "key_len": 8, "mode": DES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc013], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_256_CBC_SHA: {"name": tls.TLS_CIPHER_SUITES[0xc014], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA, "name": "SHA"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {"name": tls.TLS_CIPHER_SUITES[0xc023], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA256, "name": "SHA256"}}, + tls.TLSCipherSuite.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: {"name": tls.TLS_CIPHER_SUITES[0xc024], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": ECDSA}, "cipher": {"type": AES, "name": "AES", "key_len": 32, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA384, "name": "SHA384"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_128_CBC_SHA256: {"name": tls.TLS_CIPHER_SUITES[0xc027], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA256, "name": "SHA256"}}, + tls.TLSCipherSuite.ECDHE_RSA_WITH_AES_256_CBC_SHA384: {"name": tls.TLS_CIPHER_SUITES[0xc028], "export": False, "key_exchange": {"type": ECDHE, "name": tls.TLSKexNames.ECDHE, "sig": RSA}, "cipher": {"type": AES, "name": "AES", "key_len": 16, "mode": AES.MODE_CBC, "mode_name": "CBC"}, "hash": {"type": SHA384, "name": "SHA384"}}, + + # 0x0087: DHE_DSS_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto + # 0x0088: DHE_RSA_WITH_CAMELLIA_256_CBC_SHA => Camelia support should use camcrypt or the camelia patch for pycrypto + } # Unsupported for now, until GCM/CCM and SRP are integrated # SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xc021 # SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xc022 # TLS_FALLBACK_SCSV = 0x5600 -# 0xc02b: 'ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', -# 0xc02c: 'ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', -# 0xc02f: 'ECDHE_RSA_WITH_AES_128_GCM_SHA256', -# 0xc030: 'ECDHE_RSA_WITH_AES_256_GCM_SHA384', -# 0xc0ac: 'ECDHE_ECDSA_WITH_AES_128_CCM', -# 0xc0ad: 'ECDHE_ECDSA_WITH_AES_256_CCM', -# 0xc0ae: 'ECDHE_ECDSA_WITH_AES_128_CCM_8', -# 0xc0af: 'ECDHE_ECDSA_WITH_AES_256_CCM_8', +# 0xc02b: "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", +# 0xc02c: "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", +# 0xc02f: "ECDHE_RSA_WITH_AES_128_GCM_SHA256", +# 0xc030: "ECDHE_RSA_WITH_AES_256_GCM_SHA384", +# 0xc0ac: "ECDHE_ECDSA_WITH_AES_128_CCM", +# 0xc0ad: "ECDHE_ECDSA_WITH_AES_256_CCM", +# 0xc0ae: "ECDHE_ECDSA_WITH_AES_128_CCM_8", +# 0xc0af: "ECDHE_ECDSA_WITH_AES_256_CCM_8", def __init__(self, prf, cipher_suite, client_random, server_random, explicit_iv=False): try: @@ -898,22 +718,25 @@ def __init__(self, prf, cipher_suite, client_random, server_random, explicit_iv= self.iv_length = 0 if block_size == 1 else block_size self.explicit_iv = explicit_iv self.prf = prf - self.pms = "" - self.master_secret = "" + self.pms = b"" + self.master_secret = b"" + self.client_keystore, self.server_keystore = [tlsk.EmptySymKeyStore()] * 2 @classmethod def from_pre_master_secret(cls, prf, cipher_suite, pms, client_random, server_random, explicit_iv=False): sec_params = cls(prf, cipher_suite, client_random, server_random, explicit_iv) sec_params.pms = pms sec_params.generate_master_secret(pms, client_random, server_random) - sec_params.init_crypto(client_random, server_random, explicit_iv) + sec_params.client_keystore, sec_params.server_keystore = sec_params.init_crypto(client_random, server_random, + explicit_iv) return sec_params @classmethod def from_master_secret(cls, prf, cipher_suite, master_secret, client_random, server_random, explicit_iv=False): sec_params = cls(prf, cipher_suite, client_random, server_random, explicit_iv) sec_params.master_secret = master_secret - sec_params.init_crypto(client_random, server_random, explicit_iv) + sec_params.client_keystore, sec_params.server_keystore = sec_params.init_crypto(client_random, server_random, + explicit_iv) return sec_params def get_client_hmac(self): @@ -924,46 +747,51 @@ def get_server_hmac(self): def get_server_enc_cipher(self): if self.explicit_iv and self.cipher_mode is not None: - return self.cipher_type.new(self.server_write_key, mode=self.cipher_mode, IV=self.server_write_IV) + return self.cipher_type.new(self.server_keystore.key, mode=self.cipher_mode, IV=self.server_keystore.iv) else: return self.__server_enc_cipher def get_server_dec_cipher(self): if self.explicit_iv and self.cipher_mode is not None: - return self.cipher_type.new(self.server_write_key, mode=self.cipher_mode, IV=self.server_write_IV) + return self.cipher_type.new(self.server_keystore.key, mode=self.cipher_mode, IV=self.server_keystore.iv) else: return self.__server_dec_cipher def get_client_enc_cipher(self): if self.explicit_iv and self.cipher_mode is not None: - return self.cipher_type.new(self.client_write_key, mode=self.cipher_mode, IV=self.client_write_IV) + return self.cipher_type.new(self.client_keystore.key, mode=self.cipher_mode, IV=self.client_keystore.iv) else: return self.__client_enc_cipher def get_client_dec_cipher(self): if self.explicit_iv and self.cipher_mode is not None: - return self.cipher_type.new(self.client_write_key, mode=self.cipher_mode, IV=self.client_write_IV) + return self.cipher_type.new(self.client_keystore.key, mode=self.cipher_mode, IV=self.client_keystore.iv) else: return self.__client_dec_cipher def __init_key_material(self, data, explicit_iv): i = 0 - self.client_write_MAC_key = data[i:i+self.mac_key_length] + client_mac_key = data[i:i + self.mac_key_length] i += self.mac_key_length - self.server_write_MAC_key = data[i:i+self.mac_key_length] + server_mac_key = data[i:i + self.mac_key_length] i += self.mac_key_length - self.client_write_key = data[i:i+self.cipher_key_length] + client_key = data[i:i + self.cipher_key_length] i += self.cipher_key_length - self.server_write_key = data[i:i+self.cipher_key_length] + server_key = data[i:i + self.cipher_key_length] i += self.cipher_key_length if explicit_iv: - self.client_write_IV = "\x00"*self.iv_length - self.server_write_IV = "\x00"*self.iv_length + client_iv = "\x00" * self.iv_length + server_iv = "\x00" * self.iv_length else: - self.client_write_IV = data[i:i+self.iv_length] + client_iv = data[i:i + self.iv_length] i += self.iv_length - self.server_write_IV = data[i:i+self.iv_length] + server_iv = data[i:i + self.iv_length] i += self.iv_length + client_keystore = tlsk.CipherKeyStore(self.negotiated_crypto_param, client_key, client_mac_key, + client_iv) + server_keystore = tlsk.CipherKeyStore(self.negotiated_crypto_param, server_key, server_mac_key, + server_iv) + return client_keystore, server_keystore def generate_master_secret(self, pms, client_random, server_random): self.master_secret = self.prf.get_bytes(pms, TLSPRF.TLS_MD_MASTER_SECRET_CONST, @@ -975,39 +803,43 @@ def init_crypto(self, client_random, server_random, explicit_iv, master_secret=N master_secret = self.master_secret key_block = self.prf.get_bytes(master_secret, TLSPRF.TLS_MD_KEY_EXPANSION_CONST, server_random + client_random, num_bytes=2 * (self.mac_key_length + self.cipher_key_length + self.iv_length)) - self.__init_key_material(key_block, explicit_iv) + client_keystore, server_keystore = self.__init_key_material(key_block, explicit_iv) + self.cipher_mode = self.negotiated_crypto_param["cipher"]["mode"] self.cipher_type = self.negotiated_crypto_param["cipher"]["type"] self.hash_type = self.negotiated_crypto_param["hash"]["type"] # Block ciphers if self.cipher_mode is not None: - self.__client_enc_cipher = self.cipher_type.new(self.client_write_key, mode=self.cipher_mode, - IV=self.client_write_IV) - self.__client_dec_cipher = self.cipher_type.new(self.client_write_key, mode=self.cipher_mode, - IV=self.client_write_IV) - self.__server_enc_cipher = self.cipher_type.new(self.server_write_key, mode=self.cipher_mode, - IV=self.server_write_IV) - self.__server_dec_cipher = self.cipher_type.new(self.server_write_key, mode=self.cipher_mode, - IV=self.server_write_IV) + self.__client_enc_cipher = self.cipher_type.new(client_keystore.key, mode=self.cipher_mode, + IV=client_keystore.iv) + self.__client_dec_cipher = self.cipher_type.new(client_keystore.key, mode=self.cipher_mode, + IV=client_keystore.iv) + self.__server_enc_cipher = self.cipher_type.new(server_keystore.key, mode=self.cipher_mode, + IV=server_keystore.iv) + self.__server_dec_cipher = self.cipher_type.new(server_keystore.key, mode=self.cipher_mode, + IV=server_keystore.iv) # Stream ciphers else: - self.__client_enc_cipher = self.cipher_type.new(self.client_write_key) - self.__client_dec_cipher = self.cipher_type.new(self.client_write_key) - self.__server_enc_cipher = self.cipher_type.new(self.server_write_key) - self.__server_dec_cipher = self.cipher_type.new(self.server_write_key) - self.__client_hmac = HMAC.new(self.client_write_MAC_key, digestmod=self.hash_type) - self.__server_hmac = HMAC.new(self.server_write_MAC_key, digestmod=self.hash_type) + self.__client_enc_cipher = self.cipher_type.new(client_keystore.key) + self.__client_dec_cipher = self.cipher_type.new(client_keystore.key) + self.__server_enc_cipher = self.cipher_type.new(server_keystore.key) + self.__server_dec_cipher = self.cipher_type.new(server_keystore.key) + self.__client_hmac = HMAC.new(client_keystore.hmac, digestmod=self.hash_type) + self.__server_hmac = HMAC.new(server_keystore.hmac, digestmod=self.hash_type) + return client_keystore, server_keystore def __str__(self): - s=[] + s = [] for f in (f for f in dir(self) if "_write_" in f): - s.append( "%20s | %s"%(f,repr(getattr(self,f)))) + s.append("%20s | %s" % (f, repr(getattr(self, f)))) s.append("%20s| %s" % ("premaster_secret", repr(self.pms))) s.append("%20s| %s" % ("master_secret", repr(self.master_secret))) s.append("%20s| %s" % ("master_secret [bytes]", binascii.hexlify(self.master_secret))) return "\n".join(s) + class NullCompression(object): + """ Implements a zlib like interface for null compression """ @staticmethod @@ -1018,9 +850,8 @@ def compress(data): def decompress(data): return data + class TLSCompressionParameters(object): - comp_params = { - tls.TLSCompressionMethod.NULL: {"name":tls.TLS_COMPRESSION_METHODS[0x00], "type":NullCompression}, - tls.TLSCompressionMethod.DEFLATE: {"name":tls.TLS_COMPRESSION_METHODS[0x01], "type":zlib} - } + comp_params = {tls.TLSCompressionMethod.NULL: {"name": tls.TLS_COMPRESSION_METHODS[0x00], "type": NullCompression}, + tls.TLSCompressionMethod.DEFLATE: {"name": tls.TLS_COMPRESSION_METHODS[0x01], "type": zlib}} diff --git a/scapy_ssl_tls/ssl_tls_keystore.py b/scapy_ssl_tls/ssl_tls_keystore.py new file mode 100644 index 0000000..a726e4b --- /dev/null +++ b/scapy_ssl_tls/ssl_tls_keystore.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- + +import binascii +import math +import random + +from Crypto.PublicKey import RSA +from Crypto.Util.asn1 import DerSequence +from scapy.asn1.asn1 import ASN1_SEQUENCE +import tinyec.ec as ec + + +def rsa_public_from_der_certificate(certificate): + # Extract subject_public_key_info field from X.509 certificate (see RFC3280) + try: + # try to extract pubkey from scapy.layers.x509 X509Cert type in case + # der_certificate is of type X509Cert + # Note: der_certificate may not be of type X509Cert if it wasn't + # received completely, in that case, we'll try to extract it anyway + # using the old method. + # TODO: get rid of the old method and always expect X509Cert obj ? + """ + Rebuild ASN1 SubjectPublicKeyInfo since X509Cert does not provide the full struct + + ASN1F_SEQUENCE( + ASN1F_SEQUENCE(ASN1F_OID("pubkey_algo","1.2.840.113549.1.1.1"), + ASN1F_field("pk_value",ASN1_NULL(0))), + ASN1F_BIT_STRING("pubkey","") + ), + """ + subject_public_key_info = ASN1_SEQUENCE([ASN1_SEQUENCE([certificate.pubkey_algo, certificate.pk_value]), + certificate.pubkey]) + return RSA.importKey(str(subject_public_key_info)) + except AttributeError: + pass + + # Fallback method, may pot. allow to extract pubkey from incomplete der streams + cert = DerSequence() + cert.decode(certificate) + + tbs_certificate = DerSequence() + tbs_certificate.decode(cert[0]) # first DER SEQUENCE + + # search for pubkey OID: rsaEncryption: "1.2.840.113549.1.1.1" + # hex: 06 09 2A 86 48 86 F7 0D 01 01 01 + subject_public_key_info = None + for seq in tbs_certificate: + if not isinstance(seq, basestring): + continue # skip numerics and non sequence stuff + if "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01" in seq: + subject_public_key_info = seq + + if subject_public_key_info is None: + raise ValueError("could not find OID rsaEncryption 1.2.840.113549.1.1.1 in certificate") + + # Initialize RSA key + return RSA.importKey(subject_public_key_info) + + +def rsa_public_from_pem_certificate(certificate): + return rsa_public_from_der_certificate(pem_to_der(certificate)) + + +def pem_to_der(certificate): + # https://github.com/m4droid/U-Pasaporte/blob/7a00b344e97bb05265fd726f4125f0966dca6a5a/upasaporte/__init__.py + lines = certificate.replace(" ", "").split() + return binascii.a2b_base64("".join(lines[1:-1])) + + +def nb_bits(int_): + return int(math.ceil(math.log(int_) / math.log(2))) + + +class AsymKeyStore(object): + + def __init__(self, name, public, private=None): + self.name = name + self.private = private + self.public = public + if self.public is not None: + self.size = nb_bits(self.public.n) + else: + self.size = 0 + self.keys = (self.private, self.public) + self.certificate = None + + @classmethod + def from_private(cls, private): + raise NotImplementedError() + + def __str__(self): + template = """{name}: + certificate: {certificate} + size: {size} + public: {public} + private: {private}""" + return template.format(name=self.name, certificate=repr(self.certificate), size=self.size, public=self.public, + private=self.private) + + +class EmptyAsymKeystore(AsymKeyStore): + + def __init__(self): + super(EmptyAsymKeystore, self).__init__("Empty Asymmetrical Keystore", None, None) + + +class RSAKeystore(AsymKeyStore): + + def __init__(self, public, private=None): + super(RSAKeystore, self).__init__("RSA Keystore", public, private) + + @classmethod + def from_der_certificate(cls, certificate): + public = rsa_public_from_der_certificate(certificate) + keystore = cls(public) + keystore.certificate = certificate + return keystore + + @classmethod + def from_pem_certificate(cls, certificate): + public = rsa_public_from_pem_certificate(certificate) + keystore = cls(public) + keystore.certificate = certificate + return keystore + + @classmethod + def from_private(cls, private): + private = RSA.importKey(private) + public = private.publickey() + return cls(public, private) + + +class DSAKeystore(AsymKeyStore): + + def __init__(self, public, private=None): + super(DSAKeystore, self).__init__("DSA Keystore", public, private) + + +class KexKeyStore(object): + + def __init__(self, name, public, private=None): + self.name = name + self.public = public + self.private = private + + +class EmptyKexKeystore(KexKeyStore): + + def __init__(self): + super(EmptyKexKeystore, self).__init__("Empty Kex Keystore", None, None) + + +class DHKeyStore(KexKeyStore): + + def __init__(self, g, p, public, private=None): + self.g = g + self.p = p + self.size = nb_bits(self.p) + super(DHKeyStore, self).__init__("DH Keystore", public, private) + + @classmethod + def new_keypair(cls, g, p, private=None): + # Client private key + # Long story short, this provides 128bits of key space (sqrt(2**256)). TLS leaves this up to the implementation. + # Another option is to gather random.randint(0, 2**nb_bits(p) - 1), but has little added security + # In our case, since we don't care about security, it really doesn't matter what we pick + private = private or random.randint(0, 2 ** 256 - 1) + public = pow(g, private, p) + return cls(g, p, public, private) + + def get_psk(self, public): + return pow(public, self.private, self.p) + + def __str__(self): + template = """{name}: + generator: {g} + modulus: {p} + size: {size} + public: {public} + private: {private}""" + return template.format(name=self.name, g=self.g, p=self.p, size=self.size, public=self.public, + private=self.private) + + +class ECDHKeyStore(KexKeyStore): + + def __init__(self, curve, public, private=None): + self.curve = curve + self.public = public + self.private = private + if self.curve is None: + self.unknown_curve = True + self.size = 0 + self.keys = (self.private, self.public) + else: + self.unknown_curve = False + self.size = nb_bits(self.curve.field.p) + self.keys = ec.Keypair(curve, self.private, self.public) + super(ECDHKeyStore, self).__init__("ECDH Keystore", public, private) + + @classmethod + def from_keypair(cls, curve, keypair): + return cls(curve, keypair.pub, keypair.priv) + + def __str__(self): + template = """{name}: + curve: {curve} + size: {size} + public: {public} + private: {private}""" + curve_name = "Unknown" if self.unknown_curve else self.curve.name + return template.format(name=self.name, curve=curve_name, size=self.size, public=self.public, + private=self.private) + + +class SymKeyStore(object): + + def __init__(self, name, key=b""): + self.name = name + self.key = key + self.size = len(self.key) * 8 + + +class EmptySymKeyStore(SymKeyStore): + + def __init__(self): + super(EmptySymKeyStore, self).__init__("Empty Symmetrical Keystore") + + +class CipherKeyStore(SymKeyStore): + + def __init__(self, properties, key, hmac, iv=b""): + self.properties = properties + # Be consistent and track everything in bits + self.block_size = self.properties["cipher"]["type"].block_size * 8 + self.hmac = hmac + self.hmac_size = len(self.hmac) * 8 + self.iv = iv + self.iv_size = len(self.iv) * 8 + super(CipherKeyStore, self).__init__("%s Keystore" % self.properties["name"], key) + + def __str__(self): + template = """{name}: + {cipher_name} cipher: + mode: {mode} + key: {key} + size: {size} + block_size: {block_size} + iv: {iv} + {hmac_name} hmac: + key: {hmac_key} + size: {hmac_size}""" + return template.format(name=self.name, cipher_name=self.properties["cipher"]["name"], + mode=self.properties["cipher"]["mode_name"], key=repr(self.key), size=self.size, + block_size=self.block_size, iv=repr(self.iv), hmac_name=self.properties["hash"]["name"], + hmac_key=repr(self.hmac), hmac_size=self.hmac_size) diff --git a/scapy_ssl_tls/ssl_tls_registry.py b/scapy_ssl_tls/ssl_tls_registry.py index af9e4df..2056cea 100644 --- a/scapy_ssl_tls/ssl_tls_registry.py +++ b/scapy_ssl_tls/ssl_tls_registry.py @@ -16,7 +16,7 @@ 0x40: 'ecdsa_sign', 0x41: 'rsa_fixed_ecdh', 0x42: 'ecdsa_fixed_ecdh', - } +} TLS_CIPHER_SUITE_REGISTRY = { 0x0000: 'NULL_WITH_NULL_NULL', 0x0001: 'RSA_WITH_NULL_MD5', @@ -344,14 +344,14 @@ 0xccac: 'ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256', 0xccad: 'DHE_PSK_WITH_CHACHA20_POLY1305_SHA256', 0xccae: 'RSA_PSK_WITH_CHACHA20_POLY1305_SHA256', - } +} TLS_CONTENTTYPE_REGISTRY = { 0x14: 'change_cipher_spec', 0x15: 'alert', 0x16: 'handshake', 0x17: 'application_data', 0x18: 'heartbeat', - } +} TLS_ALERT_REGISTRY = { 0x00: 'close_notify', 0x0a: 'unexpected_message', @@ -384,7 +384,7 @@ 0x71: 'bad_certificate_status_response', 0x72: 'bad_certificate_hash_value', 0x73: 'unknown_psk_identity', - } +} TLS_HANDSHAKETYPE_REGISTRY = { 0x00: 'hello_request', 0x01: 'client_hello', @@ -401,7 +401,7 @@ 0x15: 'certificate_url', 0x16: 'certificate_status', 0x17: 'supplemental_data', - } +} SUPPORTED_GROUPS_REGISTRY = { 0x00: 'Unassigned', 0x01: 'sect163k1', @@ -442,31 +442,31 @@ 0xff00: 'Unassigned', 0xff01: 'arbitrary_explicit_prime_curves', 0xff02: 'arbitrary_explicit_char2_curves', - } +} EC_POINT_FORMAT_REGISTRY = { 0x00: 'uncompressed', 0x01: 'ansiX962_compressed_prime', 0x02: 'ansiX962_compressed_char2', - } +} EC_CURVE_TYPE_REGISTRY = { 0x00: 'Unassigned', 0x01: 'explicit_prime', 0x02: 'explicit_char2', 0x03: 'named_curve', - } +} TLS_SUPPLEMENTAL_DATA_FORMATS = { 0x00: 'user_mapping_data', 0x4002: 'authz_data', - } +} TLS_USERMAPPINGTYPE_VALUES = { 0x40: 'upn_domain_hint', - } +} TLS_SIGNATUREALGORITHM_REGISTRY = { 0x00: 'anonymous', 0x01: 'rsa', 0x02: 'dsa', 0x03: 'ecdsa', - } +} TLS_HASHALGORITHM_REGISTRY = { 0x00: 'none', 0x01: 'md5', @@ -475,7 +475,7 @@ 0x04: 'sha256', 0x05: 'sha384', 0x06: 'sha512', - } +} # Skipping: AttributeError("'NoneType' object has no attribute 'text'",) # Skipping: AttributeError("'NoneType' object has no attribute 'text'",) # Skipping: AttributeError("'NoneType' object has no attribute 'text'",) @@ -493,7 +493,7 @@ # Skipping: AttributeError("'NoneType' object has no attribute 'text'",) # Skipping: AttributeError("'NoneType' object has no attribute 'text'",) TLS_EXPORTER_LABEL_REGISTRY = { - } +} TLS_AUTHORIZATION_DATA_FORMATS = { 0x00: 'x509_attr_cert', 0x01: 'saml_assertion', @@ -502,19 +502,19 @@ 0x40: 'keynote_assertion_list', 0x41: 'keynote_assertion_list_url', 0x42: 'dtcp_authorization', - } +} HEARTBEAT_MESSAGE_TYPES = { 0x00: 'Reserved', 0x01: 'heartbeat_request', 0x02: 'heartbeat_response', 0xff: 'Reserved', - } +} HEARTBEAT_MODES = { 0x00: 'Reserved', 0x01: 'peer_allowed_to_send', 0x02: 'peer_not_allowed_to_send', 0xff: 'Reserved', - } +} # Generator: fetch_iana_tls_registry.py # date: 2016-09-28 # sources: https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xml @@ -524,7 +524,7 @@ 0x00: 'NULL', 0x01: 'DEFLATE', 0x40: 'LZS', - } +} # Generator: fetch_iana_tls_registry.py # date: 2016-09-28 # sources: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xml @@ -559,17 +559,17 @@ 0x19: 'cached_info', 0x23: 'SessionTicket_TLS', 0xff01: 'renegotiation_info', - } +} TLS_CERTIFICATE_TYPES = { 0x00: 'X_509', 0x01: 'OpenPGP', 0x02: 'Raw_Public_Key', - } +} TLS_CERTIFICATE_STATUS_TYPES = { 0x00: 'Reserved', 0x01: 'ocsp', 0x02: 'ocsp_multi', - } +} APPLICATION_LAYER_PROTOCOL_NEGOTIATION_PROTOCOL_IDS = { 'c-webrtc': 'Confidential_WebRTC_Media_and_Data', 'ftp': 'FTP', @@ -582,9 +582,9 @@ 'stun.nat-discovery': 'NAT_discovery_using_Session_Traversal_Utilities_for_NAT', 'stun.turn': 'Traversal_Using_Relays_around_NAT', 'webrtc': 'WebRTC_Media_and_Data', - } +} TLS_CACHEDINFORMATIONTYPE_VALUES = { 0x00: 'Reserved', 0x01: 'cert', 0x02: 'cert_req', - } +} diff --git a/setup.cfg b/setup.cfg index aa17af3..f7c3dbb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,9 @@ [metadata] description-file = README.md +[pep8] +max-line-length = 120 + [nosetests] ## advanced debug #verbosity=1 diff --git a/setup.py b/setup.py index 2233631..a8f2cc8 100755 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ from setuptools.command.install import install as _install import site as _site + def get_site_packages(): """ This is a hack to work around site.getsitepackages() not working in @@ -52,7 +53,7 @@ def get_scapy_locations(sites): for dir_ in dirs: if dir_ == "layers": scapy_locations.append(root) - print("INFO: Installing scapy-ssl_tls layers to: %s"%repr(scapy_locations)) + print("INFO: Installing scapy-ssl_tls layers to: %s" % repr(scapy_locations)) return scapy_locations @@ -107,6 +108,7 @@ def _post_install(dir_): else: print(line, end="") + def os_install_requires(): dependencies = ["scapy", "pycrypto", "tinyec"] # Scapy on OSX requires dnet and pcapy, but fails to declare them as dependencies @@ -114,6 +116,7 @@ def os_install_requires(): dependencies.extend(("dnet", "pcapy")) return dependencies + def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() diff --git a/tests/test_pkcs7.py b/tests/test_pkcs7.py index 2a82b52..f940e23 100644 --- a/tests/test_pkcs7.py +++ b/tests/test_pkcs7.py @@ -3,6 +3,7 @@ import unittest import scapy_ssl_tls.pkcs7 as pkcs7 + class TestPKCS7Encoder(unittest.TestCase): def setUp(self): @@ -16,7 +17,7 @@ def test_pkcs7_encoder_returns_expected_padding_on_short_block(self): self.assertEqual(pkcs7_data[len(data):], chr(self.pkcs7.k - len(data)) * (self.pkcs7.k - len(data))) def test_pkcs7_padding_only_is_returned_on_get_padding_call(self): - data = b"A"*16 + data = b"A" * 16 pkcs7_padding = self.pkcs7.get_padding(data) self.assertEqual(len(pkcs7_padding), self.pkcs7.k) self.assertEqual(chr(len(pkcs7_padding)) * len(pkcs7_padding), pkcs7_padding) @@ -24,10 +25,10 @@ def test_pkcs7_padding_only_is_returned_on_get_padding_call(self): def test_pkcs7_encode_decode(self): data = 'X' - for length in xrange(self.pkcs7.k*2+1): - pkcs7_data = self.pkcs7.encode(data*length) - self.assertEqual(len(pkcs7_data)%self.pkcs7.k, 0) - self.assertEqual(self.pkcs7.decode(pkcs7_data), data*length) + for length in xrange(self.pkcs7.k * 2 + 1): + pkcs7_data = self.pkcs7.encode(data * length) + self.assertEqual(len(pkcs7_data) % self.pkcs7.k, 0) + self.assertEqual(self.pkcs7.decode(pkcs7_data), data * length) def test_pkcs7_raises_valueerror_on_invalid_padding(self): data = "X" diff --git a/tests/test_ssl_tls.py b/tests/test_ssl_tls.py index 5b194c6..787f0a6 100644 --- a/tests/test_ssl_tls.py +++ b/tests/test_ssl_tls.py @@ -7,6 +7,7 @@ import unittest import scapy_ssl_tls.ssl_tls as tls import scapy_ssl_tls.ssl_tls_crypto as tlsc +import scapy_ssl_tls.ssl_tls_keystore as tlsk from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.Hash import MD5, SHA @@ -21,6 +22,7 @@ def env_local_file(file): class TestTLSRecord(unittest.TestCase): + def setUp(self): self.server_hello = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSServerHello() self.cert_list = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSCertificateList() @@ -104,6 +106,7 @@ def test_large_record_payload_is_fragmented_when_above_max_ushort(self): class TestTLSDissector(unittest.TestCase): + def setUp(self): self.payload = binascii.unhexlify( "160301004a02000046030155514d08929c06119d291bae09ec50ba48f52069c840673c76721aa5c53bc352202de1c20c707ba9b083282d2eba3d95bdfb5847eb9241f252173a04c9f990d508002f0016030104080b0004040004010003fe308203fa308202e2a003020102020900980ceed2480234b2300d06092a864886f70d0101050500305b310b3009060355040613025553311330110603550408130a43616c69666f726e696131173015060355040a130e4369747269782053797374656d73311e301c06035504031315616d73736c2e656e672e636974726974652e6e6574301e170d3135303432343233313435395a170d3235303432313233313435395a305b310b3009060355040613025553311330110603550408130a43616c69666f726e696131173015060355040a130e4369747269782053797374656d73311e301c06035504031315616d73736c2e656e672e636974726974652e6e657430820122300d06092a864886f70d01010105000382010f003082010a0282010100c0e2f8d4d4423ef7ce3e6ea789ad83c831fd679a8745bfe7d3628a544b7f04fec8bb8eb72737a6334764b68e796fbd70f19a1754776aba2f5d9685f2931b57456825ca75baca540c34de26115037d76d1a6fabbab6cd666af98fcb6b9c2fc714fd523828babae067f9ad7da51100306b4a5783a1402a4d80524dc14d0867f526e055dbd32e6f9f785072d72b8c36994bb56c2cdbf74e2149e7c625fed1c6405e205289c2b4608bd28704303764227f4540b95054c115be9185223b8a815462818090c6c933ce4c39d4049197106fe84918048adfd185fc7d64167804ccafbae8b84dc81d0288f4078c736a4ccc04c27184ffb45b14b4bd79ab472dba8877c20f0203010001a381c03081bd301d0603551d0e041604141979840d258e11dad71d942fe77e567fc0bbb48430818d0603551d2304818530818280141979840d258e11dad71d942fe77e567fc0bbb484a15fa45d305b310b3009060355040613025553311330110603550408130a43616c69666f726e696131173015060355040a130e4369747269782053797374656d73311e301c06035504031315616d73736c2e656e672e636974726974652e6e6574820900980ceed2480234b2300c0603551d13040530030101ff300d06092a864886f70d010105050003820101006fbd05d20b74d33b727fb2ccfebf3f36950278631bf87e77f503ce8e9a080925b6276f32218cadd0a43d40d89ba0e5fd9897ac536a079440385ba59e2593100df52224a8f8b786561466558d435d9ea5e4f320028ee7afa005f09b64b16f3e6b787af31b28d623edd480a50dd64fc6f0da0eab0c38c5d8965504c9c3d5c2c85514b7b1f8df9ee2d9116ac05781dbef26a66e98679f84b0378a1f8857f69e72cf72c11e836e0144153bd412dcfb506ed9e4a6181208b92be3ba9ec13f3c5b19eb700884e04a051603f2f2302d542e094afcce6694c5e46452a486b9ba339578e0f530f98824872eef62a23d685e9710c47362a034b699b7f9e1521b135e1e950d16030100040e000000") @@ -112,15 +115,16 @@ def setUp(self): def _static_tls_handshake(self): # Setup static parameters, so PRF output is reproducible tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.crypto.session.premaster_secret = "\x03\x01" + "C" * 46 - client_hello = tls.TLSRecord(version="TLS_1_0") / tls.TLSHandshake() / tls.TLSClientHello(version="TLS_1_0", - gmt_unix_time=1234, - random_bytes="A" * 28, - session_id="", - compression_methods=[ - 0], - cipher_suites=( - tls.TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA)) + tls_ctx.premaster_secret = "\x03\x01" + "C" * 46 + client_hello = tls.TLSRecord( + version="TLS_1_0") / tls.TLSHandshake() / tls.TLSClientHello( + version="TLS_1_0", + gmt_unix_time=1234, + random_bytes="A" * 28, + session_id="", + compression_methods=[0], + cipher_suites=( + tls.TLSCipherSuite.RSA_WITH_AES_128_CBC_SHA)) tls_ctx.insert(client_hello) server_hello = binascii.unhexlify( "160301004a02000046030155662cd45fade845839a3c8dba0e46f1abcd2fd941f4e95e75ab6d61811abcf420960ccadc00abc7043cca458d9a1df1cb877a5005b53f754ac80d392990fae3c7002f00160301047c0b0004780004750004723082046e30820356a003020102020900d1e1f53a9203251a300d06092a864886f70d0101050500308180310b3009060355040613025553311330110603550408130a536f6d652d53746174653112301006035504071309536f6d652d6369747931153013060355040a130c536f6d652d636f6d70616e793110300e060355040b1307536f6d652d4f55311f301d06035504031316736f6d652d7365727665722e736f6d652e7768657265301e170d3135303532323139313631325a170d3235303531393139313631325a308180310b3009060355040613025553311330110603550408130a536f6d652d53746174653112301006035504071309536f6d652d6369747931153013060355040a130c536f6d652d636f6d70616e793110300e060355040b1307536f6d652d4f55311f301d06035504031316736f6d652d7365727665722e736f6d652e776865726530820122300d06092a864886f70d01010105000382010f003082010a0282010100cd7b7165ee7528f107cf666edc673eedc863544ebe8cc3741346015eea182a73a9e18e26f6f1553d83843d2bdacdd4501faec7b4f5446b8790053f152e23f70d121ca7f63a22a657536ee4b50b8777568ef469905ce05211178dd9ebe223b21246cce4baf351d0b81b464830e15fb7178cf5f39e7673de7779e5dbbd7a3d2ea98589b0d6003635447693ed2ec632c3dbb632ac254e3b8cd78e1ea160982627e2cd3a369c4bb43c486141b97fbbd9d3cb014b92e0ec6ecf46ded64749bbecfb6f98d0d2f459d5cf0054a6522280af961dfcbe1650937180f43decf2f8725b94eeec10248cdc70acad63bcc3cd5370d0dc0f3cba8d369909c6b917f243e5e5bc270203010001a381e83081e5301d0603551d0e041604143cc2f7fc85dbbe4b0566b35bde744484438ae83e3081b50603551d230481ad3081aa80143cc2f7fc85dbbe4b0566b35bde744484438ae83ea18186a48183308180310b3009060355040613025553311330110603550408130a536f6d652d53746174653112301006035504071309536f6d652d6369747931153013060355040a130c536f6d652d636f6d70616e793110300e060355040b1307536f6d652d4f55311f301d06035504031316736f6d652d7365727665722e736f6d652e7768657265820900d1e1f53a9203251a300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010001738e2985692d8239fb1795e6ea0718755cf106cd739f7113afd3a074add07f981b06f34b9df3e1658c153355c5061b369d60d341eb4ccefdd98d6d6790be499cde8bd5705d1a8a89bb141599f3319914f8539e294848c106386218d8679da46ba90a2ce7587265cb55d6a629569b65581ee2e88ded264b81dff1c11e2c55728efe170dfe4f76706fbbda137b02e0fa987355b0cfdb3f8637e35473e4a6eccdcbc27d55d1f956a5f2c454e937df71d42e21d45d227477e26053b8be003fa527746b163b3d4b9a585d2860e5080ed9737d4c5fa5a32eee45a4e56d8a03542349619084580cc9c6c25b1ac7f3854b501423eafdd32896af92ce8ca6923947d77c16030100040e000000") @@ -211,6 +215,7 @@ def test_encrypted_handshake_which_fails_decryption_throws_error(self): class TestTLSDecryptablePacket(unittest.TestCase): + def test_packet_does_not_contain_mac_or_padding_if_not_received_encrypted(self): pkt = tls.TLSRecord() / tls.TLSChangeCipherSpec() records = tls.TLS(str(pkt)) @@ -255,7 +260,8 @@ def test_cbc_mac_and_padding_are_added_if_session_context_is_provided(self): def test_explicit_iv_is_added_for_tls_1_1_if_session_context_is_provided(self): data = "%s%s%s%s" % ("C" * AES.block_size, "A" * 2, "B" * SHA.digest_size, "\x03" * 4) tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.params.negotiated.version = tls.TLSVersion.TLS_1_1 + tls_ctx.negotiated.version = tls.TLSVersion.TLS_1_1 + tls_ctx.requires_iv = True tls_ctx.sec_params = tlsc.TLSSecurityParameters.from_pre_master_secret( tlsc.TLSPRF(tls.TLSVersion.TLS_1_0), tls.TLSCipherSuite.RSA_WITH_AES_256_CBC_SHA, "A" * 48, "B" * 32, "C" * 32, True) @@ -267,34 +273,35 @@ def test_explicit_iv_is_added_for_tls_1_1_if_session_context_is_provided(self): class TestTLSClientHello(unittest.TestCase): + def setUp(self): self.pkt = tls.TLSRecord() / \ - tls.TLSHandshake() / \ - tls.TLSClientHello(extensions=[ \ - tls.TLSExtension() / \ - tls.TLSExtServerNameIndication(server_names=[tls.TLSServerName(data="www.github.com"), - tls.TLSServerName(data="github.com")]), \ - tls.TLSExtension() / \ - tls.TLSExtALPN(protocol_name_list=[tls.TLSALPNProtocol(data="http/1.1"), - tls.TLSALPNProtocol(data="http/1.0")]), - tls.TLSExtension() / \ - tls.TLSExtALPN(protocol_name_list=[tls.TLSALPNProtocol(data="http/2.0"), ]), - tls.TLSExtension() / \ - tls.TLSExtMaxFragmentLength(fragment_length=0x03), - tls.TLSExtension() / \ - tls.TLSExtCertificateURL( - certificate_urls=[tls.TLSURLAndOptionalHash(url="http://www.github.com/tintinweb")]), - tls.TLSExtension() / \ - tls.TLSExtECPointsFormat(ec_point_formats=[tls.TLSEcPointFormat.ANSIX962_COMPRESSED_CHAR2]), - tls.TLSExtension() / \ - tls.TLSExtEllipticCurves(elliptic_curves=[tls.TLSEllipticCurve.SECT571R1, ]), - tls.TLSExtension() / \ - tls.TLSExtHeartbeat(mode=tls.TLSHeartbeatMode.PEER_NOT_ALLOWED_TO_SEND), - tls.TLSExtension() / \ - tls.TLSExtSessionTicketTLS(data="myticket"), - tls.TLSExtension() / \ - tls.TLSExtRenegotiationInfo(data="myreneginfo"), - ], ) + tls.TLSHandshake() / \ + tls.TLSClientHello(extensions=[ + tls.TLSExtension() / + tls.TLSExtServerNameIndication(server_names=[tls.TLSServerName(data="www.github.com"), + tls.TLSServerName(data="github.com")]), + tls.TLSExtension() / + tls.TLSExtALPN(protocol_name_list=[tls.TLSALPNProtocol(data="http/1.1"), + tls.TLSALPNProtocol(data="http/1.0")]), + tls.TLSExtension() / + tls.TLSExtALPN(protocol_name_list=[tls.TLSALPNProtocol(data="http/2.0"), ]), + tls.TLSExtension() / + tls.TLSExtMaxFragmentLength(fragment_length=0x03), + tls.TLSExtension() / + tls.TLSExtCertificateURL( + certificate_urls=[tls.TLSURLAndOptionalHash(url="http://www.github.com/tintinweb")]), + tls.TLSExtension() / + tls.TLSExtECPointsFormat(ec_point_formats=[tls.TLSEcPointFormat.ANSIX962_COMPRESSED_CHAR2]), + tls.TLSExtension() / + tls.TLSExtEllipticCurves(elliptic_curves=[tls.TLSEllipticCurve.SECT571R1, ]), + tls.TLSExtension() / + tls.TLSExtHeartbeat(mode=tls.TLSHeartbeatMode.PEER_NOT_ALLOWED_TO_SEND), + tls.TLSExtension() / + tls.TLSExtSessionTicketTLS(data="myticket"), + tls.TLSExtension() / + tls.TLSExtRenegotiationInfo(data="myreneginfo"), + ], ) unittest.TestCase.setUp(self) def test_dissect_contains_client_hello(self): @@ -355,6 +362,7 @@ def test_dissect_client_hello_conditional_extensions_length(self): class TestTLSPlaintext(unittest.TestCase): + def test_built_plaintext_has_no_mac_and_padding_when_unspecified(self): plaintext = tls.TLSPlaintext(data="AAAA") self.assertEqual(str(plaintext), "AAAA") @@ -372,6 +380,7 @@ def test_built_plaintext_includes_mac_and_padding_if_not_empty(self): class TestPCAP(unittest.TestCase): + def setUp(self): self.records = [] self.pkts = (p for p in rdpcap(env_local_file('RSA_WITH_AES_128_CBC_SHA.pcap')) if p.haslayer(tls.SSL)) @@ -434,8 +443,12 @@ def test_pcap_record_order(self): # TODO: Client and Server KEX cannot be dissected without a TLS context # self.assertTrue(record.haslayer(tls.TLSClientRSAParams)) # self.assertEqual(record[tls.TLSClientRSAParams].data) - self.assertEqual(str(record[tls.TLSClientKeyExchange])[2:], - '\x9es\xdf\xe0\xf2\xd0@2D\x9a4\x7fW\x86\x10\xea=\xc5\xe2\xf9\xa5iC\xc9\x0b\x00~\x911W\xfc\xc5e\x18\rD\xfdQ\xf8\xda\x8az\xab\x16\x03\xeb\xac#n\x8d\xdd\xbb\xf4u\xe7\xb7\xa3\xce\xdbgk}0*') + self.assertEqual( + str( + record[ + tls.TLSClientKeyExchange])[ + 2:], + '\x9es\xdf\xe0\xf2\xd0@2D\x9a4\x7fW\x86\x10\xea=\xc5\xe2\xf9\xa5iC\xc9\x0b\x00~\x911W\xfc\xc5e\x18\rD\xfdQ\xf8\xda\x8az\xab\x16\x03\xeb\xac#n\x8d\xdd\xbb\xf4u\xe7\xb7\xa3\xce\xdbgk}0*') # Change Cipher Spec record = pkts.pop() self.assertTrue(record.haslayer(tls.TLSRecord)) @@ -447,8 +460,10 @@ def test_pcap_record_order(self): self.assertEquals(record[tls.TLSRecord].content_type, tls.TLSContentType.HANDSHAKE) self.assertTrue(record.haslayer(tls.TLSCiphertext)) self.assertEqual(record[tls.TLSRecord].length, 0x30) - self.assertEqual(record[tls.TLSCiphertext].data, - "\x15\xcbz[-\xc0'\t(b\x95D\x9f\xa1\x1eNj\xfbI\x9dj$D\xc6\x8e&\xbc\xc1(\x8c'\xcc\xa2\xba\xec8cnd\xd8R\x94\x17\x96a\xfd\x9cT") + self.assertEqual( + record[ + tls.TLSCiphertext].data, + "\x15\xcbz[-\xc0'\t(b\x95D\x9f\xa1\x1eNj\xfbI\x9dj$D\xc6\x8e&\xbc\xc1(\x8c'\xcc\xa2\xba\xec8cnd\xd8R\x94\x17\x96a\xfd\x9cT") # Handshake - new session ticket record = pkts.pop() self.assertTrue(record.haslayer(tls.TLSRecord)) @@ -456,8 +471,10 @@ def test_pcap_record_order(self): self.assertTrue(record.haslayer(tls.TLSSessionTicket)) self.assertEqual(record[tls.TLSSessionTicket].lifetime, 7200) self.assertEqual(record[tls.TLSSessionTicket].ticket_length, 0xa0) - self.assertEqual(record[tls.TLSSessionTicket].ticket, - '\xd4\xee\xb0\x9b\xb5\xa2\xd3\x00W\x84Y\xec\r\xbf\x05\x0c\xd5\xb9\xe2\xf82\xb5\xec\xce\xe2\x9c%%\xd9>J\x94[\xca\x18+\x0f_\xf6s8b\xcd\xcc\xf129\xe4^0\xf3\x94\xf5\xc5\x94:\x8c\x8e\xe5\x12J\x1e\xd81\xb5\x17\t\xa6Li\xca\xae\xfb\x04\x17dT\x9e\xc2\xfa\xf3m\xe9\xa5\xed\xa6e\xfe/\xf3\xc6\xcex@\xf7e\xe0\x13\xd3w\xc7\xc5y\x16VL0\x94\xcf\xb0<\x00\x91\xbd\x86\x08\x9f/\x05g\x03o\xa7;\xb96\xf2\x80O`]L\xc4B]\x02D\xba1\x8f9\x8e\x0c\x1e\xa8&O>\x01\x96\xb3o\xc6%\xe40\x03\xd6:}') + self.assertEqual( + record[ + tls.TLSSessionTicket].ticket, + '\xd4\xee\xb0\x9b\xb5\xa2\xd3\x00W\x84Y\xec\r\xbf\x05\x0c\xd5\xb9\xe2\xf82\xb5\xec\xce\xe2\x9c%%\xd9>J\x94[\xca\x18+\x0f_\xf6s8b\xcd\xcc\xf129\xe4^0\xf3\x94\xf5\xc5\x94:\x8c\x8e\xe5\x12J\x1e\xd81\xb5\x17\t\xa6Li\xca\xae\xfb\x04\x17dT\x9e\xc2\xfa\xf3m\xe9\xa5\xed\xa6e\xfe/\xf3\xc6\xcex@\xf7e\xe0\x13\xd3w\xc7\xc5y\x16VL0\x94\xcf\xb0<\x00\x91\xbd\x86\x08\x9f/\x05g\x03o\xa7;\xb96\xf2\x80O`]L\xc4B]\x02D\xba1\x8f9\x8e\x0c\x1e\xa8&O>\x01\x96\xb3o\xc6%\xe40\x03\xd6:}') # Change Cipher Spec record = pkts.pop() self.assertTrue(record.haslayer(tls.TLSRecord)) @@ -468,8 +485,10 @@ def test_pcap_record_order(self): self.assertTrue(record.haslayer(tls.TLSRecord)) self.assertEquals(record[tls.TLSRecord].content_type, tls.TLSContentType.HANDSHAKE) self.assertTrue(record.haslayer(tls.TLSCiphertext)) - self.assertEqual(record[tls.TLSCiphertext].data, - '%\xb8X\xc1\xa6?\xf8\xbd\xe6\xae\xbd\x98\xd4u\xa5E\x1b\xd8jpy\x86)NOd\xba\xe7\x1f\xcaK\x96\x9b\xf7\x0bP\xf5O\xfd\xda\xda\xcd\xcdK\x12.\xdf\xd5') + self.assertEqual( + record[ + tls.TLSCiphertext].data, + '%\xb8X\xc1\xa6?\xf8\xbd\xe6\xae\xbd\x98\xd4u\xa5E\x1b\xd8jpy\x86)NOd\xba\xe7\x1f\xcaK\x96\x9b\xf7\x0bP\xf5O\xfd\xda\xda\xcd\xcdK\x12.\xdf\xd5') # some more encrypted traffic for _ in xrange(6): # Application data - encrypted - 6 times @@ -484,6 +503,7 @@ def test_pcap_record_order(self): class TestToRaw(unittest.TestCase): + def setUp(self): self.pem_priv_key = """-----BEGIN PRIVATE KEY----- MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDDLrmt4lKRpm6P @@ -519,7 +539,7 @@ def setUp(self): self.pub_key = PKCS1_v1_5.new(rsa_priv_key.publickey()) self.tls_ctx = tlsc.TLSSessionCtx() - self.tls_ctx.rsa_load_keys(self.pem_priv_key) + self.tls_ctx.server_ctx.load_rsa_keys(self.pem_priv_key) # SSLv2 self.record_version = 0x0002 # TLSv1.0 @@ -535,8 +555,8 @@ def setUp(self): version=self.hello_version, compression_method=self.comp_method, cipher_suite=self.cipher_suite) self.tls_ctx.insert(self.server_hello) # Build method to generate EPMS automatically in TLSSessionCtx - self.client_kex = tls.TLSRecord(version=self.hello_version) / tls.TLSHandshake() / tls.TLSClientKeyExchange() / \ - tls.TLSClientRSAParams(data=self.tls_ctx.get_encrypted_pms()) + self.client_kex = tls.TLSRecord(version=self.hello_version) / tls.TLSHandshake() / \ + tls.TLSClientKeyExchange() / tls.TLSClientRSAParams(data=self.tls_ctx.get_encrypted_pms()) self.tls_ctx.insert(self.client_kex) unittest.TestCase.setUp(self) @@ -584,7 +604,7 @@ def test_tls_record_header_is_updated_when_output(self): record = tls.to_raw(pkt, self.tls_ctx, include_record=True) self.assertTrue(record.haslayer(tls.TLSRecord)) self.assertEqual(record.content_type, 0x17) - self.assertEqual(record.version, self.tls_ctx.params.negotiated.version) + self.assertEqual(record.version, self.tls_ctx.negotiated.version) def test_format_of_tls_finished_is_as_specified_in_rfc(self): def encrypt(crypto_container): @@ -597,11 +617,13 @@ def encrypt(crypto_container): client_finished = tls.TLSRecord(content_type=0x16) / tls.to_raw(tls.TLSFinished(), self.tls_ctx, include_record=False, encrypt_hook=encrypt) pkt = tls.TLS(str(client_finished)) - # 4 bytes of TLSHandshake header, 12 bytes of verify_data, 20 bytes of HMAC SHA1, 11 bytes of padding, 1 padding length byte + # 4 bytes of TLSHandshake header, 12 bytes of verify_data, 20 bytes of + # HMAC SHA1, 11 bytes of padding, 1 padding length byte self.assertEqual(pkt[tls.TLSRecord].length, len(tls.TLSHandshake()) + 12 + SHA.digest_size + 11 + 1) class TestTLSCertificate(unittest.TestCase): + def setUp(self): ''' //default openssl 1.0.1f server.pem @@ -720,8 +742,10 @@ def test_tls_certificate_x509_pubkey(self): # dissect and extract pubkey pkt = tls.SSL(str(pkt)) - pubkey_extract_from_der = tlsc.x509_extract_pubkey_from_der(self.der_cert) - pubkey_extract_from_tls_certificate = tlsc.x509_extract_pubkey_from_der(pkt[tls.TLSCertificate].data) + keystore1 = tlsk.RSAKeystore.from_der_certificate(self.der_cert) + pubkey_extract_from_der = keystore1.public + keystore2 = tlsk.RSAKeystore.from_der_certificate(pkt[tls.TLSCertificate].data) + pubkey_extract_from_tls_certificate = keystore2.public self.assertEqual(pubkey_extract_from_der, pubkey_extract_from_tls_certificate) @@ -739,6 +763,7 @@ def test_tls_certificate_x509_pubkey(self): class TestTLSTopLevelFunctions(unittest.TestCase): + def test_tls_payload_fragmentation_raises_error_with_negative_size(self): with self.assertRaises(ValueError): tls.tls_fragment_payload("AAAA", size=-1) diff --git a/tests/test_ssl_tls_crypto.py b/tests/test_ssl_tls_crypto.py index f51d3a1..5b20065 100644 --- a/tests/test_ssl_tls_crypto.py +++ b/tests/test_ssl_tls_crypto.py @@ -7,6 +7,7 @@ import tinyec.registry as reg import scapy_ssl_tls.ssl_tls as tls import scapy_ssl_tls.ssl_tls_crypto as tlsc +import scapy_ssl_tls.ssl_tls_keystore as tlsk from Crypto.Hash import HMAC, MD5, SHA, SHA256 from Crypto.Cipher import AES, DES3, PKCS1_v1_5 @@ -14,10 +15,11 @@ def env_local_file(file): - return os.path.join(os.path.dirname(__file__), 'files', file) + return os.path.join(os.path.dirname(__file__), "files", file) class TestNullCiper(unittest.TestCase): + def test_null_cipher_returns_cleartext_on_encrypt(self): null_cipher = tlsc.NullCipher.new(key="junk_key", iv="junk_iv") cleartext = "cleartext" @@ -31,6 +33,7 @@ def test_null_cipher_returns_ciphertext_on_decrypt(self): class TestNullHash(unittest.TestCase): + def test_null_hash_always_returns_empty_string(self): null_hash = tlsc.NullHash.new("initial_junk") null_hash.update("some more junk") @@ -45,6 +48,7 @@ def test_null_hash_with_pycrypto_hmac(self): class TestTLSSessionCtx(unittest.TestCase): + def setUp(self): self.pem_priv_key = """-----BEGIN PRIVATE KEY----- MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDDLrmt4lKRpm6P @@ -87,9 +91,9 @@ def test_negotiated_cipher_is_used_in_context(self): cipher_suite=cipher_suite) tls_ctx = tlsc.TLSSessionCtx() tls_ctx.insert(pkt) - self.assertEqual(tls_ctx.params.negotiated.key_exchange, + self.assertEqual(tls_ctx.negotiated.key_exchange, tlsc.TLSSecurityParameters.crypto_params[cipher_suite]["key_exchange"]["name"]) - self.assertEqual(tls_ctx.params.negotiated.mac, + self.assertEqual(tls_ctx.negotiated.mac, tlsc.TLSSecurityParameters.crypto_params[cipher_suite]["hash"]["name"]) def test_negotiated_compression_method_is_used_in_context(self): @@ -99,10 +103,11 @@ def test_negotiated_compression_method_is_used_in_context(self): compression_method=compression_method) tls_ctx = tlsc.TLSSessionCtx() tls_ctx.insert(pkt) - self.assertEqual(tls_ctx.params.negotiated.compression_algo, + self.assertEqual(tls_ctx.negotiated.compression_algo, tlsc.TLSCompressionParameters.comp_params[compression_method]["name"]) input_ = "some data" * 16 - self.assertEqual(tls_ctx.compression.method.decompress(tls_ctx.compression.method.compress(input_)), input_) + self.assertEqual(tls_ctx.client_ctx.compression.decompress(tls_ctx.client_ctx.compression.compress(input_)), + input_) def test_encrypted_pms_is_only_available_after_server_certificate_is_presented(self): pkt = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientHello() @@ -122,26 +127,26 @@ def test_random_pms_is_generated_on_client_hello(self): tls_ctx = tlsc.TLSSessionCtx() pkt = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientHello(version=0x0301) tls_ctx.insert(pkt) - self.assertIsNotNone(tls_ctx.crypto.session.premaster_secret) + self.assertIsNotNone(tls_ctx.premaster_secret) def test_keys_are_set_in_context_when_loaded(self): tls_ctx = tlsc.TLSSessionCtx() pkt = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientHello(version=0x0301) tls_ctx.insert(pkt) - tls_ctx.rsa_load_keys(self.pem_priv_key) - self.assertIsNotNone(tls_ctx.crypto.server.rsa.privkey) - self.assertIsNotNone(tls_ctx.crypto.server.rsa.pubkey) + tls_ctx.server_ctx.load_rsa_keys(self.pem_priv_key) + self.assertIsNotNone(tls_ctx.server_ctx.asym_keystore.private) + self.assertIsNotNone(tls_ctx.server_ctx.asym_keystore.public) # Broken due to pycrypto bug: https://github.com/dlitz/pycrypto/issues/114 # Uncomment when fixed upstream - # self.assertTrue(tls_ctx.crypto.server.rsa.privkey.can_decrypt()) - # self.assertTrue(tls_ctx.crypto.server.rsa.pubkey.can_decrypt()) - self.assertTrue(tls_ctx.crypto.server.rsa.privkey.can_encrypt()) + # self.assertTrue(tls_ctx.crypto.server.asym_keystore.private.can_decrypt()) + # self.assertTrue(tls_ctx.crypto.server.asym_keystore.public.can_decrypt()) + self.assertTrue(tls_ctx.server_ctx.asym_keystore.private.can_encrypt()) # TODO: Invertigate further: broken also in pycrypto. Should return False for public keys. - # self.assertFalse(tls_ctx.crypto.server.rsa.pubkey.can_encrypt()) + # self.assertFalse(tls_ctx.crypto.server.asym_keystore.public.can_encrypt()) def test_decrypted_pms_matches_generated_pms(self): tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.rsa_load_keys(self.pem_priv_key) + tls_ctx.server_ctx.load_rsa_keys(self.pem_priv_key) pkt = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientHello() tls_ctx.insert(pkt) epms = tls_ctx.get_encrypted_pms() @@ -149,8 +154,8 @@ def test_decrypted_pms_matches_generated_pms(self): tls_ctx.insert(pkt) pkt = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientKeyExchange() / tls.TLSClientRSAParams(data=epms) tls_ctx.insert(pkt) - self.assertEqual(tls_ctx.crypto.session.encrypted_premaster_secret, epms) - self.assertEqual(tls_ctx.crypto.session.premaster_secret, self.priv_key.decrypt(epms, None)) + self.assertEqual(tls_ctx.encrypted_premaster_secret, epms) + self.assertEqual(tls_ctx.premaster_secret, self.priv_key.decrypt(epms, None)) def test_fixed_crypto_data_matches_verify_data(self): client_verify_data = "e23f73911909a86be9e93fdb" @@ -162,14 +167,14 @@ def test_fixed_crypto_data_matches_verify_data(self): # Hello Request should be ignored in verify_data calculation tls_ctx.insert(tls.TLSHelloRequest()) tls_ctx.insert(client_hello) - tls_ctx.crypto.session.premaster_secret = "B" * 48 + tls_ctx.premaster_secret = "B" * 48 epms = "C" * 256 server_hello = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSServerHello(gmt_unix_time=1234, session_id="", random_bytes="A" * 28) tls_ctx.insert(server_hello) client_kex = tls.TLSRecord() / tls.TLSHandshake() / tls.TLSClientKeyExchange() /\ - tls.TLSClientRSAParams(data=epms) + tls.TLSClientRSAParams(data=epms) tls_ctx.insert(client_kex) self.assertEqual(client_verify_data, binascii.hexlify(tls_ctx.get_verify_data())) # Make sure that client finish is included in server finish calculation @@ -181,35 +186,36 @@ def test_fixed_crypto_data_matches_verify_data(self): def test_client_dh_parameters_generation_matches_fixed_data(self): tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.crypto.server.dh.p = "\xdaX<\x16\xd9\x85\"\x89\xd0\xe4\xafuoL\xca\x92\xddK\xe53\xb8\x04\xfb\x0f\xed\x94\xef\x9c\x8aD\x03\xedWFP\xd3i\x99\xdb)\xd7v\'k\xa2\xd3\xd4\x12\xe2\x18\xf4\xdd\x1e\x08L\xf6\xd8\x00>|Gt\xe83" - tls_ctx.crypto.server.dh.g = "\x02" - tls_ctx.crypto.server.dh.y_s = "b\x1bF\xd4\xbe\xc6\x83d\x80\x1e\xeam\x86^\xcc!\xb2\x1b\x85+\xbd$j\xc9\x05\xf4\x14\x82 7\x8f_\x13\xcb\xef\xabyd\xb4\xc8\xda\xde\xac\xe8Zr\x8f\xb5\xfc\n\x16\xb0b\xf7\xd9!\x8d\x03\xef\n\r9\xd8\x87" - client_privkey = 5398526532442504864680398257365369432058147704829279760748758494328728516319L + p = 11435638110073884015312138951374632602058080675070521707579703088370446597672067452229024566834732449017970455481029703480957707976441965258194321262569523 + g = 2 + public = 5138256925703068273978027748090991496798559132548080008963338818789329120888330364361710579103845963013102056863555649866832856399945018230203391434938503 + tls_ctx.server_ctx.kex_keystore = tlsk.DHKeyStore(g, p, public) + client_privkey = 5398526532442504864680398257365369432058147704829279760748758494328728516319 client_pubkey = tls_ctx.get_client_dh_pubkey(client_privkey) self.assertEqual( - "/T\xdc;\xc49\xa6\x8cD\xd4\xc1\x07I|\xb6\xc8\xaf\xb5\x04\xe9\xfb\t\x0e}\x14~\xa4\x1f\xdfo\x08u)Z\xb3\x0e\x1c^\xa3x0\x90\xa1\xd7\x82\x9dLT\xa6^\xcc\xf7\xae\x87\x97\x86vi\x02s\x10\xb3\xdbo", + ("/T\xdc;\xc49\xa6\x8cD\xd4\xc1\x07I|\xb6\xc8\xaf\xb5\x04\xe9\xfb\t\x0e}\x14~\xa4\x1f\xdfo\x08u)Z\xb3\x0e" + "\x1c^\xa3x0\x90\xa1\xd7\x82\x9dLT\xa6^\xcc\xf7\xae\x87\x97\x86vi\x02s\x10\xb3\xdbo"), client_pubkey) self.assertEqual( - "}\xcae\xd2y\xd7F$\xde\"\xa9s\xfbNR9v\x19t9\x87\xa8\xa3\x9c\xccb]\x13\xb7\x8a\x8f\xdf\x7fv\x05\xa6\xf1\xa7\xc8\xf4X\xe3\xd4\xac\xd6\x1e4\xb4\x1cc\xbb\xce\xbe\x94lQ\x91\xb9\xde\xb7\xa6gu_", - tls_ctx.crypto.session.premaster_secret) + ("}\xcae\xd2y\xd7F$\xde\"\xa9s\xfbNR9v\x19t9\x87\xa8\xa3\x9c\xccb]\x13\xb7\x8a\x8f\xdf\x7fv\x05\xa6\xf1\xa7" + "\xc8\xf4X\xe3\xd4\xac\xd6\x1e4\xb4\x1cc\xbb\xce\xbe\x94lQ\x91\xb9\xde\xb7\xa6gu_"), + tls_ctx.premaster_secret) def test_client_ecdh_parameters_generation_matches_fixed_data(self): tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.crypto.server.ecdh.curve_name = "secp256r1" - secp256r1 = reg.get_curve(tls_ctx.crypto.server.ecdh.curve_name) - tls_ctx.crypto.server.ecdh.pub = ec.Point( - secp256r1, - 71312736565121892539464098105317518227531978702333415386264829982789952731614L, - 108064706642599821618918248475955325719985341096102200103424860263181813987462L) - client_privkey = 15320484772785058360598040144348894600917526501829289880527760633524785596585L + secp256r1 = reg.get_curve("secp256r1") + public = ec.Point(secp256r1, 71312736565121892539464098105317518227531978702333415386264829982789952731614, + 108064706642599821618918248475955325719985341096102200103424860263181813987462) + tls_ctx.server_ctx.kex_keystore = tlsk.ECDHKeyStore(secp256r1, public) + client_privkey = 15320484772785058360598040144348894600917526501829289880527760633524785596585 client_keys = ec.Keypair(secp256r1, client_privkey) client_pubkey = tls_ctx.get_client_ecdh_pubkey(client_privkey) self.assertTrue(client_pubkey.startswith("\x04")) self.assertEqual("\x04%s%s" % (tlsc.int_to_str(client_keys.pub.x), tlsc.int_to_str(client_keys.pub.y)), client_pubkey) - self.assertEqual(client_keys.pub, tls_ctx.crypto.client.ecdh.pub) + self.assertEqual(client_keys.pub, tls_ctx.client_ctx.kex_keystore.public) self.assertEqual("'(\x17\x94l\xd7AO\x03\xd4Fi\x05}mP\x1aX5C7\xf0_\xa9\xb0\xac\xba{r\x1f\x12\x8f", - tls_ctx.crypto.session.premaster_secret) + tls_ctx.premaster_secret) class TestTLSSecurityParameters(unittest.TestCase): @@ -282,7 +288,7 @@ def test_hmac_used_matches_selected_ciphersuite(self): client_hmac = sec_params.get_client_hmac() client_hmac.update("some secret") self.assertEqual(client_hmac.hexdigest(), - HMAC.new(sec_params.client_write_MAC_key, "some secret", digestmod=SHA).hexdigest()) + HMAC.new(sec_params.client_keystore.hmac, "some secret", digestmod=SHA).hexdigest()) def test_tls_1_1_and_above_iv_is_null(self): # RSA_WITH_AES_128_CBC_SHA @@ -290,8 +296,8 @@ def test_tls_1_1_and_above_iv_is_null(self): sec_params = tlsc.TLSSecurityParameters.from_pre_master_secret(self.prf, cipher_suite, self.pre_master_secret, self.client_random, self.server_random, explicit_iv=True) - self.assertEqual(sec_params.client_write_IV, "\x00" * 16) - self.assertEqual(sec_params.server_write_IV, "\x00" * 16) + self.assertEqual(sec_params.client_keystore.iv, "\x00" * 16) + self.assertEqual(sec_params.server_keystore.iv, "\x00" * 16) def test_sec_params_generated_from_ms_match_sec_params_generated_from_pms(self): cipher_suite = 0x2f @@ -303,22 +309,23 @@ def test_sec_params_generated_from_ms_match_sec_params_generated_from_pms(self): explicit_iv=True) self.assertEqual("", ms_params.pms) self.assertEqual(pms_params.master_secret, ms_params.master_secret) - self.assertEqual(pms_params.client_write_IV, ms_params.client_write_IV) - self.assertEqual(pms_params.client_write_key, ms_params.client_write_key) - self.assertEqual(pms_params.client_write_MAC_key, ms_params.client_write_MAC_key) - self.assertEqual(pms_params.server_write_IV, ms_params.server_write_IV) - self.assertEqual(pms_params.server_write_key, ms_params.server_write_key) - self.assertEqual(pms_params.server_write_MAC_key, ms_params.server_write_MAC_key) + self.assertEqual(pms_params.client_keystore.iv, ms_params.client_keystore.iv) + self.assertEqual(pms_params.client_keystore.key, ms_params.client_keystore.key) + self.assertEqual(pms_params.client_keystore.hmac, ms_params.client_keystore.hmac) + self.assertEqual(pms_params.server_keystore.iv, ms_params.server_keystore.iv) + self.assertEqual(pms_params.server_keystore.key, ms_params.server_keystore.key) + self.assertEqual(pms_params.server_keystore.hmac, ms_params.server_keystore.hmac) def test_load_rsa_privkey_from_pem_file(self): pem_file = env_local_file("openssl_1_0_1_f_server.pem") tls_ctx = tlsc.TLSSessionCtx() - tls_ctx.rsa_load_keys_from_file(pem_file) - self.assertTrue(tls_ctx.crypto.server.rsa.privkey) - self.assertTrue(tls_ctx.crypto.server.rsa.pubkey) + tls_ctx.server_ctx.load_rsa_keys_from_file(pem_file) + self.assertTrue(tls_ctx.server_ctx.asym_keystore.private) + self.assertTrue(tls_ctx.server_ctx.asym_keystore.public) class TestNullCompression(unittest.TestCase): + def test_null_compression_returns_input_on_compress(self): null_compression = tlsc.NullCompression() input_ = "some text" @@ -331,6 +338,7 @@ def test_null_compression_returns_input_on_decompress(self): class TestTLSCompressionParameters(unittest.TestCase): + def test_input_message_matches_decompressed_message_with_deflate(self): # DEFLATE compression_method = 0x1 @@ -347,6 +355,7 @@ def test_input_message_matches_decompressed_message_with_null(self): class TestCryptoContainer(unittest.TestCase): + def setUp(self): self._do_kex(tls.TLSVersion.TLS_1_0) unittest.TestCase.setUp(self) @@ -386,7 +395,7 @@ def _do_kex(self, version): self.pub_key = PKCS1_v1_5.new(rsa_priv_key.publickey()) self.tls_ctx = tlsc.TLSSessionCtx() - self.tls_ctx.rsa_load_keys(self.pem_priv_key) + self.tls_ctx.server_ctx.load_rsa_keys(self.pem_priv_key) # SSLv2 self.record_version = 0x0002 self.version = version @@ -402,20 +411,20 @@ def _do_kex(self, version): self.tls_ctx.insert(self.server_hello) # Build method to generate EPMS automatically in TLSSessionCtx self.client_kex = tls.TLSRecord(version=self.version) / tls.TLSHandshake() / tls.TLSClientKeyExchange() /\ - tls.TLSClientRSAParams(data=self.tls_ctx.get_encrypted_pms()) + tls.TLSClientRSAParams(data=self.tls_ctx.get_encrypted_pms()) self.tls_ctx.insert(self.client_kex) def test_crypto_container_increments_sequence_number(self): - client_seq_num = self.tls_ctx.crypto.session.key.client.seq_num - server_seq_num = self.tls_ctx.crypto.session.key.server.seq_num + client_seq_num = self.tls_ctx.client_ctx.sequence + server_seq_num = self.tls_ctx.server_ctx.sequence tlsc.CryptoContainer(self.tls_ctx) client_seq_num += 1 - self.assertEqual(self.tls_ctx.crypto.session.key.client.seq_num, client_seq_num) - self.assertEqual(self.tls_ctx.crypto.session.key.server.seq_num, server_seq_num) + self.assertEqual(self.tls_ctx.client_ctx.sequence, client_seq_num) + self.assertEqual(self.tls_ctx.server_ctx.sequence, server_seq_num) self.tls_ctx.client = False tlsc.CryptoContainer(self.tls_ctx) - self.assertEqual(self.tls_ctx.crypto.session.key.client.seq_num, client_seq_num) - self.assertEqual(self.tls_ctx.crypto.session.key.server.seq_num, server_seq_num + 1) + self.assertEqual(self.tls_ctx.client_ctx.sequence, client_seq_num) + self.assertEqual(self.tls_ctx.server_ctx.sequence, server_seq_num + 1) def test_crypto_container_str_returns_cipher_payload(self): data = b"abcde" @@ -434,7 +443,7 @@ def test_crypto_container_returns_ciphertext(self): crypto_container = tlsc.CryptoContainer(self.tls_ctx, data) cleartext = str(crypto_container) ciphertext = crypto_container.encrypt() - self.assertEqual(cleartext, self.tls_ctx.crypto.server.dec.decrypt(ciphertext)) + self.assertEqual(cleartext, self.tls_ctx.server_ctx.dec.decrypt(ciphertext)) def test_generated_mac_can_be_overiden(self): data = b"C" * 102 diff --git a/tests/test_ssl_tls_keystore.py b/tests/test_ssl_tls_keystore.py new file mode 100644 index 0000000..51a1548 --- /dev/null +++ b/tests/test_ssl_tls_keystore.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +import unittest + +from Crypto.PublicKey import RSA +import scapy_ssl_tls.ssl_tls_keystore as tlsk + + +class TestAsymKeyStore(unittest.TestCase): + + def setUp(self): + self.pem_priv_key = """-----BEGIN PRIVATE KEY----- + MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDDLrmt4lKRpm6P + 2blptwJsa1EBuxuuAayLjwNqKGvm5c1CAUEa/NtEpUMM8WYKRDwxzakUIGI/BdP3 + NOEMphcs5+OekgJLhzoSdtAIrXPy8JIidENZE6FzCJ2b6fHU5O4hoNvv1Bx5yoZr + HVaWJIZMRRocJJ0Nf9oMaU8IE6m6OdBzQHEwcnL2/a8Q3VxstHufzjILmaZD9WL+ + 6AESlQMKZPNQ+Xd7d4nvnVkY4ZV46tA+KvADGuotgovQwG+uiyQoGRrQUms21vHF + zIvd3G9OCiyCTCHSyfsE3g7tks33NZ8O8gF8xa9OmU9TQPwwAyUr6JQXz0CW77o7 + Cr9LpHuNAgMBAAECggEBAJRbMbtfqc8XqDYjEfGur2Lld19Pb0yl7RbvD3NjYhDR + X2DqPyhaRfg5fWubGSp4jyBz6C5qJwMsVN80DFNm83qoj7T52lC6aoOaV6og3V8t + SIZzxLUyXKdpRxM5kR13HSHmeQYkPbi9HcrRM/1PqdzTMXNuyQl3wq9oZDAJchsf + fmoh080htkaxhEb1bMXa2Lj7j2OIkHOsQeIu6BdbxIKRPIT+zrcklE6ocW8fTWAS + Qi3IZ1FYLL+fs6TTxjx0VkC8QLaxWxY0pqTiwS7ndZiZKc3l3ARuvRk8buP+X3Jg + BD86FQ18OXZC9boMbDbzv2cOLtdkq5pS3lJE4F9gjYECgYEA69ukU2pNWot2OPwK + PuPwAXWNrvnvFzQgIc0qOiCmgKJU6wqunlop4Bx5XmetHExVyJVBEhaHoDr0F3Rs + gt8IclKDsWGXoVcgfu3llMimiZ05hOf/XtcGTCZwZenMQ30cFh4ZRuUu7WCZ9tqO + 28P8jCXB3IcaRpRnNvVvmCr5NXECgYEA09nUzRW993SlohceRW2C9fT9HZ4BaPWO + 5wVlnoo5mlUfAyzl+AGT/WlKmrn/1gAHIznQJ8ZIABQvPaBXhvkANXZP5Ie0lObw + jA7qFuKt7yV4GGlDnU1MOLh+acABMQBGSx8BJDaomH7glTiPEPTZjoP6wfAsd1uv + Knjt7jH2ad0CgYEAx9ghknRd+rx0fbBBVix4riPW20324ihOmZVnlD0aF6B0Z3tz + ncUz+irmQ7GBIpsjjIO60QK6BHAvZrhFQVaNp6B26ZORkSlr5WDZyImDYtMPa6fP + 36I+OcPQNOo3I3Acnjj+ne2PJ59Ula92oIudr3pGmv72qpsQIacw2TSAWGECgYEA + sdNAN+HPMn68ZaGoLDjvW8uIB6tQnay5hhvWn8yA65YV0RGH+7Q/Z9BQ6i3EnPor + A5uMqUZbu4011jHYJpiuXzHvf/GVWAO92KLQReOCgqHd/Aen1MtEdrwOiG+90Ebd + ukLNL3ud61tc4oS2OlJ8p48LFm2mtY3FLA6UEYPoxhUCgYEAtsfWIGnBh7XC+HwI + 2higSgN92VpJHSPOyOi0aG/u5AEQ+fsCUIi3KakxzvmiGMAEvWItkKyz2Gu8smtn + 2HVsGxI5UW7aLw9s3qe8kyMSfUk6pGamVhJUQmDr77+5zEzykPBxwGwDwdeR43CR + xVgf/Neb/avXgIgi6drj8dp1fWA= + -----END PRIVATE KEY----- + """ + self.rsa = RSA.importKey(self.pem_priv_key) + + def test_when_rsa_keystore_is_initialized_then_name_is_set(self): + rsa_keystore = tlsk.RSAKeystore(self.rsa.publickey(), self.rsa.key) + self.assertEqual("RSA Keystore", rsa_keystore.name) + self.assertEqual(self.rsa.key, rsa_keystore.private) + self.assertEqual(self.rsa.publickey(), rsa_keystore.public) + self.assertEqual(2048, rsa_keystore.size) + self.assertEqual(None, rsa_keystore.certificate) diff --git a/utils/fetch_iana_tls_registry.py b/utils/fetch_iana_tls_registry.py index 5e08795..180fe06 100644 --- a/utils/fetch_iana_tls_registry.py +++ b/utils/fetch_iana_tls_registry.py @@ -20,15 +20,16 @@ "https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xml", "https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xml") -if sys.version_info[0]*10+sys.version_info[1] < 27: +if sys.version_info[0] * 10 + sys.version_info[1] < 27: raise SystemExit('This utility requires Python 2.7 or higher.') + def post_process(title, d): norm_d = {} - for k,v in d.iteritems(): - if title=="APPLICATION_LAYER_PROTOCOL_NEGOTIATION_PROTOCOL_IDS": + for k, v in d.iteritems(): + if title == "APPLICATION_LAYER_PROTOCOL_NEGOTIATION_PROTOCOL_IDS": _ = k - k = "'%s'"%''.join(re.findall(r'\("([^"]+)"\)',v)) + k = "'%s'" % ''.join(re.findall(r'\("([^"]+)"\)', v)) v = normalize_value(_) else: k = normalize_key(k) @@ -37,7 +38,8 @@ def post_process(title, d): norm_d[k] = v return title, norm_d -def pprint(name,d): + +def pprint(name, d): """dump as python dict """ print ("%s = {" % name) @@ -54,19 +56,21 @@ def normalize_key(strval): # skip ranges return None elif '0x' in strval: - strval = "0x" + strval.replace('0x','').replace(',','') + strval = "0x" + strval.replace('0x', '').replace(',', '') else: try: - strval = "0x%0.2x"%(int(strval)) + strval = "0x%0.2x" % (int(strval)) except ValueError: - strval=repr(strval) + strval = repr(strval) return strval.lower() + def normalize_value(strval): """normalize values strip TLS_ prefix, remove parenthesis and dots, replace ' ',/,. ->"_" and """ - return re.sub( r'(\s|/|\.)','_',re.sub(r'(^TLS_|\s*\([^\)]+\))','',strval)) + return re.sub(r'(\s|/|\.)', '_', re.sub(r'(^TLS_|\s*\([^\)]+\))', '', strval)) + def normalize_title(strval): """normalize registry titles @@ -75,15 +79,16 @@ def normalize_title(strval): strval = re.sub(r'(-+|\s+|\([^\)]+\))', '_', strval) return re.sub(r'__+', '_', strval).rstrip("_").upper() -def xml_registry_to_dict(xmlroot, _id, - xml_key = './{http://www.iana.org/assignments}value', - xml_value = ('./{http://www.iana.org/assignments}description', - './{http://www.iana.org/assignments}name'), - xml_title = './{http://www.iana.org/assignments}title', - normalize_value = normalize_value, normalize_key = normalize_key, normalize_title=normalize_title, + +def xml_registry_to_dict(xmlroot, _id, + xml_key='./{http://www.iana.org/assignments}value', + xml_value=('./{http://www.iana.org/assignments}description', + './{http://www.iana.org/assignments}name'), + xml_title='./{http://www.iana.org/assignments}title', + normalize_value=normalize_value, normalize_key=normalize_key, normalize_title=normalize_title, verbose=False): d = {} - registry = xmlroot.find("{http://www.iana.org/assignments}registry[@id='%s']"%_id) + registry = xmlroot.find("{http://www.iana.org/assignments}registry[@id='%s']" % _id) if registry is None: return None, None title = normalize_title(registry.find(xml_title).text) @@ -96,12 +101,13 @@ def xml_registry_to_dict(xmlroot, _id, break value = value.text if key and value: - d[key]=value + d[key] = value except AttributeError as ae: if verbose: print ("# Skipping: %s" % repr(ae)) return post_process(title, d) + def main(sources, ids, verbose=False): print ("# -*- coding: UTF-8 -*-") arg_ids = ids @@ -114,21 +120,21 @@ def main(sources, ids, verbose=False): if '://' in source: xml_data = urllib2.urlopen(source).read() elif os.path.isfile(source): - with open(source,'r') as f: - xml_data=f.read() + with open(source, 'r') as f: + xml_data = f.read() else: raise Exception("Source not supported (url,file)!") xmlroot = ET.fromstring(xml_data) if not arg_ids: # fetch all ids - ids = (registry.attrib.get("id") for registry in xmlroot.findall("{http://www.iana.org/assignments}registry") if registry.attrib.get("id")) - - for _id in ids: - title,d = xml_registry_to_dict(xmlroot, _id=_id, verbose=verbose) - pprint(title,d) + ids = (registry.attrib.get("id") + for registry in xmlroot.findall("{http://www.iana.org/assignments}registry") if registry.attrib.get("id")) -if __name__=="__main__": - ids = sys.argv[1].strip().split(",") if len(sys.argv)>1 else None - main(sources = URL_IANA_DEFS, ids = ids, verbose=True) + for _id in ids: + title, d = xml_registry_to_dict(xmlroot, _id=_id, verbose=verbose) + pprint(title, d) +if __name__ == "__main__": + ids = sys.argv[1].strip().split(",") if len(sys.argv) > 1 else None + main(sources=URL_IANA_DEFS, ids=ids, verbose=True)