Skip to content

Instantly share code, notes, and snippets.

@divergentdave
Created March 19, 2018 01:57
Show Gist options
  • Save divergentdave/7cd98ee16919a1a159ded8bc3160c8da to your computer and use it in GitHub Desktop.
Save divergentdave/7cd98ee16919a1a159ded8bc3160c8da to your computer and use it in GitHub Desktop.
Verify a self signed certificate's signature
#!/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