Skip to content

Instantly share code, notes, and snippets.

@felixfontein
Last active July 1, 2018 14:16
Show Gist options
  • Save felixfontein/1eb6356819c156ea23e86acfe9911b72 to your computer and use it in GitHub Desktop.
Save felixfontein/1eb6356819c156ea23e86acfe9911b72 to your computer and use it in GitHub Desktop.
Playing around with [cryptography](https://cryptography.io/) in preparation of using it to create an ACME client
#!/usr/bin/python
import binascii
import cryptography
import datetime
import math
from cryptography.hazmat.backends import default_backend
def count_bits(n):
return math.ceil(math.log2(n)) if n > 0 else 0
def pad_hex(n, digits):
res = hex(n)[2:]
if len(res) < digits:
res = '0' * (digits - len(res)) + res
return res
def read_file(fn, mode='b'):
with open(fn, 'r' + mode) as f:
return f.read()
def validate_cert_single_step(child, parent):
# WARNING: This is wholly incomplete! We only verify the signature, but NOTHING ELSE!
public_key = parent.public_key()
try:
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
public_key.verify(
child.signature,
child.tbs_certificate_bytes,
cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15(),
child.signature_hash_algorithm
)
elif isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
public_key.verify(
child.signature,
child.tbs_certificate_bytes,
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(child.signature_hash_algorithm),
)
else:
# Unknown cert type
return False
return True
except cryptography.exceptions.InvalidSignature as e:
raise
return False
except:
raise
return False
def validate_cert(cert, chain, root):
# WARNING: The checks are incomplete, we only verify signatures, but *nothing else*!
child = cert
for i, parent in enumerate(chain):
if not validate_cert_single_step(child, parent):
return False
child = parent
if not validate_cert_single_step(child, root):
print('Failure while validating root!')
return False
return True
backend = default_backend()
cert = cryptography.x509.load_pem_x509_certificate(read_file('example.com-fullchain.pem'), backend)
now = datetime.datetime.now()
days_valid = (cert.not_valid_after - now).days
print('Current certificate: {1} days valid, until {0}'.format(cert.not_valid_after, days_valid))
chain = [cryptography.x509.load_pem_x509_certificate(read_file('example.com-chain.pem'), backend)]
root = cryptography.x509.load_pem_x509_certificate(read_file('root.pem'), backend)
# root = cryptography.x509.load_pem_x509_certificate(read_file('example.com-root.pem'), backend)
print('Cert: {0}'.format(cert.subject))
for c in chain:
print('Chain: {0}'.format(c.subject))
print('Root: {0}'.format(root.subject))
print('Validates: {0}'.format(validate_cert(cert, chain, root)))
# openssl verify -CAfile example.com-root.pem -untrusted example.com-chain.pem example.com.pem
csr = cryptography.x509.load_pem_x509_csr(read_file('example.com.csr'), backend)
for sub in csr.subject:
if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME:
print('CSR DNS name: {0} (subject)'.format(sub.value))
for extension in csr.extensions:
if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
for name in extension.value:
if isinstance(name, cryptography.x509.DNSName):
print('CSR DNS name: {0}'.format(name.value))
message = b'test message!'
for key_name in ('letsencrypt-account-p256.key', 'letsencrypt-account-p384.key', 'letsencrypt-account-p512.key', 'letsencrypt-account-rsa.key'):
key = cryptography.hazmat.primitives.serialization.load_pem_private_key(read_file(key_name), password=None, backend=backend)
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
pk = key.public_key().public_numbers()
print('{0}: RSA key, {1} bits, n={2}, e={3}'.format(key_name, key.key_size, pk.n, pk.e))
padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
hash = cryptography.hazmat.primitives.hashes.SHA256()
signature = key.sign(message, padding, hash)
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
pk = key.public_key().public_numbers()
print('{0}: EC key, {1} bits, curve={2}, x={3}, y={4}'.format(key_name, key.key_size, pk.curve.name, pk.x, pk.y))
if pk.curve.name == 'secp256r1':
hash = cryptography.hazmat.primitives.hashes.SHA256
elif pk.curve.name == 'secp384r1':
hash = cryptography.hazmat.primitives.hashes.SHA384
elif pk.curve.name == 'secp521r1':
hash = cryptography.hazmat.primitives.hashes.SHA512
else:
print('{0}: Unknown elliptic curve!'.format(key_name))
continue
ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hash())
r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key.sign(message, ecdsa))
rr = pad_hex(r, 2 * ((key.key_size + 7) // 8))
ss = pad_hex(s, 2 * ((key.key_size + 7) // 8))
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
else:
print('{0}: Unknown key!'.format(key_name))
continue
print('{0}: Signature is {1} ({2} bits)'.format(key_name, binascii.hexlify(signature).decode('utf-8'), len(signature) * 8))
@felixfontein
Copy link
Author

The new revision updates changes I had to make to make this actually work; see ansible/ansible#42170 for the result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment