mirror of
https://github.com/moepman/acertmgr.git
synced 2025-01-01 05:31:51 +01:00
Switch from pyopenssl to cryptography
The cryptography module is already a dependency of pyopenssl. This patch thus just drops the dependency on pyopenssl and somewhat simplifies the code.
This commit is contained in:
parent
017f55f57c
commit
4766102874
@ -5,15 +5,15 @@
|
|||||||
# Copyright (c) Markus Hauschild & David Klaftenegger, 2016.
|
# Copyright (c) Markus Hauschild & David Klaftenegger, 2016.
|
||||||
# available under the ISC license, see LICENSE
|
# available under the ISC license, see LICENSE
|
||||||
|
|
||||||
from OpenSSL import crypto
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import subprocess
|
|
||||||
import textwrap
|
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -28,56 +28,56 @@ except ImportError:
|
|||||||
def cert_valid_times(cert_file):
|
def cert_valid_times(cert_file):
|
||||||
with open(cert_file, 'r') as f:
|
with open(cert_file, 'r') as f:
|
||||||
cert_data = f.read()
|
cert_data = f.read()
|
||||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data)
|
cert = x509.load_pem_x509_certificate(cert_data, default_backend())
|
||||||
asn1time = str('%Y%m%d%H%M%SZ'.encode('utf8'))
|
return (cert.not_valid_before, cert.not_valid_after)
|
||||||
not_before = datetime.datetime.strptime(str(cert.get_notBefore()), asn1time)
|
|
||||||
not_after = datetime.datetime.strptime(str(cert.get_notAfter()), asn1time)
|
|
||||||
return (not_before, not_after)
|
|
||||||
|
|
||||||
# @brief create a certificate signing request
|
# @brief create a certificate signing request
|
||||||
# @param names list of domain names the certificate should be valid for
|
# @param names list of domain names the certificate should be valid for
|
||||||
# @param key the key to use with the certificate in pyopenssl format
|
# @param key the key to use with the certificate in cryptography format
|
||||||
# @return the CSR in pyopenssl format
|
# @return the CSR in cryptography format
|
||||||
def cert_request(names, key):
|
def cert_request(names, key):
|
||||||
req = crypto.X509Req()
|
primary_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, names[0].decode('utf8'))])
|
||||||
req.get_subject().commonName = names[0]
|
all_names = x509.SubjectAlternativeName([x509.DNSName(name.decode('utf8')) for name in names])
|
||||||
entries = ['DNS:'+name for name in names]
|
req = x509.CertificateSigningRequestBuilder()
|
||||||
extensions = [crypto.X509Extension('subjectAltName'.encode('utf8'), False, ', '.join(entries).encode('utf8'))]
|
req = req.subject_name(primary_name)
|
||||||
req.add_extensions(extensions)
|
req = req.add_extension(all_names, critical=False)
|
||||||
req.set_pubkey(key)
|
req = req.sign(key, hashes.SHA256(), default_backend())
|
||||||
req.sign(key, 'sha256')
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
# @brief convert certificate to PEM format
|
# @brief convert certificate to PEM format
|
||||||
# @param cert certificate object in pyopenssl format
|
# @param cert certificate object in cryptography format
|
||||||
# @return the certificate in PEM format
|
# @return the certificate in PEM format
|
||||||
def cert_to_pem(cert):
|
def cert_to_pem(cert):
|
||||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf8')
|
return cert.public_bytes(serialization.Encoding.PEM).decode('utf8')
|
||||||
|
|
||||||
# @brief read a key from file
|
# @brief read a key from file
|
||||||
# @param path path to key file
|
# @param path path to key file
|
||||||
# @return the key in pyopenssl format
|
# @return the key in cryptography format
|
||||||
def read_key(path):
|
def read_key(path):
|
||||||
with open(path, 'r') as f:
|
with open(path, 'r') as f:
|
||||||
key_data = f.read()
|
key_data = f.read()
|
||||||
return crypto.load_privatekey(crypto.FILETYPE_PEM, key_data)
|
return serialization.load_pem_private_key(key_data, None, default_backend())
|
||||||
|
|
||||||
|
# @brief convert numbers to byte-string
|
||||||
|
# @param num number to convert
|
||||||
|
# @return byte-string containing the number
|
||||||
|
# @todo better code welcome
|
||||||
|
def byte_string_format(num):
|
||||||
|
n = format(num, 'x')
|
||||||
|
n = "0{0}".format(n) if len(n) % 2 else n
|
||||||
|
return binascii.unhexlify(n)
|
||||||
|
|
||||||
# @brief create the header information for ACME communication
|
# @brief create the header information for ACME communication
|
||||||
# @param key the account key
|
# @param key the account key
|
||||||
# @return the header for ACME
|
# @return the header for ACME
|
||||||
def acme_header(key):
|
def acme_header(key):
|
||||||
txt = crypto.dump_privatekey(crypto.FILETYPE_TEXT, key)
|
numbers = key.public_key().public_numbers()
|
||||||
pub_mod, pub_exp = re.search(
|
|
||||||
r"modulus:\n\s+00:([0-9a-f:\s]+)\npublicExponent: [0-9]+ \(0x([0-9A-F]+)\)",
|
|
||||||
txt.decode('utf8'), re.DOTALL).groups()
|
|
||||||
pub_mod = re.sub('[:\s]', '', pub_mod)
|
|
||||||
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
|
|
||||||
header = {
|
header = {
|
||||||
"alg": "RS256",
|
"alg": "RS256",
|
||||||
"jwk": {
|
"jwk": {
|
||||||
"e": base64_enc(binascii.unhexlify(pub_exp.encode("utf-8"))),
|
"e": base64_enc(byte_string_format(numbers.e)),
|
||||||
"kty": "RSA",
|
"kty": "RSA",
|
||||||
"n": base64_enc(binascii.unhexlify(pub_mod.encode("utf-8"))),
|
"n": base64_enc(byte_string_format(numbers.n)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return header
|
return header
|
||||||
@ -119,7 +119,10 @@ def send_signed(account_key, CA, url, header, payload):
|
|||||||
protected = copy.deepcopy(header)
|
protected = copy.deepcopy(header)
|
||||||
protected["nonce"] = urlopen(CA + "/directory").headers['Replay-Nonce']
|
protected["nonce"] = urlopen(CA + "/directory").headers['Replay-Nonce']
|
||||||
protected64 = base64_enc(json.dumps(protected).encode('utf8'))
|
protected64 = base64_enc(json.dumps(protected).encode('utf8'))
|
||||||
out = crypto.sign(account_key, '.'.join([protected64, payload64]), 'sha256')
|
# @todo check why this padding is not working
|
||||||
|
#pad = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH)
|
||||||
|
pad = padding.PKCS1v15()
|
||||||
|
out = account_key.sign('.'.join([protected64, payload64]).encode('utf8'), pad, hashes.SHA256())
|
||||||
data = json.dumps({
|
data = json.dumps({
|
||||||
"header": header, "protected": protected64,
|
"header": header, "protected": protected64,
|
||||||
"payload": payload64, "signature": base64_enc(out),
|
"payload": payload64, "signature": base64_enc(out),
|
||||||
@ -141,7 +144,9 @@ def send_signed(account_key, CA, url, header, payload):
|
|||||||
def get_crt_from_csr(account_key, csr, domains, acme_dir, CA):
|
def get_crt_from_csr(account_key, csr, domains, acme_dir, CA):
|
||||||
header = acme_header(account_key)
|
header = acme_header(account_key)
|
||||||
accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':'))
|
accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':'))
|
||||||
account_thumbprint = base64_enc(hashlib.sha256(accountkey_json.encode('utf8')).digest())
|
account_hash = hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
|
account_hash.update(accountkey_json.encode('utf8'))
|
||||||
|
account_thumbprint = base64_enc(account_hash.finalize())
|
||||||
|
|
||||||
# verify each domain
|
# verify each domain
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@ -202,7 +207,7 @@ def get_crt_from_csr(account_key, csr, domains, acme_dir, CA):
|
|||||||
|
|
||||||
# get the new certificate
|
# get the new certificate
|
||||||
print("Signing certificate...")
|
print("Signing certificate...")
|
||||||
csr_der = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, csr)
|
csr_der = csr.public_bytes(serialization.Encoding.DER)
|
||||||
code, result = send_signed(account_key, CA, CA + "/acme/new-cert", header, {
|
code, result = send_signed(account_key, CA, CA + "/acme/new-cert", header, {
|
||||||
"resource": "new-cert",
|
"resource": "new-cert",
|
||||||
"csr": base64_enc(csr_der),
|
"csr": base64_enc(csr_der),
|
||||||
@ -212,6 +217,6 @@ def get_crt_from_csr(account_key, csr, domains, acme_dir, CA):
|
|||||||
|
|
||||||
# return signed certificate!
|
# return signed certificate!
|
||||||
print("Certificate signed!")
|
print("Certificate signed!")
|
||||||
cert = crypto.load_certificate(crypto.FILETYPE_ASN1, result)
|
cert = x509.load_der_x509_certificate(result, default_backend())
|
||||||
return cert
|
return cert
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user