summaryrefslogtreecommitdiff
path: root/monitor/verify_sct.py
diff options
context:
space:
mode:
Diffstat (limited to 'monitor/verify_sct.py')
-rwxr-xr-xmonitor/verify_sct.py239
1 files changed, 239 insertions, 0 deletions
diff --git a/monitor/verify_sct.py b/monitor/verify_sct.py
new file mode 100755
index 0000000..32e460d
--- /dev/null
+++ b/monitor/verify_sct.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python
+
+# Signed Certificate Timestamp TLS extension verifier
+# Copyright (c) 2015 Pier Carlo Chiodi - http://www.pierky.com
+#
+# https://github.com/pierky/sct-verify
+
+import sys
+import subprocess
+import base64
+import struct
+
+OPENSSL_PATH="/usr/local/ssl/bin/openssl"
+
+LOGS = [
+ { "Name": "Aviator",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1J\n"
+ "YP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q=" },
+
+ { "Name": "Digicert Log",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCF\n"
+ "RkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "VhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0=" },
+
+ { "Name": "Pilot",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHT\n"
+ "DM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA=" },
+
+ { "Name": "Rocketeer",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1\n"
+ "aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=" },
+
+ { "Name": "Izenpe",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJ2Q5DC3cUBj4IQCiDu0s6j51up+T\n"
+ "ZAkAEcQRF6tczw90rLWXkJMAW7jr9yc92bIKgV8vDXU4lDeZHvYHduDuvg==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "dGG0oJz7PUHXUVlXWy52SaRFqNJ3CbDMVkpkgrfrQaM=" },
+
+ { "Name": "Certly",
+ "Key": "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2M\n"
+ "NvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA==\n"
+ "-----END PUBLIC KEY-----",
+ "LogID": "zbUXm3/BwEb+6jETaj+PAC5hgvr4iW/syLL1tatgSQA=" }
+
+ ]
+
+if len( sys.argv ) <= 1:
+ print( "Missing hostname argument." )
+ print( "Usage: ./sct-verify hostname" )
+ print( "" )
+ print( "Example:" )
+ print( " ./sct-verify sni.velox.ch" )
+ print( "" )
+ print( "Known hosts implementing SCT TLS Extensions:" )
+ print( " - sni.velox.ch" )
+ print( " - ritter.vg" )
+ quit()
+
+HostName = sys.argv[1]
+
+Args = [ OPENSSL_PATH ]
+Args.extend( [ "s_client", "-serverinfo", "18", "-connect", "%s:443" % HostName, "-servername", HostName ])
+
+OpenSSL= subprocess.Popen( Args, stdin=open('/dev/null', 'r'), stdout=subprocess.PIPE, stderr=subprocess.PIPE )
+OpenSSL_stdout, OpenSSL_stderr = OpenSSL.communicate()
+OpenSSL_exitcode = OpenSSL.wait()
+
+if OpenSSL_exitcode != 0:
+ print("OpenSSL can't connect to %s" % HostName)
+ print(OpenSSL_stderr)
+ quit()
+
+ServerInfo18 = ""
+ServerInfo18_Add = False
+EECert = ""
+EECert_Add = False
+for L in OpenSSL_stdout.split('\n'):
+ if L == "-----BEGIN SERVERINFO FOR EXTENSION 18-----":
+ ServerInfo18_Add = True
+ elif L == "-----END SERVERINFO FOR EXTENSION 18-----":
+ ServerInfo18_Add = False
+ elif L == "-----BEGIN CERTIFICATE-----":
+ EECert_Add = True
+ elif L == "-----END CERTIFICATE-----":
+ EECert_Add = False
+ elif ServerInfo18_Add:
+ if ServerInfo18:
+ ServerInfo18 = ServerInfo18 + '\n'
+ ServerInfo18 = ServerInfo18 + L
+ elif EECert_Add:
+ if EECert:
+ EECert = EECert + '\n'
+ EECert = EECert + L
+
+EECertDER = base64.b64decode( EECert )
+
+Data = base64.b64decode( ServerInfo18 )
+DataLen = len(Data)
+
+def ToHex( v ):
+ if type(v) is int or type(v) is long:
+ return hex(v)
+ else:
+ return ":".join("{:02x}".format(ord(c)) for c in v)
+
+def Read( buf, offset, format ):
+ Values = struct.unpack_from( format, buf, offset )
+ NewOffset = offset + struct.calcsize( format )
+
+ Ret = ()
+ Ret = Ret + ( NewOffset, )
+ Ret = Ret + Values
+ return Ret
+
+def ReadSCT( SCT ):
+ print("===========================================================")
+ Offset = 0
+
+ Offset, SCTVersion = Read( SCT, Offset, "!B" )
+
+ Offset, SCTLogID = Read( SCT, Offset, "!32s" )
+ Base64LogID = base64.b64encode( SCTLogID )
+
+ Offset, SCTTimestamp = Read( SCT, Offset, "!Q" )
+
+ Offset, SCTExtensionsLen = Read( SCT, Offset, "!H" )
+
+ #FIXME
+ if SCTExtensionsLen > 0:
+ print("Extensions length > 0; not implemented")
+ return
+
+ Offset, SCTSignatureAlgHash = Read( SCT, Offset, "!B" )
+ Offset, SCTSignatureAlgSign = Read( SCT, Offset, "!B" )
+
+ Offset, SCTSignatureLen = Read( SCT, Offset, "!H" )
+ Offset, SCTSignature = Read( SCT, Offset, "!%ss" % SCTSignatureLen )
+
+ # print SCT information
+
+ print( "Version : %s" % ToHex( SCTVersion ) )
+ SCTLogID1, SCTLogID2 = struct.unpack( "!16s16s", SCTLogID )
+ print( "LogID : %s" % ToHex( SCTLogID1 ) )
+ print( " %s" % ToHex( SCTLogID2 ) )
+ print( "LogID b64 : %s" % Base64LogID )
+ print( "Timestamp : %s (%s)" % ( SCTTimestamp, ToHex( SCTTimestamp ) ) )
+ print( "Extensions: %s (%s)" % ( SCTExtensionsLen, ToHex( SCTExtensionsLen )) )
+ print( "Algorithms: %s/%s (hash/sign)" % ( ToHex( SCTSignatureAlgHash ), ToHex ( SCTSignatureAlgSign ) ))
+
+ SigOffset = 0
+ while SigOffset < len( SCTSignature ):
+ if len( SCTSignature ) - SigOffset > 16:
+ SigBytesToRead = 16
+ else:
+ SigBytesToRead = len( SCTSignature ) - SigOffset
+ SigBytes = struct.unpack_from( "!%ss" % SigBytesToRead, SCTSignature, SigOffset )[0]
+
+ if SigOffset == 0:
+ print( "Signature : %s" % ToHex( SigBytes ) )
+ else:
+ print( " %s" % ToHex( SigBytes ) )
+
+ SigOffset = SigOffset + SigBytesToRead
+
+ # look for signing log and its key
+
+ PubKey = None
+ for Log in LOGS:
+ if Log["LogID"] == Base64LogID:
+ print( "Log found : %s" % Log["Name"])
+ PubKey = Log["Key"]
+
+ if not PubKey:
+ print("Log not found")
+ return
+
+ # signed data
+
+ # 1 version
+ # 1 signature_type
+ # 8 timestamp
+ # 2 entry_type
+ # 3 DER lenght
+ # x DER
+ # 2 extensions length
+
+ EECertDERLen = len( EECertDER )
+ _, EECertDERLen1, EECertDERLen2, EECertDERLen3 = struct.unpack( "!4B", struct.pack( "!I", EECertDERLen ) )
+
+ Data = struct.pack("!BBQhBBB%ssh" % len( EECertDER ), SCTVersion, 0, SCTTimestamp, 0, EECertDERLen1, EECertDERLen2, EECertDERLen3, EECertDER, SCTExtensionsLen )
+
+ File = open("tmp-signeddata.bin", "wb")
+ File.write( Data )
+ File.close()
+
+ File = open("tmp-pubkey.pem", "w")
+ File.write( PubKey )
+ File.close()
+
+ File = open("tmp-signature.bin", "wb")
+ File.write( SCTSignature )
+ File.close()
+
+ Args = [ OPENSSL_PATH ]
+ Args.extend( [ "dgst", "-sha256", "-verify", "tmp-pubkey.pem", "-signature", "tmp-signature.bin", "tmp-signeddata.bin" ] )
+
+ OpenSSL= subprocess.Popen( Args, stdin=open('/dev/null', 'r'), stdout=subprocess.PIPE, stderr=subprocess.PIPE )
+ OpenSSL_stdout, OpenSSL_stderr = OpenSSL.communicate()
+ OpenSSL_exitcode = OpenSSL.wait()
+
+ if OpenSSL_exitcode == 0:
+ print( "Result : %s" % OpenSSL_stdout )
+ else:
+ print( "OpenSSL error - Exit code %d" % OpenSSL_exitcode )
+ print( OpenSSL_stderr )
+
+Offset = 0
+Offset, TLS_ExtensionType, TLS_ExtensionLen = Read( Data, Offset, "!HH" )
+Offset, SignedCertificateTimestampListLen = Read( Data, Offset, "!H" )
+
+while Offset < DataLen:
+ Offset, SCTLen = Read( Data, Offset, "!H" )
+ Offset, SCT = Read( Data, Offset, "!%ss" % SCTLen )
+ # print SCT
+ ReadSCT( SCT )
+