1
0
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:
2017-08-16 20:25:16 +00:00
parent 789d80d712
commit 509f7bfaa8
18 changed files with 533 additions and 681 deletions

View File

@@ -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

View File

@@ -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"))

View File

@@ -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()

View File

@@ -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
"""

View File

@@ -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"))

View File

@@ -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")

View File

@@ -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"))