2010/07/24

ssl-v3

今まで OpenSSL/GnuTLS/Java のライブラリを使って証明書取ってきました
そろそろ SSL のこと詳しく知ってもいいんじゃない?
でも暗号とか難しそうだしどうしようかなぁ

手元に「マスタリングTCP/IP SSL/TLS編」はあったので
つらつらと眺めたり wireshark のログを見て分かってきたので
更に原本にあたろうと思い SSLv3 の仕様を眺めることに
The SSL Protocol Version 3.0
SSLv2 はもぉ使わない方がいいらしいですしね
TLSv1 でもいいんですが、何となく SSL の方を選択

すげー簡単に SSL の手順書くと
  • client hello で使える暗号スイートリストと client random 送信
  • server hello で使う暗号スイートと server random 送信
  • server certificate でサーバの証明書送信
  • client key exchange で pre-master secret 送信
  • 両者で鍵作って finish
細かいことは解説してるサイトを見るということで

よーし、とーさん Python で実装しちゃうぞー、と思いますよね
データのやりとりは TCP などの上で SSL Record とかいう
version と type と length だけ頭に付ける単純な入れ物に入れて行われて
中身もまぁ大体似たようなもんでした、type と length くらい

client hello は random とか送るんだけど決め打ちで
時間を入れるとこがあるんでそこだけ真面目に入れてみました

server hello done まではまだ暗号とか使ってないんで
パースして証明書を取り出してみました

ここまでで力尽きました
標準ライブラリだけでガンバりましたヨ!!
def bin2int(b):
return sum(256**e*x for (e, x) in enumerate(reversed([ord(x) for x in b])))

def recv_server_hello_from(s):
messages = []

received = ''
while True:
while len(received) < 5:
bytes = s.recv(1024)
if len(bytes) == 0:
raise socket.error("0 bytes received")
received += bytes

header, received = received[:5], received[5:]
content_type = ord(header[0])
version_major = ord(header[1])
version_minor = ord(header[2])
if not (content_type == 22
and version_major == 3 and version_minor in range(3)):
raise socket.error("not SSLv3/TLS")
content_length = bin2int(header[3:5])

while len(received) < content_length:
bytes = s.recv(1024)
if len(bytes) == 0:
raise socket.error("0 bytes received")
received += bytes

content, received = received[:content_length], received[content_length:]

class ServerHelloDone(Exception):
pass
try:
while content:
msg_type = ord(content[0])
if msg_type == 14:
raise ServerHelloDone
if msg_type not in (2, 11, 12, 13, 14, 15, 16, 20):
raise socket.error("not SSLv3/TLS msg_type = %d" % msg_type)
msg_length = bin2int(content[1:4])
msg = content[4:4+msg_length]
messages.append((msg_type, msg_length, msg))
content = content[4+msg_length:]
except ServerHelloDone:
break

return messages

def parse_server_certificate(data):
certs = []

length, data = data[:3], data[3:]

while data:
length = bin2int(data[:3])
certs.append(data[3:length+3])
data = data[length+3:]

return certs

if __name__ == '__main__':
import socket
import struct
import time

client_hello = (b'\x16\x03\x00\x00`\x01\x00\x00\\\x03\x00'
+ struct.pack('>I', int(time.time()))
+ 'L?\xac\xc8\xc5lw?\xa8\x1a\xce\xdf\x8d\xc0\x96'
+ '\t\x97\xab\xa1dv\xc1\xec\x9aj\xc8\x0f(\x00\x004\x00'
+ ':\x009\x008\x005\x004\x003\x002\x00/\x00\x1b\x00\x1a'
+ '\x00\x19\x00\x18\x00\x17\x00\x16\x00\x15\x00\x14\x00'
+ '\x13\x00\x12\x00\x11\x00\n\x00\t\x00\x08\x00\x06\x00'
+ '\x05\x00\x04\x00\x03\x02\x01\x00')

s = socket.socket()
s.connect(('www.google.com', 443))
s.send(client_hello)

for (msg_type, msg_length, msg) in recv_server_hello_from(s):
if msg_type == 11:
for cert in parse_server_certificate(msg):
print '-----BEGIN CERTIFICATE-----'
print cert.encode('base64'),
print '-----END CERTIFICATE-----'
これ動かすと
$ python sslv3.py 
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQG
EwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhh
d3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVT
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApH
b29nbGUgSW5jMRcwFQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj9xVo
RaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vuuWciKh0R73mkszeK
9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0w
K6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYI
KwYBBQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzAB
hhZodHRwOi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j
b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOBgQCfQ89bxFAp
sb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ONgJd8IyAPkU0Wueru9G2Jysa9
zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqA
ibAxWEEHXw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzEXMBUGA1UE
ChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAwMDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYD
VQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMN
VGhhd3RlIFNHQyBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGh
PwtxPKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g5/OIty0y
3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo3nWhLHpo39XKHIdYYBkC
AwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsGA1UdDwQEAwIBBjARBglghkgBhvhCAQEE
BAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCow
KDAmoCSgIoYgaHR0cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAk
MCIGCCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUFBwMB
BggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUAA4GBAFWsY+re
od3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTcq3J5Lwa/q4FwxKjt6lM07e8eU9kG
x1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTRbcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXW
rOU/VG+WHgWv
-----END CERTIFICATE-----
証明書が出てきます

つづく (のか!?)