Created
March 19, 2018 01:57
-
-
Save divergentdave/7cd98ee16919a1a159ded8bc3160c8da to your computer and use it in GitHub Desktop.
Verify a self signed certificate's signature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
This script verifies whether a given X.509 certificate is self-signed, while | |
ignorng the subject and issuer distinguished names. Python 3 is required, | |
along with recent versions of pyasn1 and pyasn1-modules. To check a | |
certificate, run this script with the file name of that certificate as a | |
command line argument. Certificates can be in PEM or DER format. Only RSA | |
signatures are supported. | |
To verify whether a certificate is self-signed, this script parses the | |
certificate, calculates the appropriate hash over the to-be-signed portion, | |
parses the public modulus, public exponent, and signature into native Python | |
integers. The RSA signature verification is performed using the built-in pow() | |
function. Finally, the contents of the verified signature are parsed and | |
compared to the previously computed hash. "True" is printed for valid | |
self-signed certificates, and "False" is printed for all other certificates. | |
""" | |
import argparse | |
import hashlib | |
import pyasn1.codec.der.decoder | |
import pyasn1.codec.der.encoder | |
import pyasn1.type.univ | |
import pyasn1_modules.pem | |
import pyasn1_modules.rfc2315 | |
import pyasn1_modules.rfc2437 | |
import pyasn1_modules.rfc2459 | |
import pyasn1_modules.rfc5280 | |
HASH_OID_LOOKUP = { | |
pyasn1_modules.rfc2437.sha1WithRSAEncryption: | |
pyasn1_modules.rfc2437.id_sha1, | |
pyasn1.type.univ.ObjectIdentifier("1.2.840.113549.1.1.11"): | |
pyasn1.type.univ.ObjectIdentifier("2.16.840.1.101.3.4.2.1") | |
} | |
HASH_CONSTRUCTOR_LOOKUP = { | |
pyasn1_modules.rfc2437.sha1WithRSAEncryption: | |
hashlib.sha1, | |
pyasn1.type.univ.ObjectIdentifier("1.2.840.113549.1.1.11"): | |
hashlib.sha256, | |
} | |
def integer_to_bytes(n, k): | |
assert n >> (8 * k) == 0 | |
return bytes((n >> (8 * i)) & 0xff | |
for i in range(k - 1, -1, -1)) | |
def load_cert(path): | |
with open(path, "rb") as f: | |
data = f.read() | |
if data.startswith(b"-----BEGIN"): | |
# decode PEM | |
with open(path, "r") as f: | |
return pyasn1_modules.pem.readPemFromFile(f) | |
else: | |
# assume it's binary DER-encoded data | |
return data | |
def public_key_from_certificate(cert_data): | |
cert, _ = pyasn1.codec.der.decoder.decode( | |
cert_data, | |
pyasn1_modules.rfc5280.Certificate()) | |
tbs = cert["tbsCertificate"] | |
spki = tbs["subjectPublicKeyInfo"] | |
assert (spki["algorithm"]["algorithm"] == | |
pyasn1_modules.rfc2437.rsaEncryption) | |
pyasn1.codec.der.decoder.decode( | |
spki["algorithm"]["parameters"], | |
pyasn1.type.univ.Null()) | |
rsa_public_key, _ = pyasn1.codec.der.decoder.decode( | |
spki["subjectPublicKey"].asOctets(), | |
pyasn1_modules.rfc2437.RSAPublicKey()) | |
return (int(rsa_public_key["modulus"]), | |
int(rsa_public_key["publicExponent"])) | |
def parse_certificate(cert_data): | |
cert, _ = pyasn1.codec.der.decoder.decode( | |
cert_data, | |
pyasn1_modules.rfc5280.Certificate()) | |
algorithmIdentifier = cert["signatureAlgorithm"] | |
signature_algorithm = algorithmIdentifier["algorithm"] | |
pyasn1.codec.der.decoder.decode( | |
algorithmIdentifier["parameters"], | |
pyasn1.type.univ.Null()) | |
tbs_der = pyasn1.codec.der.encoder.encode(cert["tbsCertificate"]) | |
return (tbs_der, | |
int(cert["signature"]), | |
len(cert["signature"]), | |
signature_algorithm) | |
def pkcs1_15_unpad(padded): | |
assert padded.startswith(b"\x00\x01") | |
temp = padded[2:].lstrip(b"\xff") | |
assert temp.startswith(b"\x00") | |
return temp[1:] | |
def hash_tbs(tbs, signature_algorithm): | |
digest = HASH_CONSTRUCTOR_LOOKUP[signature_algorithm]() | |
digest.update(tbs) | |
return digest.digest() | |
def unwrap_hash(signature_contents, signature_algorithm): | |
digestInfo, _ = pyasn1.codec.der.decoder.decode( | |
signature_contents, | |
pyasn1_modules.rfc2315.DigestInfo()) | |
algorithmIdentifier = digestInfo["digestAlgorithm"] | |
assert (algorithmIdentifier["algorithm"] == | |
HASH_OID_LOOKUP[signature_algorithm]) | |
pyasn1.codec.der.decoder.decode( | |
algorithmIdentifier["parameters"], | |
pyasn1.type.univ.Null()) | |
return digestInfo["digest"] | |
def verify(path): | |
cert = load_cert(path) | |
( | |
tbscert, | |
signature, | |
bit_length, | |
signature_algorithm | |
) = parse_certificate(cert) | |
tbs_hash = hash_tbs(tbscert, signature_algorithm) | |
n, e = public_key_from_certificate(cert) | |
signed_int = pow(signature, e, n) | |
signed_bytes_padded = integer_to_bytes(signed_int, bit_length // 8) | |
signed_data = pkcs1_15_unpad(signed_bytes_padded) | |
hash_bytes = unwrap_hash(signed_data, signature_algorithm) | |
return tbs_hash == hash_bytes | |
def main(): | |
parser = argparse.ArgumentParser( | |
description="Self-signed certificate signature verifier") | |
parser.add_argument( | |
"certs", | |
nargs="+", | |
metavar="path", | |
help="Certificate file") | |
args = parser.parse_args() | |
for cert in args.certs: | |
print("{}: {}".format(cert, verify(cert))) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment