├── .gitignore ├── LICENSE ├── README.md ├── scripts ├── toytls-handshake.py ├── toytls-probe.py ├── toytls-sign.py └── toytls-verify.py └── toytls ├── __init__.py ├── hash.py ├── rc4.py ├── rsa.py ├── tls.py └── x509.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.py[co] 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | #Translations 25 | *.mo 26 | 27 | #Mr Developer 28 | .mr.developer.cfg 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 by Björn Edström 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | toytls 2 | ====== 3 | 4 | Fun with the TLS handshake 5 | 6 | See the article at http://blog.bjrn.se 7 | 8 | The code depends on m2crypto. 9 | -------------------------------------------------------------------------------- /scripts/toytls-handshake.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import socket 4 | import sys 5 | 6 | import toytls.tls as tls 7 | 8 | 9 | def do_handshake_socket(ctx, host, port): 10 | s = socket.socket() 11 | s.connect((host, port)) 12 | 13 | def send_func(buf): 14 | print '>>>', buf.encode('hex') 15 | s.sendall(buf) 16 | 17 | def recv_func(n=0): 18 | ret = s.recv(16*1024) 19 | print '<<<', ret.encode('hex') 20 | return ret 21 | 22 | try: 23 | tls.do_handshake(ctx, send_func, recv_func) 24 | finally: 25 | s.close() 26 | 27 | 28 | def main(): 29 | try: 30 | host, port = sys.argv[1:] 31 | port = int(port) 32 | except Exception, e: 33 | print >> sys.stderr, 'usage: %s HOST PORT' % (sys.argv[0],) 34 | sys.exit(1) 35 | 36 | ctx = tls.TLSContext() 37 | ctx.ciphers = [tls.TLS_RSA_WITH_RC4_128_SHA] 38 | ctx.client_random = 'a'*32 39 | ctx.pre_master_secret = tls.TLS_VERSION_STR + 'b'*46 40 | 41 | do_handshake_socket(ctx, host, port) 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /scripts/toytls-probe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import socket 3 | import sys 4 | 5 | import toytls.tls as tls 6 | 7 | SUITES = """ 8 | 0x00,0x01 TLS_RSA_WITH_NULL_MD5 9 | 0x00,0x02 TLS_RSA_WITH_NULL_SHA 10 | 0x00,0x03 TLS_RSA_EXPORT_WITH_RC4_40_MD5 11 | 0x00,0x04 TLS_RSA_WITH_RC4_128_MD5 12 | 0x00,0x05 TLS_RSA_WITH_RC4_128_SHA 13 | 0x00,0x06 TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 14 | 0x00,0x07 TLS_RSA_WITH_IDEA_CBC_SHA 15 | 0x00,0x08 TLS_RSA_EXPORT_WITH_DES40_CBC_SHA 16 | 0x00,0x09 TLS_RSA_WITH_DES_CBC_SHA 17 | 0x00,0x0A TLS_RSA_WITH_3DES_EDE_CBC_SHA 18 | 0x00,0x0B TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA 19 | 0x00,0x0C TLS_DH_DSS_WITH_DES_CBC_SHA 20 | 0x00,0x0D TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA 21 | 0x00,0x0E TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA 22 | 0x00,0x0F TLS_DH_RSA_WITH_DES_CBC_SHA 23 | 0x00,0x10 TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA 24 | 0x00,0x11 TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 25 | 0x00,0x12 TLS_DHE_DSS_WITH_DES_CBC_SHA 26 | 0x00,0x13 TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 27 | 0x00,0x14 TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA 28 | 0x00,0x15 TLS_DHE_RSA_WITH_DES_CBC_SHA 29 | 0x00,0x16 TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA 30 | 0x00,0x17 TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 31 | 0x00,0x18 TLS_DH_anon_WITH_RC4_128_MD5 32 | 0x00,0x19 TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA 33 | 0x00,0x1A TLS_DH_anon_WITH_DES_CBC_SHA 34 | 0x00,0x1B TLS_DH_anon_WITH_3DES_EDE_CBC_SHA 35 | 0x00,0x1E TLS_KRB5_WITH_DES_CBC_SHA 36 | 0x00,0x1F TLS_KRB5_WITH_3DES_EDE_CBC_SHA 37 | 0x00,0x20 TLS_KRB5_WITH_RC4_128_SHA 38 | 0x00,0x21 TLS_KRB5_WITH_IDEA_CBC_SHA 39 | 0x00,0x22 TLS_KRB5_WITH_DES_CBC_MD5 40 | 0x00,0x23 TLS_KRB5_WITH_3DES_EDE_CBC_MD5 41 | 0x00,0x24 TLS_KRB5_WITH_RC4_128_MD5 42 | 0x00,0x25 TLS_KRB5_WITH_IDEA_CBC_MD5 43 | 0x00,0x26 TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA 44 | 0x00,0x27 TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA 45 | 0x00,0x28 TLS_KRB5_EXPORT_WITH_RC4_40_SHA 46 | 0x00,0x29 TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 47 | 0x00,0x2A TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 48 | 0x00,0x2B TLS_KRB5_EXPORT_WITH_RC4_40_MD5 49 | 0x00,0x2F TLS_RSA_WITH_AES_128_CBC_SHA 50 | 0x00,0x30 TLS_DH_DSS_WITH_AES_128_CBC_SHA 51 | 0x00,0x31 TLS_DH_RSA_WITH_AES_128_CBC_SHA 52 | 0x00,0x32 TLS_DHE_DSS_WITH_AES_128_CBC_SHA 53 | 0x00,0x33 TLS_DHE_RSA_WITH_AES_128_CBC_SHA 54 | 0x00,0x34 TLS_DH_anon_WITH_AES_128_CBC_SHA 55 | 0x00,0x35 TLS_RSA_WITH_AES_256_CBC_SHA 56 | 0x00,0x36 TLS_DH_DSS_WITH_AES_256_CBC_SHA 57 | 0x00,0x37 TLS_DH_RSA_WITH_AES_256_CBC_SHA 58 | 0x00,0x38 TLS_DHE_DSS_WITH_AES_256_CBC_SHA 59 | 0x00,0x39 TLS_DHE_RSA_WITH_AES_256_CBC_SHA 60 | 0x00,0x3A TLS_DH_anon_WITH_AES_256_CBC_SHA 61 | 0x00,0x3B TLS_RSA_WITH_NULL_SHA256 62 | 0x00,0x3C TLS_RSA_WITH_AES_128_CBC_SHA256 63 | 0x00,0x3D TLS_RSA_WITH_AES_256_CBC_SHA256 64 | 0x00,0x3E TLS_DH_DSS_WITH_AES_128_CBC_SHA256 65 | 0x00,0x3F TLS_DH_RSA_WITH_AES_128_CBC_SHA256 66 | 0x00,0x40 TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 67 | 0x00,0x67 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 68 | 0x00,0x68 TLS_DH_DSS_WITH_AES_256_CBC_SHA256 69 | 0x00,0x69 TLS_DH_RSA_WITH_AES_256_CBC_SHA256 70 | 0x00,0x6A TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 71 | 0x00,0x6B TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 72 | 0x00,0x6C TLS_DH_anon_WITH_AES_128_CBC_SHA256 73 | 0x00,0x6D TLS_DH_anon_WITH_AES_256_CBC_SHA256 74 | 0x00,0x9C TLS_RSA_WITH_AES_128_GCM_SHA256 75 | 0x00,0x9D TLS_RSA_WITH_AES_256_GCM_SHA384 76 | 0x00,0x9E TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 77 | 0x00,0x9F TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 78 | 0x00,0xA0 TLS_DH_RSA_WITH_AES_128_GCM_SHA256 79 | 0x00,0xA1 TLS_DH_RSA_WITH_AES_256_GCM_SHA384 80 | 0x00,0xA2 TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 81 | 0x00,0xA3 TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 82 | 0x00,0xA4 TLS_DH_DSS_WITH_AES_128_GCM_SHA256 83 | 0x00,0xA5 TLS_DH_DSS_WITH_AES_256_GCM_SHA384 84 | 0x00,0xA6 TLS_DH_anon_WITH_AES_128_GCM_SHA256 85 | 0x00,0xA7 TLS_DH_anon_WITH_AES_256_GCM_SHA384 86 | 0xC0,0x01 TLS_ECDH_ECDSA_WITH_NULL_SHA 87 | 0xC0,0x02 TLS_ECDH_ECDSA_WITH_RC4_128_SHA 88 | 0xC0,0x03 TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 89 | 0xC0,0x04 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA 90 | 0xC0,0x05 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA 91 | 0xC0,0x06 TLS_ECDHE_ECDSA_WITH_NULL_SHA 92 | 0xC0,0x07 TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 93 | 0xC0,0x08 TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA 94 | 0xC0,0x09 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 95 | 0xC0,0x0A TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 96 | 0xC0,0x0B TLS_ECDH_RSA_WITH_NULL_SHA 97 | 0xC0,0x0C TLS_ECDH_RSA_WITH_RC4_128_SHA 98 | 0xC0,0x0D TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA 99 | 0xC0,0x0E TLS_ECDH_RSA_WITH_AES_128_CBC_SHA 100 | 0xC0,0x0F TLS_ECDH_RSA_WITH_AES_256_CBC_SHA 101 | 0xC0,0x10 TLS_ECDHE_RSA_WITH_NULL_SHA 102 | 0xC0,0x11 TLS_ECDHE_RSA_WITH_RC4_128_SHA 103 | 0xC0,0x12 TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 104 | 0xC0,0x13 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 105 | 0xC0,0x14 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 106 | 0xC0,0x15 TLS_ECDH_anon_WITH_NULL_SHA 107 | 0xC0,0x16 TLS_ECDH_anon_WITH_RC4_128_SHA 108 | 0xC0,0x17 TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA 109 | 0xC0,0x18 TLS_ECDH_anon_WITH_AES_128_CBC_SHA 110 | 0xC0,0x19 TLS_ECDH_anon_WITH_AES_256_CBC_SHA 111 | 0xC0,0x23 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 112 | 0xC0,0x24 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 113 | 0xC0,0x25 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 114 | 0xC0,0x26 TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 115 | 0xC0,0x27 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 116 | 0xC0,0x28 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 117 | 0xC0,0x29 TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 118 | 0xC0,0x2A TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 119 | 0xC0,0x2B TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 120 | 0xC0,0x2C TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 121 | 0xC0,0x2D TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 122 | 0xC0,0x2E TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 123 | 0xC0,0x2F TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 124 | 0xC0,0x30 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 125 | 0xC0,0x31 TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 126 | 0xC0,0x32 TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 127 | 0xcc,0x13 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 128 | 0xcc,0x14 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 129 | 0xcc,0x15 TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 130 | """ 131 | 132 | 133 | def probe_one(host, port, cipher): 134 | s = socket.socket() 135 | s.connect((host, port)) 136 | ctx = tls.TLSContext() 137 | ctx.ciphers = [cipher] 138 | ctx.client_random = 'a'*32 139 | msg = tls.make_msg_client_hello(ctx) 140 | s.send(tls.header(*msg) + msg[1]) 141 | ret = s.recv(16*1024) 142 | s.close() 143 | return ret[0] != '\x15' 144 | 145 | 146 | def probe(host, port): 147 | for suite in SUITES.split('\n'): 148 | if not suite: 149 | continue 150 | num, name = suite.split(' ') 151 | a, b = eval(num) 152 | num = (a << 8) | b 153 | 154 | if probe_one(host, port, num): 155 | print '0x%04x' % num, name 156 | 157 | if __name__ == '__main__': 158 | probe(sys.argv[1], int(sys.argv[2])) 159 | 160 | -------------------------------------------------------------------------------- /scripts/toytls-sign.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import socket 5 | import sys 6 | 7 | import toytls.tls as tls 8 | 9 | 10 | def hexdump(bytes, step=32): 11 | i = 0 12 | while True: 13 | sub = bytes[i:i+step] 14 | if not sub: 15 | break 16 | print sub.encode('hex') 17 | i += step 18 | 19 | 20 | def do_handshake_socket(ctx, host, port): 21 | s = socket.socket() 22 | s.connect((host, port)) 23 | 24 | def send_func(buf): 25 | s.sendall(buf) 26 | 27 | def recv_func(n=0): 28 | ret = s.recv(16*1024) 29 | return ret 30 | 31 | try: 32 | tls.do_handshake(ctx, send_func, recv_func) 33 | finally: 34 | s.close() 35 | 36 | 37 | def main(): 38 | try: 39 | host, port, path = sys.argv[1:] 40 | port = int(port) 41 | except Exception, e: 42 | print >> sys.stderr, 'usage: %s HOST PORT PATH' % (sys.argv[0],) 43 | sys.exit(1) 44 | 45 | message = sys.stdin.read() 46 | 47 | if len(message) > 32: 48 | print >> sys.stderr, 'error: message length must be less than 32 bytes' 49 | sys.exit(1) 50 | message += '\x00' * (32 - len(message)) 51 | assert len(message) == 32 52 | 53 | print 'Signing message:' 54 | hexdump(message) 55 | print 56 | 57 | ctx = tls.TLSContext() 58 | ctx.ciphers = [tls.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 59 | tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, 60 | tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 61 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 62 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 63 | ctx.client_random = message 64 | ctx.pre_master_secret = tls.TLS_VERSION_STR + 'b'*46 65 | 66 | do_handshake_socket(ctx, host, port) 67 | 68 | print 'Bytes signed:' 69 | hexdump(ctx.signed_bytes) 70 | file(path + '.bytes', 'w').write(ctx.signed_bytes) 71 | print 72 | print 'Signature:' 73 | hexdump(ctx.signed_signature) 74 | file(path + '.signature', 'w').write(ctx.signed_signature) 75 | print 76 | print 'Server certificate:' 77 | hexdump(ctx.certificates[0]) 78 | file(path + '.certificate.der', 'w').write(ctx.certificates[0]) 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /scripts/toytls-verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | import hashlib 5 | import os 6 | import socket 7 | import sys 8 | import struct 9 | 10 | import toytls.tls as tls 11 | import toytls.rsa 12 | import toytls.x509 13 | 14 | 15 | def hexdump(bytes, step=32): 16 | i = 0 17 | while True: 18 | sub = bytes[i:i+step] 19 | if not sub: 20 | break 21 | print sub.encode('hex') 22 | i += step 23 | 24 | 25 | def main(): 26 | try: 27 | path = sys.argv[1] 28 | except Exception, e: 29 | print >> sys.stderr, 'usage: %s PATH' % (sys.argv[0],) 30 | sys.exit(1) 31 | 32 | certificate = file(path + '.certificate.der').read() 33 | signature = file(path + '.signature').read() 34 | bytes = file(path + '.bytes').read() 35 | 36 | rsa = toytls.rsa.RSA(*toytls.x509.parse_der(certificate)) 37 | check = rsa.verify(signature, hashlib.md5(bytes).digest() + hashlib.sha1(bytes).digest()) 38 | 39 | if check: 40 | print 'Signature Verification SUCCESS' 41 | print 42 | else: 43 | print >> sys.stderr, 'Signature Verification FAILURE' 44 | sys.exit(1) 45 | 46 | print 'Bytes signed:' 47 | hexdump(bytes) 48 | print 49 | print 'Bytes signed, user supplied messsage (hex):' 50 | hexdump(bytes[0:32]) 51 | print 52 | print 'Bytes signed, user supplied messsage (repr):' 53 | print repr(bytes[0:32]) 54 | print 55 | print 'Bytes signed, server unix timestamp:' 56 | ts, = struct.unpack('!L', bytes[32:32+4]) 57 | print ts 58 | print 59 | print 'Bytes signed, server UTC timestamp:' 60 | print datetime.datetime.utcfromtimestamp(ts) 61 | print 62 | print 'Signature:' 63 | hexdump(signature) 64 | print 65 | print 'Server certificate. For more details, do:' 66 | print ' $ openssl asn1parse -inform DER -in %s' % (path + '.certificate.der',) 67 | print 68 | hexdump(certificate) 69 | 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /toytls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjornedstrom/toytls/302834e4776880579057b1eed6464008075a1770/toytls/__init__.py -------------------------------------------------------------------------------- /toytls/hash.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ reference implementation of TLS PRF from tlslite 4 | """ 5 | 6 | import hashlib 7 | import math 8 | import hmac 9 | 10 | 11 | def P_hash(hashmod, secret, seed, length): 12 | bytes = ['\x00'] * length 13 | A = seed 14 | index = 0 15 | while 1: 16 | A = hmac.HMAC(secret, A, hashmod).digest() 17 | output = hmac.HMAC(secret, A+seed, hashmod).digest() 18 | for c in output: 19 | if index >= length: 20 | return ''.join(bytes) 21 | bytes[index] = c 22 | index += 1 23 | return ''.join(bytes) 24 | 25 | 26 | def PRF(secret, label, seed, length): 27 | half = int(math.ceil(len(secret)/2.0)) 28 | S1 = secret[:half] 29 | S2 = secret[half:] 30 | p_md5 = P_hash(hashlib.md5, S1, label + seed, length) 31 | p_sha1 = P_hash(hashlib.sha1, S2, label + seed, length) 32 | res = [] 33 | for x in range(length): 34 | res.append(chr(ord(p_md5[x]) ^ ord(p_sha1[x]))) 35 | return ''.join(res) 36 | -------------------------------------------------------------------------------- /toytls/rc4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from M2Crypto import m2 4 | 5 | class RC4(object): 6 | def __init__(self, key): 7 | if len(key) < 16 or len(key) > 256: 8 | raise ValueError() 9 | self.rc4 = m2.rc4_new() 10 | m2.rc4_set_key(self.rc4, key) 11 | 12 | def __del__(self): 13 | m2.rc4_free(self.rc4) 14 | 15 | def encrypt(self, plaintext): 16 | return m2.rc4_update(self.rc4, plaintext) 17 | 18 | def decrypt(self, ciphertext): 19 | return self.encrypt(ciphertext) 20 | -------------------------------------------------------------------------------- /toytls/rsa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import math 4 | import struct 5 | import hashlib 6 | 7 | from M2Crypto import m2 8 | 9 | 10 | def mpi_to_num(mpi): 11 | return int(m2.bn_to_hex(m2.mpi_to_bn(mpi)), 16) 12 | 13 | def num_to_mpi(num): 14 | return m2.bn_to_mpi(m2.dec_to_bn(str(num))) 15 | 16 | def num_to_bytes(num, padding=None): 17 | def raw(): 18 | if num == 0: 19 | return '' 20 | b = num_to_mpi(num)[4:] 21 | if b[0] == '\x00': 22 | return b[1:] 23 | return b 24 | ret = raw() 25 | if padding is None: 26 | return ret 27 | return '\x00' * (padding - len(ret)) + ret 28 | 29 | def bytes_to_num(bytes): 30 | return mpi_to_num(struct.pack('!Lb', len(bytes) + 1, 0) + bytes) 31 | 32 | def getRandomBytes(howMany): 33 | s = os.urandom(howMany) 34 | assert(len(s) == howMany) 35 | return s 36 | 37 | class RSA(object): 38 | def __init__(self, n, e): 39 | self.rsa = None 40 | self.n = n 41 | self.e = e 42 | 43 | self.rsa = m2.rsa_new() 44 | m2.rsa_set_n(self.rsa, num_to_mpi(self.n)) 45 | m2.rsa_set_e(self.rsa, num_to_mpi(self.e)) 46 | 47 | self.n_bytes = len(num_to_bytes(self.n)) 48 | 49 | def encrypt(self, bytes): 50 | padded = self._pkcs1_padding(bytes) 51 | m = bytes_to_num(padded) 52 | if m >= self.n: 53 | raise ValueError() 54 | c = self._public_crypt(m) 55 | enc = num_to_bytes(c, self.n_bytes) 56 | return enc 57 | 58 | def verify(self, signature, bytes): 59 | if len(signature) != self.n_bytes: 60 | return False 61 | padded_bytes = self._pkcs1_padding(bytes, True) 62 | c = bytes_to_num(signature) 63 | if c >= self.n: 64 | return False 65 | m = self._public_crypt(c) 66 | check_bytes = num_to_bytes(m, self.n_bytes) 67 | #print (check_bytes, padded_bytes) 68 | return check_bytes == padded_bytes 69 | 70 | def _pkcs1_padding(self, bytes, signature=False): 71 | pad_length = (self.n_bytes - (len(bytes)+3)) 72 | 73 | if signature: 74 | pad = [0xFF] * pad_length 75 | padding_type = 1 76 | else: 77 | pad = [] 78 | padding_type = 2 79 | while len(pad) < pad_length: 80 | 81 | # XXX (bjorn): Avoid this 82 | # pad_bytes = getRandomBytes(pad_length * 2) 83 | pad_bytes = [1]*(pad_length*2) 84 | ### 85 | pad = [b for b in pad_bytes if b != 0] 86 | pad = pad[:pad_length] 87 | 88 | 89 | padding = ''.join(map(chr,[0, padding_type] + pad + [0])) 90 | return padding + bytes 91 | 92 | def _public_crypt(self, c): 93 | s = num_to_bytes(c, self.n_bytes) 94 | m = bytes_to_num(m2.rsa_public_decrypt(self.rsa, s, m2.no_padding)) 95 | return m 96 | -------------------------------------------------------------------------------- /toytls/tls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2012 Björn Edström 4 | # See license for details. 5 | 6 | """ 7 | http://tools.ietf.org/html/rfc5246#section-7.4.2 8 | http://tools.ietf.org/html/rfc4492#section-5.4 9 | http://tools.ietf.org/html/rfc6066 10 | """ 11 | 12 | import hashlib 13 | import hmac 14 | import struct 15 | import cStringIO as stringio 16 | import socket 17 | import time 18 | 19 | import toytls.rc4 20 | import toytls.rsa 21 | import toytls.hash 22 | import toytls.x509 23 | 24 | 25 | TLS_VERSION = 0x0302 # TLS 1.1 26 | TLS_VERSION_STR = '\x03\x02' # TLS 1.1 27 | 28 | TLS_CONTENT_HANDSHAKE = 22 29 | TLS_CONTENT_CIPHER_SPEC = 20 30 | 31 | TLS_HANDSHAKE_CLIENT_HELLO = 1 32 | TLS_HANDSHAKE_SERVER_HELLO = 2 33 | TLS_HANDSHAKE_CERTIFICATE = 11 34 | TLS_HANDSHAKE_SERVER_HELLO_DONE = 14 35 | TLS_HANDSHAKE_CLIENT_KEY_EXCHANGE = 0x10 36 | TLS_HANDSHAKE_FINISHED = 0x14 37 | 38 | TLS_RSA_WITH_RC4_128_SHA = 0x0005 39 | TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 40 | TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xc011 41 | TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xc012 42 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013 43 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014 44 | 45 | 46 | TLS_CIPHER_SUITE = [ 47 | # name, mac length, key length, iv length 48 | (TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 20, 16, 0) 49 | ] 50 | 51 | 52 | class TLSContext(object): 53 | def __init__(self): 54 | self.cipher = None 55 | self.ciphers = None 56 | self.client_random = None 57 | self.server_random = None 58 | self.pre_master_secret = None 59 | self.rsa = None 60 | self.certificates = [] 61 | self.client_num = 0 62 | self.server_num = 0 63 | self.signed_bytes = None 64 | self.signed_signature = None 65 | 66 | # private 67 | self._handshake_hash_sha1 = hashlib.sha1() 68 | self._handshake_hash_md5 = hashlib.md5() 69 | 70 | def master_secret(self): 71 | return toytls.hash.PRF(self.pre_master_secret, 72 | 'master secret', 73 | self.client_random + self.server_random, 74 | 48) 75 | 76 | def update_hash(self, buf): 77 | self._handshake_hash_sha1.update(buf) 78 | self._handshake_hash_md5.update(buf) 79 | 80 | def setup_crypto(self): 81 | crypto_settings = TLS_CIPHER_SUITE[0] # XXX Hard coded 82 | 83 | material = stringio.StringIO(toytls.hash.PRF(self.master_secret(), 84 | 'key expansion', 85 | self.server_random + self.client_random, 86 | crypto_settings[1]*2 + crypto_settings[2]*2 + crypto_settings[3]*2)) 87 | 88 | client_mac = material.read(crypto_settings[1]) 89 | server_mac = material.read(crypto_settings[1]) 90 | 91 | client_key = material.read(crypto_settings[2]) 92 | server_key = material.read(crypto_settings[2]) 93 | 94 | client_iv = material.read(crypto_settings[3]) 95 | server_iv = material.read(crypto_settings[3]) 96 | 97 | self.client_enc = toytls.rc4.RC4(client_key) 98 | self.server_enc = toytls.rc4.RC4(server_key) 99 | 100 | hmac_func = lambda k: hmac.new(k, digestmod=hashlib.sha1) 101 | 102 | self.client_mac = hmac_func(client_mac) 103 | self.server_mac = hmac_func(server_mac) 104 | 105 | def get_client_verify_data(self): 106 | handshake_hashes = self._handshake_hash_md5.digest() + self._handshake_hash_sha1.digest() 107 | self.client_verify_data = toytls.hash.PRF(self.master_secret(), 108 | 'client finished', handshake_hashes, 12) 109 | 110 | def get_server_verify_data(self): 111 | handshake_hashes = self._handshake_hash_md5.digest() + self._handshake_hash_sha1.digest() 112 | self.server_verify_data = toytls.hash.PRF(self.master_secret(), 113 | 'server finished', handshake_hashes, 12) 114 | 115 | 116 | def header(content_type, msg): 117 | return struct.pack('!bHH', content_type, TLS_VERSION, len(msg)) 118 | 119 | 120 | def sub_header_handshake(msg_type, msg): 121 | header = struct.pack('!L', (msg_type << 24) | len(msg)) 122 | return header + msg 123 | 124 | 125 | def make_msg_client_hello(ctx): 126 | assert ctx.client_random and len(ctx.client_random) == 32 127 | 128 | # 7.4.1.4.1. Signature Algorithms 129 | #ext_data = '\x02\x01\x02\x02' # sha1-rsa 130 | #extension = struct.pack('!HH', 13, len(ext_data)) + ext_data 131 | 132 | extension = '' 133 | 134 | # http://tools.ietf.org/html/rfc4492#section-5.4 0xc011 TLS_ECDHE_RSA_WITH_RC4_128_SHA (TLS 3.1) 135 | 136 | ciphers = '' 137 | for cipher in ctx.ciphers: 138 | ciphers += struct.pack('!H', cipher) 139 | 140 | client_hello = TLS_VERSION_STR + ctx.client_random + \ 141 | struct.pack('!bH%ssHH' % len(ciphers), 142 | 0, 143 | len(ciphers), # cipher suites length 144 | ciphers, 145 | 0x0100, # no compression 146 | len(extension) 147 | ) + extension 148 | 149 | client_hello = sub_header_handshake(TLS_HANDSHAKE_CLIENT_HELLO, client_hello) 150 | 151 | ctx.update_hash(client_hello) 152 | 153 | return (TLS_CONTENT_HANDSHAKE, client_hello) 154 | 155 | 156 | def make_msg_client_key_exchange(ctx): 157 | assert ctx.rsa 158 | assert ctx.pre_master_secret and len(ctx.pre_master_secret) == 48 159 | 160 | encrypted_pre_master_secret = ctx.rsa.encrypt(ctx.pre_master_secret) 161 | 162 | key = '\x00\x80' + str(encrypted_pre_master_secret) 163 | 164 | client_key_exchange = sub_header_handshake(TLS_HANDSHAKE_CLIENT_KEY_EXCHANGE, key) 165 | 166 | ctx.update_hash(client_key_exchange) 167 | 168 | return (TLS_CONTENT_HANDSHAKE, client_key_exchange) 169 | 170 | 171 | def make_msg_change_cipher_spec(ctx): 172 | 173 | return (TLS_CONTENT_CIPHER_SPEC, '\x01') 174 | 175 | 176 | def parse_change_cipher_spec(ctx, s): 177 | 178 | header_type, header_version, header_length = \ 179 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 180 | 181 | assert s.read(1) == '\x01' 182 | 183 | 184 | def make_msg_finished(ctx): 185 | assert ctx.client_verify_data 186 | 187 | finished = sub_header_handshake(TLS_HANDSHAKE_FINISHED, str(ctx.client_verify_data)) 188 | 189 | ctx.update_hash(finished) 190 | 191 | return (TLS_CONTENT_HANDSHAKE, finished) 192 | 193 | 194 | def parse_finished(ctx, s): 195 | 196 | header_type, header_version, header_length = \ 197 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 198 | 199 | assert header_type == 0x16 200 | 201 | msg = decrypt_msg(ctx, 'server', header_type, s.read(header_length)) 202 | 203 | ss = stringio.StringIO(msg[1]) 204 | 205 | type_length, = struct.unpack('!L', ss.read(4)) 206 | msg_type = type_length >> 24 207 | msg_length = type_length & 0xffffff 208 | 209 | assert msg_type == 0x14 # XXX 210 | 211 | assert ss.read() == ctx.server_verify_data 212 | 213 | 214 | def parse_server_hello(ctx, s): 215 | header_type, header_version, header_length = \ 216 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 217 | 218 | buf = s.read(header_length) 219 | 220 | ctx.update_hash(buf) 221 | 222 | ss = stringio.StringIO(buf) 223 | 224 | type_length, = struct.unpack('!L', ss.read(4)) 225 | msg_type = type_length >> 24 226 | msg_length = type_length & 0xffffff 227 | 228 | assert msg_type == TLS_HANDSHAKE_SERVER_HELLO 229 | 230 | assert ss.read(2) == TLS_VERSION_STR # version 231 | 232 | ctx.server_random = ss.read(32) 233 | 234 | session_length, = struct.unpack('!b', ss.read(1)) 235 | session_id = ss.read(session_length) 236 | yy = ss.read(3) 237 | ctx.cipher, compression = struct.unpack('!Hb', yy) 238 | 239 | 240 | def parse_server_key_exchange(ctx, s): 241 | 242 | if ctx.ciphers in ([TLS_RSA_WITH_RC4_128_SHA],): 243 | return 244 | 245 | assert ctx.rsa 246 | 247 | header_type, header_version, header_length = \ 248 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 249 | 250 | buf = s.read(header_length) 251 | 252 | ctx.update_hash(buf) 253 | 254 | ss = stringio.StringIO(buf) 255 | 256 | type_length, = struct.unpack('!L', ss.read(4)) 257 | msg_type = type_length >> 24 258 | msg_length = type_length & 0xffffff 259 | 260 | assert msg_type == 12 261 | 262 | raw = [] 263 | def get_field(ss): 264 | x = ss.read(2) 265 | length, = struct.unpack('!H', x) 266 | y = ss.read(length) 267 | raw.append(x + y) 268 | return y 269 | 270 | #opaque dh_p<1..2^16-1>; 271 | #opaque dh_g<1..2^16-1>; 272 | #opaque dh_Ys<1..2^16-1>; 273 | 274 | if ctx.cipher == TLS_DHE_RSA_WITH_AES_128_CBC_SHA: 275 | dh_p = get_field(ss) 276 | dh_g = get_field(ss) 277 | df_Ys = get_field(ss) 278 | 279 | elif ctx.cipher in (TLS_ECDHE_RSA_WITH_RC4_128_SHA, 280 | TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 281 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 282 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA): 283 | tmp = ss.read(1) # type 284 | assert tmp == '\x03' 285 | raw.append(tmp) 286 | tmp = ss.read(2) # named curve 287 | raw.append(tmp) 288 | length_str = ss.read(1) 289 | raw.append(length_str) 290 | point = ss.read(ord(length_str)) 291 | raw.append(point) 292 | 293 | bytes = ctx.client_random + ctx.server_random + ''.join(raw) 294 | 295 | ctx.signed_bytes = bytes 296 | 297 | length, = struct.unpack('!H', ss.read(2)) 298 | signature = ss.read(length) 299 | ctx.signed_signature = signature 300 | assert ss.read() == '' 301 | 302 | 303 | 304 | def parse_server_hello_done(ctx, s): 305 | header_type, header_version, header_length = \ 306 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 307 | 308 | buf = s.read(header_length) 309 | 310 | ctx.update_hash(buf) 311 | 312 | ss = stringio.StringIO(buf) 313 | 314 | type_length, = struct.unpack('!L', ss.read(4)) 315 | msg_type = type_length >> 24 316 | msg_length = type_length & 0xffffff 317 | 318 | assert msg_type == TLS_HANDSHAKE_SERVER_HELLO_DONE 319 | 320 | 321 | def parse_server_certificate(ctx, s): 322 | header_type, header_version, header_length = \ 323 | struct.unpack('!bHH', s.read(1 + 2 + 2)) 324 | 325 | buf = s.read(header_length) 326 | 327 | ctx.update_hash(buf) 328 | 329 | ss = stringio.StringIO(buf) 330 | 331 | type_length, = struct.unpack('!L', ss.read(4)) 332 | msg_type = type_length >> 24 333 | msg_length = type_length & 0xffffff 334 | 335 | assert msg_type == TLS_HANDSHAKE_CERTIFICATE 336 | 337 | length, = struct.unpack('!L', '\x00' + ss.read(3)) 338 | 339 | while length: 340 | length2, = struct.unpack('!L', '\x00' + ss.read(3)) 341 | certificate = ss.read(length2) 342 | #print repr(certificate) 343 | 344 | ctx.certificates.append(certificate) 345 | 346 | length -= 3 + length2 347 | 348 | 349 | def encrypt_msg(ctx, who, content_type, msg): 350 | if who == 'client': 351 | enc_obj = ctx.client_enc 352 | mac_obj = ctx.client_mac 353 | else: 354 | enc_obj = ctx.server_enc 355 | mac_obj = ctx.server_mac 356 | 357 | # mac 358 | if who == 'client': 359 | seq = struct.pack('!Q', ctx.client_num) 360 | ctx.client_num += 1 361 | else: 362 | seq = struct.pack('!Q', ctx.server_num) 363 | ctx.server_num += 1 364 | 365 | mac_obj = mac_obj.copy() 366 | mac_obj.update(seq) 367 | mac_obj.update(header(content_type, msg)) 368 | mac_obj.update(msg) 369 | msg_mac = mac_obj.digest() 370 | 371 | #print 'MAC', repr(msg_mac) 372 | 373 | # crypto 374 | msg_crypted = enc_obj.encrypt(msg + msg_mac) 375 | 376 | return content_type, msg_crypted 377 | 378 | 379 | def decrypt_msg(ctx, who, content_type, msg): 380 | if who == 'client': 381 | enc_obj = ctx.client_enc 382 | mac_obj = ctx.client_mac 383 | else: 384 | enc_obj = ctx.server_enc 385 | mac_obj = ctx.server_mac 386 | 387 | # crypto 388 | plaintext = enc_obj.decrypt(msg) 389 | msg, msg_mac = plaintext[:-20], plaintext[-20:] # XXX hard coded size of 390 | 391 | # mac 392 | if who == 'client': 393 | seq = struct.pack('!Q', ctx.client_num) 394 | ctx.client_num += 1 395 | else: 396 | seq = struct.pack('!Q', ctx.server_num) 397 | ctx.server_num += 1 398 | 399 | mac_obj = mac_obj.copy() 400 | mac_obj.update(seq) 401 | mac_obj.update(header(content_type, msg)) 402 | mac_obj.update(msg) 403 | msg_mac_calculated = mac_obj.digest() 404 | 405 | #print 'MAC', repr(msg_mac), repr(msg_mac_calculated) 406 | 407 | assert msg_mac == msg_mac_calculated 408 | 409 | return content_type, msg 410 | 411 | 412 | def do_handshake(ctx, send_func, recv_func): 413 | 414 | def send(msg): 415 | ret = (header(*msg) + msg[1]) 416 | send_func(ret) 417 | 418 | def recv(): 419 | ret = recv_func() 420 | return ret 421 | 422 | msg = make_msg_client_hello(ctx) 423 | send(msg) 424 | time.sleep(0.5) # XXX 425 | ret = stringio.StringIO(recv()) 426 | parse_server_hello(ctx, ret) 427 | parse_server_certificate(ctx, ret) 428 | ctx.rsa = toytls.rsa.RSA(*toytls.x509.parse_der(ctx.certificates[0])) 429 | 430 | parse_server_key_exchange(ctx, ret) 431 | parse_server_hello_done(ctx, ret) 432 | 433 | if TLS_RSA_WITH_RC4_128_SHA not in ctx.ciphers: 434 | return 435 | 436 | msg = make_msg_client_key_exchange(ctx) 437 | send(msg) 438 | msg = make_msg_change_cipher_spec(ctx) 439 | send(msg) 440 | ctx.get_client_verify_data() 441 | ctx.setup_crypto() 442 | 443 | # encrypted step 444 | msg = make_msg_finished(ctx) 445 | msg = encrypt_msg(ctx, 'client', msg[0], msg[1]) 446 | send(msg) 447 | 448 | ctx.get_server_verify_data() 449 | 450 | time.sleep(1.5) 451 | ret = stringio.StringIO(recv()) 452 | parse_change_cipher_spec(ctx, ret) 453 | parse_finished(ctx, ret) 454 | 455 | 456 | def do_handshake_socket(ctx, host, port): 457 | s = socket.socket() 458 | s.connect((host, port)) 459 | 460 | def send_func(buf): 461 | s.sendall(buf) 462 | def recv_func(n=0): 463 | return s.recv(16*1024) 464 | 465 | try: 466 | do_handshake(ctx, send_func, recv_func) 467 | finally: 468 | s.close() 469 | 470 | 471 | def do_handshake_transcript(transcript): 472 | send_buf = [] 473 | recv_buf = [] 474 | 475 | for line in transcript.split('\n'): 476 | if line.startswith('>>> '): 477 | send_buf.append(line[4:].decode('hex')) 478 | elif line.startswith('<<< '): 479 | recv_buf.append(line[4:].decode('hex')) 480 | 481 | def send_func(buf): 482 | pass 483 | 484 | def recv_func(n=0): 485 | s = recv_buf[0] 486 | del recv_buf[0] 487 | return s 488 | 489 | do_handshake(send_func, recv_func) 490 | -------------------------------------------------------------------------------- /toytls/x509.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from M2Crypto import m2, X509 4 | 5 | def mpi_to_num(mpi): 6 | return int(m2.bn_to_hex(m2.mpi_to_bn(mpi)), 16) 7 | 8 | def parse_der(der): 9 | cert = X509.load_cert_der_string(der) 10 | 11 | #log.debug('PARSING CERTIFICATE %s', cert.get_subject().as_text()) 12 | 13 | rsa = cert.get_pubkey().get_rsa() 14 | e = mpi_to_num(m2.rsa_get_e(rsa.rsa)) 15 | n = mpi_to_num(m2.rsa_get_n(rsa.rsa)) 16 | return (n, e) 17 | --------------------------------------------------------------------------------