mirror of
https://github.com/moepman/acertmgr.git
synced 2024-12-29 10:31:49 +01:00
tools: add log function, update log messages mentioning certificates
This simple implementation writes log messages to stdout/err and flushes the buffers immediately after the message has been written. Also update log messages with the certificate CN to a better readable format Introduce functions for get_cert_cn and get_cert_valid_until to encapsulate all cryptographic functions consistently in tools.
This commit is contained in:
parent
2046215e37
commit
b63a0bc424
@ -16,12 +16,13 @@ import subprocess
|
|||||||
from acertmgr import configuration, tools
|
from acertmgr import configuration, tools
|
||||||
from acertmgr.authority import authority
|
from acertmgr.authority import authority
|
||||||
from acertmgr.modes import challenge_handler
|
from acertmgr.modes import challenge_handler
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
|
|
||||||
# @brief fetch new certificate from letsencrypt
|
# @brief fetch new certificate from letsencrypt
|
||||||
# @param settings the domain's configuration options
|
# @param settings the domain's configuration options
|
||||||
def cert_get(settings):
|
def cert_get(settings):
|
||||||
print("Getting certificate for %s" % settings['domainlist'])
|
log("Getting certificate for %s" % settings['domainlist'])
|
||||||
|
|
||||||
acme = authority(settings['authority'])
|
acme = authority(settings['authority'])
|
||||||
acme.register_account()
|
acme.register_account()
|
||||||
@ -38,16 +39,16 @@ def cert_get(settings):
|
|||||||
if os.path.isfile(key_file):
|
if os.path.isfile(key_file):
|
||||||
key = tools.read_pem_file(key_file, key=True)
|
key = tools.read_pem_file(key_file, key=True)
|
||||||
else:
|
else:
|
||||||
print("SSL key not found at '{0}'. Creating {1} bit key.".format(key_file, key_length))
|
log("SSL key not found at '{0}'. Creating {1} bit key.".format(key_file, key_length))
|
||||||
key = tools.new_ssl_key(key_file, key_length)
|
key = tools.new_ssl_key(key_file, key_length)
|
||||||
|
|
||||||
# create ssl csr
|
# create ssl csr
|
||||||
csr_file = settings['csr_file']
|
csr_file = settings['csr_file']
|
||||||
if os.path.isfile(csr_file) and str(settings['csr_static']).lower() == 'true':
|
if os.path.isfile(csr_file) and str(settings['csr_static']).lower() == 'true':
|
||||||
print('Loading CSR from {}'.format(csr_file))
|
log('Loading CSR from {}'.format(csr_file))
|
||||||
cr = tools.read_pem_file(csr_file, csr=True)
|
cr = tools.read_pem_file(csr_file, csr=True)
|
||||||
else:
|
else:
|
||||||
print('Generating CSR for {}'.format(settings['domainlist']))
|
log('Generating CSR for {}'.format(settings['domainlist']))
|
||||||
must_staple = str(settings.get('cert_must_staple')).lower() == "true"
|
must_staple = str(settings.get('cert_must_staple')).lower() == "true"
|
||||||
cr = tools.new_cert_request(settings['domainlist'], key, must_staple)
|
cr = tools.new_cert_request(settings['domainlist'], key, must_staple)
|
||||||
tools.write_pem_file(cr, csr_file)
|
tools.write_pem_file(cr, csr_file)
|
||||||
@ -57,7 +58,8 @@ def cert_get(settings):
|
|||||||
|
|
||||||
# if resulting certificate is valid: store in final location
|
# if resulting certificate is valid: store in final location
|
||||||
if tools.is_cert_valid(crt, settings['ttl_days']):
|
if tools.is_cert_valid(crt, settings['ttl_days']):
|
||||||
print("Certificate '{}' renewed and valid until {}".format(crt, crt.not_valid_after))
|
log("Certificate '{}' renewed and valid until {}".format(tools.get_cert_cn(crt),
|
||||||
|
tools.get_cert_valid_until(crt)))
|
||||||
tools.write_pem_file(crt, settings['cert_file'], stat.S_IREAD)
|
tools.write_pem_file(crt, settings['cert_file'], stat.S_IREAD)
|
||||||
if (not str(settings.get('ca_static')).lower() == 'true' or not os.path.exists(settings['ca_file'])) \
|
if (not str(settings.get('ca_static')).lower() == 'true' or not os.path.exists(settings['ca_file'])) \
|
||||||
and ca is not None:
|
and ca is not None:
|
||||||
@ -107,11 +109,11 @@ def cert_put(settings):
|
|||||||
try:
|
try:
|
||||||
os.chown(crt_path, uid, gid)
|
os.chown(crt_path, uid, gid)
|
||||||
except OSError:
|
except OSError:
|
||||||
print('Warning: Could not set certificate file ownership!')
|
log('Could not set certificate file ownership!', warning=True)
|
||||||
try:
|
try:
|
||||||
os.chmod(crt_path, int(crt_perm, 8))
|
os.chmod(crt_path, int(crt_perm, 8))
|
||||||
except OSError:
|
except OSError:
|
||||||
print('Warning: Could not set certificate file permissions!')
|
log('Could not set certificate file permissions!', warning=True)
|
||||||
|
|
||||||
return crt_action
|
return crt_action
|
||||||
|
|
||||||
@ -131,7 +133,7 @@ def main():
|
|||||||
runtimeconfig, domainconfigs = configuration.load()
|
runtimeconfig, domainconfigs = configuration.load()
|
||||||
if runtimeconfig.get('mode') == 'revoke':
|
if runtimeconfig.get('mode') == 'revoke':
|
||||||
# Mode: revoke certificate
|
# Mode: revoke certificate
|
||||||
print("Revoking {}".format(runtimeconfig['revoke']))
|
log("Revoking {}".format(runtimeconfig['revoke']))
|
||||||
cert_revoke(tools.read_pem_file(runtimeconfig['revoke']), domainconfigs, runtimeconfig['revoke_reason'])
|
cert_revoke(tools.read_pem_file(runtimeconfig['revoke']), domainconfigs, runtimeconfig['revoke_reason'])
|
||||||
else:
|
else:
|
||||||
# Mode: issue certificates (implicit)
|
# Mode: issue certificates (implicit)
|
||||||
@ -152,7 +154,7 @@ def main():
|
|||||||
if str(config.get('cert_revoke_superseded')).lower() == 'true' and cert:
|
if str(config.get('cert_revoke_superseded')).lower() == 'true' and cert:
|
||||||
superseded.add(cert)
|
superseded.add(cert)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Certificate issue/renew failed: {}".format(e))
|
log("Certificate issue/renew failed", e, error=True)
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
|
|
||||||
# deploy new certificates after all are renewed
|
# deploy new certificates after all are renewed
|
||||||
@ -161,10 +163,10 @@ def main():
|
|||||||
try:
|
try:
|
||||||
for cfg in config['actions']:
|
for cfg in config['actions']:
|
||||||
if not tools.target_is_current(cfg['path'], config['cert_file']):
|
if not tools.target_is_current(cfg['path'], config['cert_file']):
|
||||||
print("Updating '{}' due to newer version".format(cfg['path']))
|
log("Updating '{}' due to newer version".format(cfg['path']))
|
||||||
actions.add(cert_put(cfg))
|
actions.add(cert_put(cfg))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Certificate deployment failed: {}".format(e))
|
log("Certificate deployment failed", e, error=True)
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
deployment_success = False
|
deployment_success = False
|
||||||
|
|
||||||
@ -174,9 +176,10 @@ def main():
|
|||||||
try:
|
try:
|
||||||
# Run actions in a shell environment (to allow shell syntax) as stated in the configuration
|
# Run actions in a shell environment (to allow shell syntax) as stated in the configuration
|
||||||
output = subprocess.check_output(action, shell=True, stderr=subprocess.STDOUT)
|
output = subprocess.check_output(action, shell=True, stderr=subprocess.STDOUT)
|
||||||
print("Executed '{}' successfully: {}".format(action, output))
|
log("Executed '{}' successfully: {}".format(action, output))
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print("Execution of '{}' failed with error '{}': {}".format(e.cmd, e.returncode, e.output))
|
log("Execution of '{}' failed with error '{}': {}".format(e.cmd, e.returncode, e.output), e,
|
||||||
|
error=True)
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
deployment_success = False
|
deployment_success = False
|
||||||
|
|
||||||
@ -184,14 +187,14 @@ def main():
|
|||||||
if deployment_success:
|
if deployment_success:
|
||||||
for superseded_cert in superseded:
|
for superseded_cert in superseded:
|
||||||
try:
|
try:
|
||||||
print("Revoking previous certificate '{}' valid until {} as superseded".format(
|
log("Revoking '{}' valid until {} as superseded".format(
|
||||||
superseded_cert,
|
tools.get_cert_cn(superseded_cert),
|
||||||
superseded_cert.not_valid_after))
|
tools.get_cert_valid_until(superseded_cert)))
|
||||||
cert_revoke(superseded_cert, domainconfigs, reason=4) # reason=4 is superseded
|
cert_revoke(superseded_cert, domainconfigs, reason=4) # reason=4 is superseded
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Certificate supersede revoke failed: {}".format(e))
|
log("Certificate supersede revoke failed", e, error=True)
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
|
|
||||||
# throw a RuntimeError with all exceptions caught while working if there were any
|
# throw a RuntimeError with all exceptions caught while working if there were any
|
||||||
if len(exceptions) > 0:
|
if len(exceptions) > 0:
|
||||||
raise RuntimeError("{} exception(s) occurred during runtime: {}".format(len(exceptions), exceptions))
|
raise RuntimeError("{} exception(s) occurred during processing".format(len(exceptions)))
|
||||||
|
@ -10,6 +10,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from acertmgr import tools
|
from acertmgr import tools
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
authorities = dict()
|
authorities = dict()
|
||||||
|
|
||||||
@ -23,10 +24,10 @@ def authority(settings):
|
|||||||
else:
|
else:
|
||||||
acc_file = settings['account_key']
|
acc_file = settings['account_key']
|
||||||
if os.path.isfile(acc_file):
|
if os.path.isfile(acc_file):
|
||||||
print("Reading account key from {}".format(acc_file))
|
log("Reading account key from {}".format(acc_file))
|
||||||
acc_key = tools.read_pem_file(acc_file, key=True)
|
acc_key = tools.read_pem_file(acc_file, key=True)
|
||||||
else:
|
else:
|
||||||
print("Account key not found at '{0}'. Creating key.".format(acc_file))
|
log("Account key not found at '{0}'. Creating key.".format(acc_file))
|
||||||
acc_key = tools.new_account_key(acc_file)
|
acc_key = tools.new_account_key(acc_file)
|
||||||
|
|
||||||
authority_module = importlib.import_module("acertmgr.authority.{0}".format(settings["api"]))
|
authority_module = importlib.import_module("acertmgr.authority.{0}".format(settings["api"]))
|
||||||
|
@ -13,6 +13,7 @@ import time
|
|||||||
|
|
||||||
from acertmgr import tools
|
from acertmgr import tools
|
||||||
from acertmgr.authority.acme import ACMEAuthority as AbstractACMEAuthority
|
from acertmgr.authority.acme import ACMEAuthority as AbstractACMEAuthority
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
|
|
||||||
class ACMEAuthority(AbstractACMEAuthority):
|
class ACMEAuthority(AbstractACMEAuthority):
|
||||||
@ -70,11 +71,11 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
"agreement": self.agreement,
|
"agreement": self.agreement,
|
||||||
})
|
})
|
||||||
if code == 201:
|
if code == 201:
|
||||||
print("Registered!")
|
log("Registered!")
|
||||||
self.registered_account = True
|
self.registered_account = True
|
||||||
return True
|
return True
|
||||||
elif code == 409:
|
elif code == 409:
|
||||||
print("Already registered!")
|
log("Already registered!")
|
||||||
self.registered_account = True
|
self.registered_account = True
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -96,7 +97,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
# verify each domain
|
# verify each domain
|
||||||
try:
|
try:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
print("Verifying {0}...".format(domain))
|
log("Verifying {0}...".format(domain))
|
||||||
|
|
||||||
# get new challenge
|
# get new challenge
|
||||||
code, result = self._send_signed(self.ca + "/acme/new-authz", header, {
|
code, result = self._send_signed(self.ca + "/acme/new-authz", header, {
|
||||||
@ -120,7 +121,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
for domain in domains:
|
for domain in domains:
|
||||||
challenge_handlers[domain].start_challenge(domain, account_thumbprint, tokens[domain])
|
challenge_handlers[domain].start_challenge(domain, account_thumbprint, tokens[domain])
|
||||||
try:
|
try:
|
||||||
print("Starting key authorization")
|
log("Starting key authorization")
|
||||||
# notify challenge are met
|
# notify challenge are met
|
||||||
keyauthorization = "{0}.{1}".format(tokens[domain], account_thumbprint)
|
keyauthorization = "{0}.{1}".format(tokens[domain], account_thumbprint)
|
||||||
code, result = self._send_signed(challenges[domain]['uri'], header, {
|
code, result = self._send_signed(challenges[domain]['uri'], header, {
|
||||||
@ -141,7 +142,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
if challenge_status['status'] == "pending":
|
if challenge_status['status'] == "pending":
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
elif challenge_status['status'] == "valid":
|
elif challenge_status['status'] == "valid":
|
||||||
print("{0} verified!".format(domain))
|
log("{0} verified!".format(domain))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError("{0} challenge did not pass: {1}".format(
|
raise ValueError("{0} challenge did not pass: {1}".format(
|
||||||
@ -155,10 +156,10 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
try:
|
try:
|
||||||
challenge_handlers[domain].destroy_challenge(domain, account_thumbprint, tokens[domain])
|
challenge_handlers[domain].destroy_challenge(domain, account_thumbprint, tokens[domain])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Challenge destruction failed: {}'.format(e))
|
log('Challenge destruction failed: {}'.format(e), error=True)
|
||||||
|
|
||||||
# get the new certificate
|
# get the new certificate
|
||||||
print("Signing certificate...")
|
log("Signing certificate...")
|
||||||
code, result = self._send_signed(self.ca + "/acme/new-cert", header, {
|
code, result = self._send_signed(self.ca + "/acme/new-cert", header, {
|
||||||
"resource": "new-cert",
|
"resource": "new-cert",
|
||||||
"csr": tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
|
"csr": tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
|
||||||
@ -167,7 +168,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
raise ValueError("Error signing certificate: {0} {1}".format(code, result))
|
raise ValueError("Error signing certificate: {0} {1}".format(code, result))
|
||||||
|
|
||||||
# return signed certificate!
|
# return signed certificate!
|
||||||
print("Certificate signed!")
|
log("Certificate signed!")
|
||||||
cert = tools.convert_der_bytes_to_cert(result)
|
cert = tools.convert_der_bytes_to_cert(result)
|
||||||
return cert, tools.download_issuer_ca(cert)
|
return cert, tools.download_issuer_ca(cert)
|
||||||
|
|
||||||
@ -181,6 +182,6 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
payload['reason'] = int(reason)
|
payload['reason'] = int(reason)
|
||||||
code, result = self._send_signed(self.ca + "/acme/revoke-cert", header, payload)
|
code, result = self._send_signed(self.ca + "/acme/revoke-cert", header, payload)
|
||||||
if code < 400:
|
if code < 400:
|
||||||
print("Revocation successful")
|
log("Revocation successful")
|
||||||
else:
|
else:
|
||||||
raise ValueError("Revocation failed: {}".format(result))
|
raise ValueError("Revocation failed: {}".format(result))
|
||||||
|
@ -12,6 +12,7 @@ import time
|
|||||||
|
|
||||||
from acertmgr import tools
|
from acertmgr import tools
|
||||||
from acertmgr.authority.acme import ACMEAuthority as AbstractACMEAuthority
|
from acertmgr.authority.acme import ACMEAuthority as AbstractACMEAuthority
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
|
|
||||||
class ACMEAuthority(AbstractACMEAuthority):
|
class ACMEAuthority(AbstractACMEAuthority):
|
||||||
@ -41,7 +42,8 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
"newOrder": "{}/acme/new-order".format(self.ca),
|
"newOrder": "{}/acme/new-order".format(self.ca),
|
||||||
"revokeCert": "{}/acme/revoke-cert".format(self.ca),
|
"revokeCert": "{}/acme/revoke-cert".format(self.ca),
|
||||||
}
|
}
|
||||||
print("API directory retrieval failed ({}). Guessed necessary values: {}".format(code, self.directory))
|
log("API directory retrieval failed ({}). Guessed necessary values: {}".format(code, self.directory),
|
||||||
|
warning=True)
|
||||||
self.nonce = None
|
self.nonce = None
|
||||||
|
|
||||||
self.algorithm, jwk = tools.get_key_alg_and_jwk(key)
|
self.algorithm, jwk = tools.get_key_alg_and_jwk(key)
|
||||||
@ -127,8 +129,8 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
if code < 400 and result['status'] == 'valid':
|
if code < 400 and result['status'] == 'valid':
|
||||||
self.account_id = headers['Location']
|
self.account_id = headers['Location']
|
||||||
if 'meta' in self.directory and 'termsOfService' in self.directory['meta']:
|
if 'meta' in self.directory and 'termsOfService' in self.directory['meta']:
|
||||||
print("ToS at {} have been accepted.".format(self.directory['meta']['termsOfService']))
|
log("ToS at {} have been accepted.".format(self.directory['meta']['termsOfService']))
|
||||||
print("Account registered and valid.".format())
|
log("Account registered and valid on {}.".format(self.ca))
|
||||||
else:
|
else:
|
||||||
raise ValueError("Error registering account: {0} {1}".format(code, result))
|
raise ValueError("Error registering account: {0} {1}".format(code, result))
|
||||||
|
|
||||||
@ -142,7 +144,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
account_thumbprint = tools.bytes_to_base64url(
|
account_thumbprint = tools.bytes_to_base64url(
|
||||||
tools.hash_of_str(json.dumps(self.account_protected['jwk'], sort_keys=True, separators=(',', ':'))))
|
tools.hash_of_str(json.dumps(self.account_protected['jwk'], sort_keys=True, separators=(',', ':'))))
|
||||||
|
|
||||||
print("Ordering certificate for {}".format(domains))
|
log("Ordering certificate for {}".format(domains))
|
||||||
identifiers = [{'type': 'dns', 'value': domain} for domain in domains]
|
identifiers = [{'type': 'dns', 'value': domain} for domain in domains]
|
||||||
code, order, headers = self._request_acme_endpoint('newOrder', {'identifiers': identifiers})
|
code, order, headers = self._request_acme_endpoint('newOrder', {'identifiers': identifiers})
|
||||||
if code >= 400:
|
if code >= 400:
|
||||||
@ -160,7 +162,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
|
|
||||||
authorization['_domain'] = "*.{}".format(authorization['identifier']['value']) if \
|
authorization['_domain'] = "*.{}".format(authorization['identifier']['value']) if \
|
||||||
'wildcard' in authorization and authorization['wildcard'] else authorization['identifier']['value']
|
'wildcard' in authorization and authorization['wildcard'] else authorization['identifier']['value']
|
||||||
print("Authorizing {0}".format(authorization['_domain']))
|
log("Authorizing {0}".format(authorization['_domain']))
|
||||||
|
|
||||||
# create the challenge
|
# create the challenge
|
||||||
matching_challenges = [c for c in authorization['challenges'] if
|
matching_challenges = [c for c in authorization['challenges'] if
|
||||||
@ -181,7 +183,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
|
|
||||||
# after all challenges are created, start processing authorizations
|
# after all challenges are created, start processing authorizations
|
||||||
for authorization in authorizations:
|
for authorization in authorizations:
|
||||||
print("Starting verification of {}".format(authorization['_domain']))
|
log("Starting verification of {}".format(authorization['_domain']))
|
||||||
challenge_handlers[authorization['_domain']].start_challenge(authorization['identifier']['value'],
|
challenge_handlers[authorization['_domain']].start_challenge(authorization['identifier']['value'],
|
||||||
account_thumbprint,
|
account_thumbprint,
|
||||||
authorization['_token'])
|
authorization['_token'])
|
||||||
@ -196,7 +198,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
code, challenge_status, _ = self._request_url(authorization['_challenge']['url'])
|
code, challenge_status, _ = self._request_url(authorization['_challenge']['url'])
|
||||||
|
|
||||||
if challenge_status.get('status') == "valid":
|
if challenge_status.get('status') == "valid":
|
||||||
print("{0} verified".format(authorization['_domain']))
|
log("{0} verified".format(authorization['_domain']))
|
||||||
else:
|
else:
|
||||||
raise ValueError("{0} challenge did not pass: {1}".format(
|
raise ValueError("{0} challenge did not pass: {1}".format(
|
||||||
authorization['_domain'], challenge_status))
|
authorization['_domain'], challenge_status))
|
||||||
@ -212,7 +214,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
challenge_handlers[authorization['_domain']].destroy_challenge(
|
challenge_handlers[authorization['_domain']].destroy_challenge(
|
||||||
authorization['identifier']['value'], account_thumbprint, authorization['_token'])
|
authorization['identifier']['value'], account_thumbprint, authorization['_token'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Challenge destruction failed: {}'.format(e))
|
log('Challenge destruction failed: {}'.format(e), error=True)
|
||||||
|
|
||||||
# check order status and retry once
|
# check order status and retry once
|
||||||
code, order, _ = self._request_url(order_url)
|
code, order, _ = self._request_url(order_url)
|
||||||
@ -223,7 +225,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
raise ValueError("Order is still not ready to be finalized: {0} {1}".format(code, order))
|
raise ValueError("Order is still not ready to be finalized: {0} {1}".format(code, order))
|
||||||
|
|
||||||
# get the new certificate
|
# get the new certificate
|
||||||
print("Finalizing certificate")
|
log("Finalizing certificate")
|
||||||
code, finalize, _ = self._request_acme_url(order['finalize'], {
|
code, finalize, _ = self._request_acme_url(order['finalize'], {
|
||||||
"csr": tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
|
"csr": tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
|
||||||
})
|
})
|
||||||
@ -232,7 +234,7 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
code, finalize, _ = self._request_url(order_url)
|
code, finalize, _ = self._request_url(order_url)
|
||||||
if code >= 400:
|
if code >= 400:
|
||||||
raise ValueError("Error finalizing certificate: {0} {1}".format(code, finalize))
|
raise ValueError("Error finalizing certificate: {0} {1}".format(code, finalize))
|
||||||
print("Certificate ready!")
|
log("Certificate ready!")
|
||||||
|
|
||||||
# return certificate
|
# return certificate
|
||||||
code, certificate, _ = self._request_url(finalize['certificate'], raw_result=True)
|
code, certificate, _ = self._request_url(finalize['certificate'], raw_result=True)
|
||||||
@ -259,6 +261,6 @@ class ACMEAuthority(AbstractACMEAuthority):
|
|||||||
payload['reason'] = int(reason)
|
payload['reason'] = int(reason)
|
||||||
code, result, _ = self._request_acme_endpoint("revokeCert", payload)
|
code, result, _ = self._request_acme_endpoint("revokeCert", payload)
|
||||||
if code < 400:
|
if code < 400:
|
||||||
print("Revocation successful")
|
log("Revocation successful")
|
||||||
else:
|
else:
|
||||||
raise ValueError("Revocation failed: {}".format(result))
|
raise ValueError("Revocation failed: {}".format(result))
|
||||||
|
@ -14,6 +14,8 @@ import json
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import idna
|
import idna
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -80,7 +82,7 @@ def idna_convert(domainlist):
|
|||||||
return domaintranslation
|
return domaintranslation
|
||||||
else:
|
else:
|
||||||
if 'idna' not in sys.modules:
|
if 'idna' not in sys.modules:
|
||||||
print("Unicode domain found but IDNA names could not be translated due to missing idna module")
|
log("Unicode domain(s) found but IDNA names could not be translated due to missing idna module", error=True)
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
|
|
||||||
@ -143,14 +145,14 @@ def parse_config_entry(entry, globalconfig, runtimeconfig):
|
|||||||
|
|
||||||
# SSL cert location (with compatibility to older versions)
|
# SSL cert location (with compatibility to older versions)
|
||||||
if 'server_cert' in globalconfig:
|
if 'server_cert' in globalconfig:
|
||||||
print("WARNING: Legacy configuration directive 'server_cert' used. Support will be removed in 1.0")
|
log("Legacy configuration directive 'server_cert' used. Support will be removed in 1.0", warning=True)
|
||||||
update_config_value(config, 'cert_file', localconfig, globalconfig,
|
update_config_value(config, 'cert_file', localconfig, globalconfig,
|
||||||
globalconfig.get('server_cert',
|
globalconfig.get('server_cert',
|
||||||
os.path.join(config['cert_dir'], "{}.crt".format(config['id']))))
|
os.path.join(config['cert_dir'], "{}.crt".format(config['id']))))
|
||||||
|
|
||||||
# SSL key location (with compatibility to older versions)
|
# SSL key location (with compatibility to older versions)
|
||||||
if 'server_key' in globalconfig:
|
if 'server_key' in globalconfig:
|
||||||
print("WARNING: Legacy configuration directive 'server_key' used. Support will be removed in 1.0")
|
log("Legacy configuration directive 'server_key' used. Support will be removed in 1.0", warning=True)
|
||||||
update_config_value(config, 'key_file', localconfig, globalconfig,
|
update_config_value(config, 'key_file', localconfig, globalconfig,
|
||||||
globalconfig.get('server_key',
|
globalconfig.get('server_key',
|
||||||
os.path.join(config['cert_dir'], "{}.key".format(config['id']))))
|
os.path.join(config['cert_dir'], "{}.key".format(config['id']))))
|
||||||
@ -162,11 +164,13 @@ def parse_config_entry(entry, globalconfig, runtimeconfig):
|
|||||||
# SSL CA location / use static
|
# SSL CA location / use static
|
||||||
update_config_value(config, 'ca_file', localconfig, globalconfig,
|
update_config_value(config, 'ca_file', localconfig, globalconfig,
|
||||||
globalconfig.get('server_ca', config['defaults'].get('server_ca',
|
globalconfig.get('server_ca', config['defaults'].get('server_ca',
|
||||||
os.path.join(config['cert_dir'], "{}.ca".format(config['id'])))))
|
os.path.join(config['cert_dir'],
|
||||||
|
"{}.ca".format(
|
||||||
|
config['id'])))))
|
||||||
update_config_value(config, 'ca_static', localconfig, globalconfig, "false")
|
update_config_value(config, 'ca_static', localconfig, globalconfig, "false")
|
||||||
if 'server_ca' in globalconfig or 'server_ca' in config['defaults']:
|
if 'server_ca' in globalconfig or 'server_ca' in config['defaults']:
|
||||||
config['ca_static'] = "true"
|
config['ca_static'] = "true"
|
||||||
print("WARNING: Legacy configuration directive 'server_ca' used. Support will be removed in 1.0")
|
log("Legacy configuration directive 'server_ca' used. Support removed in 1.0", warning=True)
|
||||||
|
|
||||||
# Domain action configuration
|
# Domain action configuration
|
||||||
config['actions'] = list()
|
config['actions'] = list()
|
||||||
@ -221,7 +225,8 @@ def load():
|
|||||||
if args.config_file:
|
if args.config_file:
|
||||||
global_config_file = args.config_file
|
global_config_file = args.config_file
|
||||||
elif os.path.isfile(LEGACY_CONF_FILE):
|
elif os.path.isfile(LEGACY_CONF_FILE):
|
||||||
print("WARNING: Legacy config file '{}' used. Move to '{}' for 1.0".format(LEGACY_CONF_FILE, DEFAULT_CONF_FILE))
|
log("Legacy config file '{}' used. Move to '{}' for 1.0".format(LEGACY_CONF_FILE, DEFAULT_CONF_FILE),
|
||||||
|
warning=True)
|
||||||
global_config_file = LEGACY_CONF_FILE
|
global_config_file = LEGACY_CONF_FILE
|
||||||
else:
|
else:
|
||||||
global_config_file = DEFAULT_CONF_FILE
|
global_config_file = DEFAULT_CONF_FILE
|
||||||
@ -230,7 +235,7 @@ def load():
|
|||||||
if args.config_dir:
|
if args.config_dir:
|
||||||
domain_config_dir = args.config_dir
|
domain_config_dir = args.config_dir
|
||||||
elif os.path.isdir(LEGACY_CONF_DIR):
|
elif os.path.isdir(LEGACY_CONF_DIR):
|
||||||
print("WARNING: Legacy config dir '{}' used. Move to '{}' for 1.0".format(LEGACY_CONF_DIR, DEFAULT_CONF_DIR))
|
log("Legacy config dir '{}' used. Move to '{}' for 1.0".format(LEGACY_CONF_DIR, DEFAULT_CONF_DIR), warning=True)
|
||||||
domain_config_dir = LEGACY_CONF_DIR
|
domain_config_dir = LEGACY_CONF_DIR
|
||||||
else:
|
else:
|
||||||
domain_config_dir = DEFAULT_CONF_DIR
|
domain_config_dir = DEFAULT_CONF_DIR
|
||||||
@ -240,7 +245,7 @@ def load():
|
|||||||
if args.work_dir:
|
if args.work_dir:
|
||||||
runtimeconfig['work_dir'] = args.work_dir
|
runtimeconfig['work_dir'] = args.work_dir
|
||||||
elif os.path.isdir(LEGACY_WORK_DIR) and domain_config_dir == LEGACY_CONF_DIR:
|
elif os.path.isdir(LEGACY_WORK_DIR) and domain_config_dir == LEGACY_CONF_DIR:
|
||||||
print("WARNING: Legacy work dir '{}' used. Move to config-dir for 1.0".format(LEGACY_WORK_DIR))
|
log("Legacy work dir '{}' used. Move to config-dir for 1.0".format(LEGACY_WORK_DIR), warning=True)
|
||||||
runtimeconfig['work_dir'] = LEGACY_WORK_DIR
|
runtimeconfig['work_dir'] = LEGACY_WORK_DIR
|
||||||
else:
|
else:
|
||||||
runtimeconfig['work_dir'] = domain_config_dir
|
runtimeconfig['work_dir'] = domain_config_dir
|
||||||
|
@ -18,6 +18,7 @@ import dns.update
|
|||||||
|
|
||||||
from acertmgr import tools
|
from acertmgr import tools
|
||||||
from acertmgr.modes.abstract import AbstractChallengeHandler
|
from acertmgr.modes.abstract import AbstractChallengeHandler
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
REGEX_IP4 = r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
|
REGEX_IP4 = r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
|
||||||
REGEX_IP6 = r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}' \
|
REGEX_IP6 = r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}' \
|
||||||
@ -187,7 +188,7 @@ class DNSChallengeHandler(AbstractChallengeHandler):
|
|||||||
if self.verify_dns_record(domain, txtvalue):
|
if self.verify_dns_record(domain, txtvalue):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
print("Waiting until TXT record '{}' is ready".format(domain))
|
log("Waiting until TXT record '{}' is ready".format(domain))
|
||||||
while failtime > datetime.now():
|
while failtime > datetime.now():
|
||||||
time.sleep(self.dns_verify_interval)
|
time.sleep(self.dns_verify_interval)
|
||||||
if self.verify_dns_record(domain, txtvalue):
|
if self.verify_dns_record(domain, txtvalue):
|
||||||
@ -204,7 +205,7 @@ class DNSChallengeHandler(AbstractChallengeHandler):
|
|||||||
ns_ip = self._lookup_ns_ip(domain, nameserverip)
|
ns_ip = self._lookup_ns_ip(domain, nameserverip)
|
||||||
if len(ns_ip) > 0 and all(self._check_txt_record_value(domain, txtvalue, ip) for ip in ns_ip):
|
if len(ns_ip) > 0 and all(self._check_txt_record_value(domain, txtvalue, ip) for ip in ns_ip):
|
||||||
# All NS servers have the necessary TXT record. Succeed immediately!
|
# All NS servers have the necessary TXT record. Succeed immediately!
|
||||||
print("All NS ({}) for '{}' have the correct TXT record".format(','.join(ns_ip), domain))
|
log("All NS ({}) for '{}' have the correct TXT record".format(','.join(ns_ip), domain))
|
||||||
return True
|
return True
|
||||||
except (ValueError, dns.exception.DNSException):
|
except (ValueError, dns.exception.DNSException):
|
||||||
# Fall back to next verification
|
# Fall back to next verification
|
||||||
@ -216,7 +217,7 @@ class DNSChallengeHandler(AbstractChallengeHandler):
|
|||||||
nameserverip = self._lookup_ip(self.dns_verify_server)
|
nameserverip = self._lookup_ip(self.dns_verify_server)
|
||||||
if self._check_txt_record_value(domain, txtvalue, nameserverip):
|
if self._check_txt_record_value(domain, txtvalue, nameserverip):
|
||||||
# Verify server confirms the necessary TXT record. Succeed immediately!
|
# Verify server confirms the necessary TXT record. Succeed immediately!
|
||||||
print("DNS server '{}' found correct TXT record for '{}'".format(self.dns_verify_server, domain))
|
log("DNS server '{}' found correct TXT record for '{}'".format(self.dns_verify_server, domain))
|
||||||
return True
|
return True
|
||||||
except (ValueError, dns.exception.DNSException):
|
except (ValueError, dns.exception.DNSException):
|
||||||
# Fall back to next verification
|
# Fall back to next verification
|
||||||
|
@ -13,6 +13,7 @@ import dns.tsigkeyring
|
|||||||
import dns.update
|
import dns.update
|
||||||
|
|
||||||
from acertmgr.modes.dns.abstract import DNSChallengeHandler
|
from acertmgr.modes.dns.abstract import DNSChallengeHandler
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
DEFAULT_KEY_ALGORITHM = "HMAC-MD5.SIG-ALG.REG.INT"
|
DEFAULT_KEY_ALGORITHM = "HMAC-MD5.SIG-ALG.REG.INT"
|
||||||
|
|
||||||
@ -29,12 +30,9 @@ class ChallengeHandler(DNSChallengeHandler):
|
|||||||
algorithm = re.search(r"algorithm ([a-zA-Z0-9_-]+?);", key_data, re.DOTALL).group(1)
|
algorithm = re.search(r"algorithm ([a-zA-Z0-9_-]+?);", key_data, re.DOTALL).group(1)
|
||||||
tsig_secret = re.search(r"secret \"(.*?)\"", key_data, re.DOTALL).group(1)
|
tsig_secret = re.search(r"secret \"(.*?)\"", key_data, re.DOTALL).group(1)
|
||||||
except IOError as exc:
|
except IOError as exc:
|
||||||
print(exc)
|
raise ValueError("A problem was encountered opening your keyfile '{}': {}".format(tsig_key_file, exc))
|
||||||
raise Exception(
|
|
||||||
"A problem was encountered opening your keyfile, %s." % tsig_key_file)
|
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
print(exc)
|
raise ValueError("Unable to decipher data from your keyfile: {}".format(exc))
|
||||||
raise Exception("Unable to decipher the keyname and secret from your keyfile.")
|
|
||||||
|
|
||||||
keyring = dns.tsigkeyring.from_text({
|
keyring = dns.tsigkeyring.from_text({
|
||||||
key_name: tsig_secret
|
key_name: tsig_secret
|
||||||
@ -73,14 +71,14 @@ class ChallengeHandler(DNSChallengeHandler):
|
|||||||
zone, nameserverip = self._determine_zone_and_nameserverip(domain)
|
zone, nameserverip = self._determine_zone_and_nameserverip(domain)
|
||||||
update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
|
update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
|
||||||
update.add(domain, self.dns_ttl, dns.rdatatype.TXT, txtvalue)
|
update.add(domain, self.dns_ttl, dns.rdatatype.TXT, txtvalue)
|
||||||
print('Adding \'{} {} IN TXT "{}"\' to {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
log('Adding \'{} {} IN TXT "{}"\' to {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
||||||
dns.query.tcp(update, nameserverip)
|
dns.query.tcp(update, nameserverip)
|
||||||
|
|
||||||
def remove_dns_record(self, domain, txtvalue):
|
def remove_dns_record(self, domain, txtvalue):
|
||||||
zone, nameserverip = self._determine_zone_and_nameserverip(domain)
|
zone, nameserverip = self._determine_zone_and_nameserverip(domain)
|
||||||
update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
|
update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
|
||||||
update.delete(domain, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.TXT, txtvalue))
|
update.delete(domain, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.TXT, txtvalue))
|
||||||
print('Deleting \'{} {} IN TXT "{}"\' from {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
log('Deleting \'{} {} IN TXT "{}"\' from {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
||||||
dns.query.tcp(update, nameserverip)
|
dns.query.tcp(update, nameserverip)
|
||||||
|
|
||||||
def verify_dns_record(self, domain, txtvalue):
|
def verify_dns_record(self, domain, txtvalue):
|
||||||
@ -88,7 +86,7 @@ class ChallengeHandler(DNSChallengeHandler):
|
|||||||
# Verify master DNS only if we don't do a full NS check and it has not yet been verified
|
# Verify master DNS only if we don't do a full NS check and it has not yet been verified
|
||||||
_, nameserverip = self._determine_zone_and_nameserverip(domain)
|
_, nameserverip = self._determine_zone_and_nameserverip(domain)
|
||||||
if self._check_txt_record_value(domain, txtvalue, nameserverip, use_tcp=True):
|
if self._check_txt_record_value(domain, txtvalue, nameserverip, use_tcp=True):
|
||||||
print('Verified \'{} {} IN TXT "{}"\' on {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
log('Verified \'{} {} IN TXT "{}"\' on {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
|
||||||
self.nsupdate_verified = True
|
self.nsupdate_verified = True
|
||||||
else:
|
else:
|
||||||
# Master DNS verification failed. Return immediately and try again.
|
# Master DNS verification failed. Return immediately and try again.
|
||||||
|
@ -16,6 +16,7 @@ import socket
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from acertmgr.modes.webdir import HTTPChallengeHandler
|
from acertmgr.modes.webdir import HTTPChallengeHandler
|
||||||
|
from acertmgr.tools import log
|
||||||
|
|
||||||
HTTPServer.allow_reuse_address = True
|
HTTPServer.allow_reuse_address = True
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ class ChallengeHandler(HTTPChallengeHandler):
|
|||||||
# Custom HTTP request handler
|
# Custom HTTP request handler
|
||||||
class _HTTPRequestHandler(BaseHTTPRequestHandler):
|
class _HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
def log_message(self, fmt, *args):
|
def log_message(self, fmt, *args):
|
||||||
print("Request from '%s': %s" % (self.address_string(), fmt % args))
|
log("Request from '%s': %s" % (self.address_string(), fmt % args))
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
# Match token on http://<domain>/.well-known/acme-challenge/<token>
|
# Match token on http://<domain>/.well-known/acme-challenge/<token>
|
||||||
|
@ -11,6 +11,8 @@ import binascii
|
|||||||
import datetime
|
import datetime
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
@ -29,6 +31,35 @@ class InvalidCertificateError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# @brief wrapper for log output
|
||||||
|
def log(msg, exc=None, error=False, warning=False):
|
||||||
|
if error:
|
||||||
|
prefix = "Error: "
|
||||||
|
elif warning:
|
||||||
|
prefix = "Warning: "
|
||||||
|
else:
|
||||||
|
prefix = ""
|
||||||
|
|
||||||
|
output = prefix + msg
|
||||||
|
if exc:
|
||||||
|
_, exc_value, _ = sys.exc_info()
|
||||||
|
if not getattr(exc, '__traceback__', None) and exc == exc_value:
|
||||||
|
# Traceback handling on Python 2 is ugly, so we only output it if the exception is the current sys one
|
||||||
|
formatted_exc = traceback.format_exc()
|
||||||
|
else:
|
||||||
|
formatted_exc = traceback.format_exception(type(exc), exc, getattr(exc, '__traceback__', None))
|
||||||
|
exc_string = ''.join(formatted_exc) if isinstance(formatted_exc, list) else str(formatted_exc)
|
||||||
|
indent = ' ' * len(prefix)
|
||||||
|
output += os.linesep + os.linesep.join(indent + line for line in exc_string.splitlines())
|
||||||
|
|
||||||
|
if error or warning:
|
||||||
|
sys.stderr.write(output + os.linesep)
|
||||||
|
sys.stderr.flush() # force flush buffers after message was written for immediate display
|
||||||
|
else:
|
||||||
|
sys.stdout.write(output + os.linesep)
|
||||||
|
sys.stdout.flush() # force flush buffers after message was written for immediate display
|
||||||
|
|
||||||
|
|
||||||
# @brief wrapper for downloading an url
|
# @brief wrapper for downloading an url
|
||||||
def get_url(url, data=None, headers=None):
|
def get_url(url, data=None, headers=None):
|
||||||
return urlopen(Request(url, data=data, headers={} if headers is None else headers))
|
return urlopen(Request(url, data=data, headers={} if headers is None else headers))
|
||||||
@ -71,7 +102,7 @@ def new_cert_request(names, key, must_staple=False):
|
|||||||
if getattr(x509, 'TLSFeature', None):
|
if getattr(x509, 'TLSFeature', None):
|
||||||
req = req.add_extension(x509.TLSFeature(features=[x509.TLSFeatureType.status_request]), critical=False)
|
req = req.add_extension(x509.TLSFeature(features=[x509.TLSFeatureType.status_request]), critical=False)
|
||||||
else:
|
else:
|
||||||
print('OCSP must-staple ignored as current version of cryptography does not support the flag.')
|
log('OCSP must-staple ignored as current version of cryptography does not support the flag.', warning=True)
|
||||||
req = req.sign(key, hashes.SHA256(), default_backend())
|
req = req.sign(key, hashes.SHA256(), default_backend())
|
||||||
return req
|
return req
|
||||||
|
|
||||||
@ -101,7 +132,7 @@ def new_ssl_key(path=None, key_size=4096):
|
|||||||
try:
|
try:
|
||||||
os.chmod(path, int("0400", 8))
|
os.chmod(path, int("0400", 8))
|
||||||
except OSError:
|
except OSError:
|
||||||
print('Warning: Could not set file permissions on {0}!'.format(path))
|
log('Could not set file permissions on {0}!'.format(path), warning=True)
|
||||||
return private_key
|
return private_key
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +159,7 @@ def write_pem_file(crt, path, perms=None):
|
|||||||
try:
|
try:
|
||||||
os.chmod(path, perms)
|
os.chmod(path, perms)
|
||||||
except OSError:
|
except OSError:
|
||||||
print('Warning: Could not set file permissions ({0}) on {1}!'.format(perms, path))
|
log('Could not set file permissions ({0}) on {1}!'.format(perms, path), warning=True)
|
||||||
|
|
||||||
|
|
||||||
# @brief download the issuer ca for a given certificate
|
# @brief download the issuer ca for a given certificate
|
||||||
@ -143,14 +174,14 @@ def download_issuer_ca(cert):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not ca_issuers:
|
if not ca_issuers:
|
||||||
print("Could not determine issuer CA for given certificate: {}".format(cert))
|
log("Could not determine issuer CA for given certificate: {}".format(cert), error=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
print("Downloading CA certificate from {}".format(ca_issuers))
|
log("Downloading CA certificate from {}".format(ca_issuers))
|
||||||
resp = get_url(ca_issuers)
|
resp = get_url(ca_issuers)
|
||||||
code = resp.getcode()
|
code = resp.getcode()
|
||||||
if code >= 400:
|
if code >= 400:
|
||||||
print("Could not download issuer CA (error {}) for given certificate: {}".format(code, cert))
|
log("Could not download issuer CA (error {}) for given certificate: {}".format(code, cert), error=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return x509.load_der_x509_certificate(resp.read(), default_backend())
|
return x509.load_der_x509_certificate(resp.read(), default_backend())
|
||||||
@ -159,7 +190,7 @@ def download_issuer_ca(cert):
|
|||||||
# @brief determine all san domains on a given certificate
|
# @brief determine all san domains on a given certificate
|
||||||
def get_cert_domains(cert):
|
def get_cert_domains(cert):
|
||||||
if cert is None:
|
if cert is None:
|
||||||
print("WARN: None-certificate has no domains. You have found a bug. Congratulations!")
|
log("None-certificate has no domains. You have found a bug. Congratulations!", warning=True)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
san_cert = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
|
san_cert = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
|
||||||
@ -169,6 +200,16 @@ def get_cert_domains(cert):
|
|||||||
return [cert.subject.rfc4514_string()[3:], ] # strip CN= from the result and return as 1 item list
|
return [cert.subject.rfc4514_string()[3:], ] # strip CN= from the result and return as 1 item list
|
||||||
|
|
||||||
|
|
||||||
|
# @brief determine certificate cn
|
||||||
|
def get_cert_cn(cert):
|
||||||
|
return "CN={}".format(cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value)
|
||||||
|
|
||||||
|
|
||||||
|
# @brief determine certificate end of validity
|
||||||
|
def get_cert_valid_until(cert):
|
||||||
|
return cert.not_valid_after
|
||||||
|
|
||||||
|
|
||||||
# @brief convert certificate to PEM format
|
# @brief convert certificate to PEM format
|
||||||
# @param cert certificate object in pyopenssl format
|
# @param cert certificate object in pyopenssl format
|
||||||
# @return the certificate in PEM format
|
# @return the certificate in PEM format
|
||||||
|
Loading…
Reference in New Issue
Block a user