mirror of
https://github.com/moepman/acertmgr.git
synced 2024-11-16 05:19:10 +01:00
acertmgr: Add a OCSP validation to certificate verification
This commit is contained in:
parent
c33a39a433
commit
b37d0cad94
@ -72,6 +72,7 @@ By default the directory (work_dir) containing the working data (csr,certificate
|
||||
| account_key_algorithm | d,**g** | Key-algorithm for newly generated account keys (RSA, EC, ED25519, ED448) | RSA |
|
||||
| account_key_length | d,**g** | Key-length for newly generated RSA account keys (in bits) or EC curve (256=P-256, 384=P-384, 521=P-521) | depends on account_key_algorithm |
|
||||
| ttl_days | d,**g** | Renew certificate if it has less than this value validity left | 30 |
|
||||
| validate_ocsp | d,**g** | Renew certificate if it's OCSP status is REVOKED. Allowed values for this key are: false, sha1, sha224, sha256, sha384, sha512 | sha1 (as mandated by RFC5019) |
|
||||
| cert_dir | d,**g** | Directory containing all certificate related data (crt,key,csr) | {work_dir} |
|
||||
| key_algorithm | d,**g** | Key-algorithm for newly generated private keys (RSA, EC, ED25519, ED448) | RSA |
|
||||
| key_length | d,**g** | Key-length for newly generated RSA private keys (in bits) or EC curve (256=P-256, 384=P-384, 521=P-521) | depends on key_algorithm |
|
||||
|
@ -129,7 +129,8 @@ def cert_revoke(cert, configs, fallback_authority, reason=None):
|
||||
if not acmeconfig:
|
||||
acmeconfig = fallback_authority
|
||||
log("No matching authority found to revoke {}: {}, using globalconfig/defaults".format(tools.get_cert_cn(cert),
|
||||
tools.get_cert_domains(cert)), warning=True)
|
||||
tools.get_cert_domains(
|
||||
cert)), warning=True)
|
||||
acme = authority(acmeconfig)
|
||||
acme.register_account()
|
||||
acme.revoke_crt(cert, reason)
|
||||
@ -157,9 +158,21 @@ def main():
|
||||
cert = None
|
||||
if os.path.isfile(config['cert_file']):
|
||||
cert = tools.read_pem_file(config['cert_file'])
|
||||
if not cert or not tools.is_cert_valid(cert, config['ttl_days']) or (
|
||||
'force_renew' in runtimeconfig and
|
||||
all(d in config['domainlist'] for d in runtimeconfig['force_renew'])):
|
||||
validate_ocsp = str(config.get('validate_ocsp')).lower() != 'false'
|
||||
if validate_ocsp and cert and os.path.isfile(config['ca_file']):
|
||||
try:
|
||||
issuer = tools.read_pem_file(config['ca_file'])
|
||||
except Exception as e1:
|
||||
log("Failed to retrieve issuer from ca file: {}. Trying to download...".format(e1))
|
||||
try:
|
||||
issuer = tools.download_issuer_ca(cert)
|
||||
except Exception as e2:
|
||||
log("Failed to download issuer for cert file: {}. Cannot validate OCSP.".format(e2))
|
||||
validate_ocsp = False
|
||||
if not cert or ('force_renew' in runtimeconfig and all(
|
||||
d in config['domainlist'] for d in runtimeconfig['force_renew'])) \
|
||||
or not tools.is_cert_valid(cert, config['ttl_days']) \
|
||||
or (validate_ocsp and not tools.is_ocsp_valid(cert, issuer, config['validate_ocsp'])):
|
||||
cert_get(config)
|
||||
if str(config.get('cert_revoke_superseded')).lower() == 'true' and cert:
|
||||
superseded.add(cert)
|
||||
|
@ -19,6 +19,7 @@ from acertmgr.tools import idna_convert
|
||||
DEFAULT_CONF_DIR = "/etc/acertmgr"
|
||||
DEFAULT_CONF_FILENAME = "acertmgr.conf"
|
||||
DEFAULT_TTL = 30 # days
|
||||
DEFAULT_VALIDATE_OCSP = "sha1" # mandated by RFC5019
|
||||
DEFAULT_API = "v2"
|
||||
DEFAULT_AUTHORITY = "https://acme-v02.api.letsencrypt.org"
|
||||
|
||||
@ -106,6 +107,9 @@ def parse_config_entry(entry, globalconfig, runtimeconfig):
|
||||
update_config_value(config, 'ttl_days', localconfig, globalconfig, DEFAULT_TTL)
|
||||
config['ttl_days'] = int(config['ttl_days'])
|
||||
|
||||
# Validate OCSP on certificate verification
|
||||
update_config_value(config, 'validate_ocsp', localconfig, globalconfig, DEFAULT_VALIDATE_OCSP)
|
||||
|
||||
# Revoke old certificate with reason superseded after renewal
|
||||
update_config_value(config, 'cert_revoke_superseded', localconfig, globalconfig, "false")
|
||||
|
||||
|
@ -22,6 +22,11 @@ from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
||||
from cryptography.utils import int_to_bytes
|
||||
from cryptography.x509.oid import NameOID, ExtensionOID
|
||||
|
||||
try:
|
||||
from cryptography.x509 import ocsp
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519, ed448
|
||||
except ImportError:
|
||||
@ -388,3 +393,48 @@ def idna_convert(domainlist):
|
||||
if any(ord(c) >= 128 for c in ''.join(domainlist)) and 'idna' not in sys.modules:
|
||||
log("Unicode domain(s) found but IDNA names could not be translated due to missing idna module", error=True)
|
||||
return [(x, x) for x in domainlist]
|
||||
|
||||
|
||||
# @brief validate the OCSP status for a given certificate by the given issuer
|
||||
def is_ocsp_valid(cert, issuer, hash_algo):
|
||||
if hash_algo == 'sha1':
|
||||
algorithm = hashes.SHA1
|
||||
elif hash_algo == 'sha224':
|
||||
algorithm = hashes.SHA224
|
||||
elif hash_algo == 'sha256':
|
||||
algorithm = hashes.SHA256
|
||||
elif hash_algo == 'sha385':
|
||||
algorithm = hashes.SHA384
|
||||
elif hash_algo == 'sha512':
|
||||
algorithm = hashes.SHA512
|
||||
else:
|
||||
log("Invalid hash algorithm '{}' used for OCSP validation. Validation ignored.".format(hash_algo), warning=True)
|
||||
return True
|
||||
|
||||
try:
|
||||
ocsp_urls = []
|
||||
aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS)
|
||||
for data in aia.value:
|
||||
if data.access_method == x509.OID_OCSP:
|
||||
ocsp_urls.append(data.access_location.value)
|
||||
|
||||
# This is a bit of a hack due to validation problems within cryptography (TODO: Check if this is still true)
|
||||
# Correct replacement: ocsprequest = ocsp.OCSPRequestBuilder().add_certificate(cert, issuer, algorithm).build()
|
||||
ocsprequest = ocsp.OCSPRequestBuilder((cert, issuer, algorithm)).build()
|
||||
ocsprequestdata = ocsprequest.public_bytes(serialization.Encoding.DER)
|
||||
for ocsp_url in ocsp_urls:
|
||||
response = get_url(ocsp_url,
|
||||
ocsprequestdata,
|
||||
{
|
||||
'Accept': 'application/ocsp-response',
|
||||
'Content-Type': 'application/ocsp-request',
|
||||
})
|
||||
ocspresponsedata = response.read()
|
||||
ocspresponse = ocsp.load_der_ocsp_response(ocspresponsedata)
|
||||
if ocspresponse.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL \
|
||||
and ocspresponse.certificate_status == ocsp.OCSPCertStatus.REVOKED:
|
||||
return False
|
||||
except Exception as e:
|
||||
log("An exception occurred during OCSP validation (Validation will be ignored): {}".format(e), error=True)
|
||||
|
||||
return True
|
||||
|
Loading…
Reference in New Issue
Block a user