mirror of
https://github.com/laurivosandi/certidude
synced 2025-10-30 08:59:13 +00:00
Migrate from cryptography.io to oscrypto
This commit is contained in:
@@ -13,7 +13,6 @@ from certidude import authority, mailer
|
||||
from certidude.auth import login_required, authorize_admin
|
||||
from certidude.user import User
|
||||
from certidude.decorators import serialize, csrf_protection
|
||||
from cryptography.x509.oid import NameOID
|
||||
from certidude import const, config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -82,8 +81,8 @@ class SessionResource(object):
|
||||
common_name = common_name,
|
||||
server = server,
|
||||
# TODO: key type, key length, key exponent, key modulo
|
||||
signed = obj.not_valid_before,
|
||||
expires = obj.not_valid_after,
|
||||
signed = obj["tbs_certificate"]["validity"]["not_before"].native,
|
||||
expires = obj["tbs_certificate"]["validity"]["not_after"].native,
|
||||
sha256sum = hashlib.sha256(buf).hexdigest(),
|
||||
lease = lease,
|
||||
tags = tags,
|
||||
@@ -108,8 +107,7 @@ class SessionResource(object):
|
||||
offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option
|
||||
dead = 604800 # Seconds from last activity to consider lease dead, X509 chain broken or machine discarded
|
||||
),
|
||||
common_name = authority.ca_cert.subject.get_attributes_for_oid(
|
||||
NameOID.COMMON_NAME)[0].value,
|
||||
common_name = authority.certificate.subject.native["common_name"],
|
||||
mailer = dict(
|
||||
name = config.MAILER_NAME,
|
||||
address = config.MAILER_ADDRESS
|
||||
|
||||
@@ -33,7 +33,7 @@ class LeaseResource(object):
|
||||
# TODO: verify signature
|
||||
common_name = req.get_param("client", required=True)
|
||||
path, buf, cert = authority.get_signed(common_name) # TODO: catch exceptions
|
||||
if req.get_param("serial") and cert.serial != req.get_param_as_int("serial"): # OCSP-ish solution for OpenVPN, not exposed for StrongSwan
|
||||
if req.get_param("serial") and cert.serial_number != req.get_param_as_int("serial"): # OCSP-ish solution for OpenVPN, not exposed for StrongSwan
|
||||
raise falcon.HTTPForbidden("Forbidden", "Invalid serial number supplied")
|
||||
|
||||
xattr.setxattr(path, "user.lease.outer_address", req.get_param("outer_address", required=True).encode("ascii"))
|
||||
|
||||
@@ -52,7 +52,7 @@ class OCSPResource(object):
|
||||
assert link_target.startswith("../")
|
||||
assert link_target.endswith(".pem")
|
||||
path, buf, cert = authority.get_signed(link_target[3:-4])
|
||||
if serial != cert.serial:
|
||||
if serial != cert.serial_number:
|
||||
raise EnvironmentError("integrity check failed")
|
||||
status = ocsp.CertStatus(name='good', value=None)
|
||||
except EnvironmentError:
|
||||
@@ -94,9 +94,13 @@ class OCSPResource(object):
|
||||
'response_type': u"basic_ocsp_response",
|
||||
'response': {
|
||||
'tbs_response_data': response_data,
|
||||
'certs': [server_certificate.asn1],
|
||||
'signature_algorithm': {'algorithm': u"sha1_rsa"},
|
||||
'signature': b64decode(authority.signer_exec("sign-pkcs7", b64encode(response_data.dump()))),
|
||||
'certs': [server_certificate.asn1]
|
||||
'signature': asymmetric.rsa_pkcs1v15_sign(
|
||||
authority.private_key,
|
||||
response_data.dump(),
|
||||
"sha1"
|
||||
)
|
||||
}
|
||||
}
|
||||
}).dump()
|
||||
|
||||
@@ -6,18 +6,16 @@ import ipaddress
|
||||
import json
|
||||
import os
|
||||
import hashlib
|
||||
from asn1crypto import pem
|
||||
from asn1crypto.csr import CertificationRequest
|
||||
from base64 import b64decode
|
||||
from certidude import config, authority, push, errors
|
||||
from certidude.auth import login_required, login_optional, authorize_admin
|
||||
from certidude.decorators import serialize, csrf_protection
|
||||
from certidude.firewall import whitelist_subnets, whitelist_content_types
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.x509.oid import NameOID
|
||||
from datetime import datetime
|
||||
from oscrypto import asymmetric
|
||||
from oscrypto.errors import SignatureError
|
||||
from xattr import getxattr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -35,19 +33,14 @@ class RequestListResource(object):
|
||||
@whitelist_content_types("application/pkcs10")
|
||||
def on_post(self, req, resp):
|
||||
"""
|
||||
Validate and parse certificate signing request
|
||||
Validate and parse certificate signing request, the RESTful way
|
||||
"""
|
||||
reasons = []
|
||||
body = req.stream.read(req.content_length)
|
||||
csr = x509.load_pem_x509_csr(body, default_backend())
|
||||
try:
|
||||
common_name, = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
|
||||
except: # ValueError?
|
||||
logger.warning(u"Rejected signing request without common name from %s",
|
||||
req.context.get("remote_addr"))
|
||||
raise falcon.HTTPBadRequest(
|
||||
"Bad request",
|
||||
"No common name specified!")
|
||||
body = req.stream.read(req.content_length).encode("ascii")
|
||||
|
||||
header, _, der_bytes = pem.unarmor(body)
|
||||
csr = CertificationRequest.load(der_bytes)
|
||||
common_name = csr["certification_request_info"]["subject"].native["common_name"]
|
||||
|
||||
"""
|
||||
Handle domain computer automatic enrollment
|
||||
@@ -55,10 +48,10 @@ class RequestListResource(object):
|
||||
machine = req.context.get("machine")
|
||||
if machine:
|
||||
if config.MACHINE_ENROLLMENT_ALLOWED:
|
||||
if common_name.value != machine:
|
||||
if common_name != machine:
|
||||
raise falcon.HTTPBadRequest(
|
||||
"Bad request",
|
||||
"Common name %s differs from Kerberos credential %s!" % (common_name.value, machine))
|
||||
"Common name %s differs from Kerberos credential %s!" % (common_name, machine))
|
||||
|
||||
# Automatic enroll with Kerberos machine cerdentials
|
||||
resp.set_header("Content-Type", "application/x-pem-file")
|
||||
@@ -73,52 +66,48 @@ class RequestListResource(object):
|
||||
Attempt to renew certificate using currently valid key pair
|
||||
"""
|
||||
try:
|
||||
path, buf, cert = authority.get_signed(common_name.value)
|
||||
path, buf, cert = authority.get_signed(common_name)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
pass # No currently valid certificate for this common name
|
||||
else:
|
||||
if cert.public_key().public_numbers() == csr.public_key().public_numbers():
|
||||
cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native
|
||||
csr_pk = csr["certification_request_info"]["subject_pk_info"].native
|
||||
|
||||
if cert_pk == csr_pk: # Same public key, assume renewal
|
||||
expires = cert["tbs_certificate"]["validity"]["not_after"].native.replace(tzinfo=None)
|
||||
renewal_header = req.get_header("X-Renewal-Signature")
|
||||
|
||||
if not renewal_header:
|
||||
# No header supplied, redirect to signed API call
|
||||
resp.status = falcon.HTTP_SEE_OTHER
|
||||
resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", common_name.value)
|
||||
resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", common_name)
|
||||
return
|
||||
|
||||
try:
|
||||
renewal_signature = b64decode(renewal_header)
|
||||
except TypeError, ValueError:
|
||||
logger.error(u"Renewal failed, bad signature supplied for %s", common_name.value)
|
||||
logger.error(u"Renewal failed, bad signature supplied for %s", common_name)
|
||||
reasons.append("Renewal failed, bad signature supplied")
|
||||
else:
|
||||
try:
|
||||
verifier = cert.public_key().verifier(
|
||||
renewal_signature,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA512()),
|
||||
salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA512()
|
||||
)
|
||||
verifier.update(buf)
|
||||
verifier.update(body)
|
||||
verifier.verify()
|
||||
except InvalidSignature:
|
||||
logger.error(u"Renewal failed, invalid signature supplied for %s", common_name.value)
|
||||
asymmetric.rsa_pss_verify(
|
||||
asymmetric.load_certificate(cert),
|
||||
renewal_signature, buf + body, "sha512")
|
||||
except SignatureError:
|
||||
logger.error(u"Renewal failed, invalid signature supplied for %s", common_name)
|
||||
reasons.append("Renewal failed, invalid signature supplied")
|
||||
else:
|
||||
# At this point renewal signature was valid but we need to perform some extra checks
|
||||
if datetime.utcnow() > cert.not_valid_after:
|
||||
logger.error(u"Renewal failed, current certificate for %s has expired", common_name.value)
|
||||
if datetime.utcnow() > expires:
|
||||
logger.error(u"Renewal failed, current certificate for %s has expired", common_name)
|
||||
reasons.append("Renewal failed, current certificate expired")
|
||||
elif not config.CERTIFICATE_RENEWAL_ALLOWED:
|
||||
logger.error(u"Renewal requested for %s, but not allowed by authority settings", common_name.value)
|
||||
logger.error(u"Renewal requested for %s, but not allowed by authority settings", common_name)
|
||||
reasons.append("Renewal requested, but not allowed by authority settings")
|
||||
else:
|
||||
resp.set_header("Content-Type", "application/x-x509-user-cert")
|
||||
_, resp.body = authority._sign(csr, body, overwrite=True)
|
||||
logger.info(u"Renewed certificate for %s", common_name.value)
|
||||
logger.info(u"Renewed certificate for %s", common_name)
|
||||
return
|
||||
|
||||
|
||||
@@ -127,17 +116,17 @@ class RequestListResource(object):
|
||||
autosigning was requested and certificate can be automatically signed
|
||||
"""
|
||||
if req.get_param_as_bool("autosign"):
|
||||
if "." not in common_name.value:
|
||||
if not authority.server_flags(common_name):
|
||||
for subnet in config.AUTOSIGN_SUBNETS:
|
||||
if req.context.get("remote_addr") in subnet:
|
||||
try:
|
||||
resp.set_header("Content-Type", "application/x-pem-file")
|
||||
_, resp.body = authority._sign(csr, body)
|
||||
logger.info(u"Autosigned %s as %s is whitelisted", common_name.value, req.context.get("remote_addr"))
|
||||
logger.info(u"Autosigned %s as %s is whitelisted", common_name, req.context.get("remote_addr"))
|
||||
return
|
||||
except EnvironmentError:
|
||||
logger.info(u"Autosign for %s from %s failed, signed certificate already exists",
|
||||
common_name.value, req.context.get("remote_addr"))
|
||||
common_name, req.context.get("remote_addr"))
|
||||
reasons.append("Autosign failed, signed certificate already exists")
|
||||
break
|
||||
else:
|
||||
@@ -147,7 +136,7 @@ class RequestListResource(object):
|
||||
|
||||
# Attempt to save the request otherwise
|
||||
try:
|
||||
request_path, _, _ = authority.store_request(body.decode("ascii"),
|
||||
request_path, _, _ = authority.store_request(body,
|
||||
address=str(req.context.get("remote_addr")))
|
||||
except errors.RequestExists:
|
||||
reasons.append("Same request already uploaded exists")
|
||||
@@ -160,10 +149,10 @@ class RequestListResource(object):
|
||||
"CSR with such CN already exists",
|
||||
"Will not overwrite existing certificate signing request, explicitly delete CSR and try again")
|
||||
else:
|
||||
push.publish("request-submitted", common_name.value)
|
||||
push.publish("request-submitted", common_name)
|
||||
|
||||
# Wait the certificate to be signed if waiting is requested
|
||||
logger.info(u"Signing request %s from %s stored", common_name.value, req.context.get("remote_addr"))
|
||||
logger.info(u"Stored signing request %s from %s", common_name, req.context.get("remote_addr"))
|
||||
if req.get_param("wait"):
|
||||
# Redirect to nginx pub/sub
|
||||
url = config.LONG_POLL_SUBSCRIBE % hashlib.sha256(body).hexdigest()
|
||||
@@ -221,7 +210,7 @@ class RequestDetailResource(object):
|
||||
@csrf_protection
|
||||
@login_required
|
||||
@authorize_admin
|
||||
def on_patch(self, req, resp, cn):
|
||||
def on_post(self, req, resp, cn):
|
||||
"""
|
||||
Sign a certificate signing request
|
||||
"""
|
||||
|
||||
@@ -6,9 +6,6 @@ import logging
|
||||
from certidude import const, config
|
||||
from certidude.authority import export_crl, list_revoked
|
||||
from certidude.firewall import whitelist_subnets
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,9 +20,8 @@ class RevocationListResource(object):
|
||||
"Content-Disposition",
|
||||
("attachment; filename=%s.crl" % const.HOSTNAME).encode("ascii"))
|
||||
# Convert PEM to DER
|
||||
logger.debug(u"Serving revocation list to %s in DER format", req.context.get("remote_addr"))
|
||||
resp.body = x509.load_pem_x509_crl(export_crl(),
|
||||
default_backend()).public_bytes(Encoding.DER)
|
||||
logger.debug(u"Serving revocation list (DER) to %s", req.context.get("remote_addr"))
|
||||
resp.body = export_crl(pem=False)
|
||||
elif req.client_accepts("application/x-pem-file"):
|
||||
if req.get_param_as_bool("wait"):
|
||||
url = config.LONG_POLL_SUBSCRIBE % "crl"
|
||||
@@ -38,7 +34,7 @@ class RevocationListResource(object):
|
||||
resp.append_header(
|
||||
"Content-Disposition",
|
||||
("attachment; filename=%s-crl.pem" % const.HOSTNAME).encode("ascii"))
|
||||
logger.debug(u"Serving revocation list to %s in PEM format", req.context.get("remote_addr"))
|
||||
logger.debug(u"Serving revocation list (PEM) to %s", req.context.get("remote_addr"))
|
||||
resp.body = export_crl()
|
||||
else:
|
||||
logger.debug(u"Client %s asked revocation list in unsupported format" % req.context.get("remote_addr"))
|
||||
|
||||
@@ -41,7 +41,7 @@ class SCEPResource(object):
|
||||
def on_get(self, req, resp):
|
||||
operation = req.get_param("operation")
|
||||
if operation.lower() == "getcacert":
|
||||
resp.stream = keys.parse_certificate(authority.ca_buf).dump()
|
||||
resp.stream = keys.parse_certificate(authority.certificate_buf).dump()
|
||||
resp.append_header("Content-Type", "application/x-x509-ca-cert")
|
||||
return
|
||||
|
||||
@@ -125,14 +125,16 @@ class SCEPResource(object):
|
||||
encrypted_content = encrypted_content_info['encrypted_content'].native
|
||||
recipient, = encrypted_envelope['recipient_infos']
|
||||
|
||||
if recipient.native["rid"]["serial_number"] != authority.ca_cert.serial:
|
||||
if recipient.native["rid"]["serial_number"] != authority.certificate.serial_number:
|
||||
raise SCEPBadCertId()
|
||||
|
||||
# Since CA private key is not directly readable here, we'll redirect it to signer socket
|
||||
key = b64decode(authority.signer_exec("decrypt-pkcs7", b64encode(recipient.native["encrypted_key"])))
|
||||
key = asymmetric.rsa_pkcs1v15_decrypt(
|
||||
authority.private_key,
|
||||
recipient.native["encrypted_key"])
|
||||
if len(key) == 8: key = key * 3 # Convert DES to 3DES
|
||||
buf = symmetric.tripledes_cbc_pkcs5_decrypt(key, encrypted_content, iv)
|
||||
_, common_name = authority.store_request(buf, overwrite=True)
|
||||
_, _, common_name = authority.store_request(buf, overwrite=True)
|
||||
cert, buf = authority.sign(common_name, overwrite=True)
|
||||
signed_certificate = asymmetric.load_certificate(buf)
|
||||
content = signed_certificate.asn1.dump()
|
||||
@@ -251,7 +253,11 @@ class SCEPResource(object):
|
||||
}),
|
||||
'digest_algorithm': algos.DigestAlgorithm({'algorithm': u"sha1"}),
|
||||
'signature_algorithm': algos.SignedDigestAlgorithm({'algorithm': u"rsassa_pkcs1v15"}),
|
||||
'signature': b64decode(authority.signer_exec("sign-pkcs7", b64encode(b"\x31" + attrs.dump()[1:])))
|
||||
'signature': asymmetric.rsa_pkcs1v15_sign(
|
||||
authority.private_key,
|
||||
b"\x31" + attrs.dump()[1:],
|
||||
"sha1"
|
||||
)
|
||||
})
|
||||
|
||||
resp.append_header("Content-Type", "application/x-pki-message")
|
||||
|
||||
@@ -31,9 +31,9 @@ class SignedCertificateDetailResource(object):
|
||||
resp.set_header("Content-Disposition", ("attachment; filename=%s.json" % cn))
|
||||
resp.body = json.dumps(dict(
|
||||
common_name = cn,
|
||||
serial_number = "%x" % cert.serial,
|
||||
signed = cert.not_valid_before.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
||||
expires = cert.not_valid_after.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
||||
serial_number = "%x" % cert.serial_number,
|
||||
signed = cert["tbs_certificate"]["validity"]["not_before"].native.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
||||
expires = cert["tbs_certificate"]["validity"]["not_after"].native.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
||||
sha256sum = hashlib.sha256(buf).hexdigest()))
|
||||
logger.debug(u"Served certificate %s to %s as application/json",
|
||||
cn, req.context.get("remote_addr"))
|
||||
|
||||
Reference in New Issue
Block a user