certidude/certidude/api/ocsp.py

109 lines
4.3 KiB
Python
Raw Normal View History

2017-05-25 19:20:29 +00:00
import click
import hashlib
import os
from asn1crypto.util import timezone
from asn1crypto import cms, algos, x509, ocsp
from base64 import b64decode, b64encode
from certbuilder import pem_armor_certificate
from certidude import authority, push, config
from certidude.firewall import whitelist_subnets
2017-07-05 21:22:02 +00:00
from datetime import datetime, timedelta
2017-05-25 19:20:29 +00:00
from oscrypto import keys, asymmetric, symmetric
from oscrypto.errors import SignatureError
class OCSPResource(object):
@whitelist_subnets(config.OCSP_SUBNETS)
def __call__(self, req, resp):
if req.method == "GET":
_, _, _, tail = req.path.split("/", 3)
body = b64decode(tail)
elif req.method == "POST":
body = req.stream.read(req.content_length or 0)
else:
raise falcon.HTTPMethodNotAllowed()
fh = open(config.AUTHORITY_CERTIFICATE_PATH, "rb") # TODO: import from authority
2017-05-25 19:20:29 +00:00
server_certificate = asymmetric.load_certificate(fh.read())
fh.close()
ocsp_req = ocsp.OCSPRequest.load(body)
2017-05-25 19:20:29 +00:00
now = datetime.now(timezone.utc)
response_extensions = []
try:
for ext in ocsp_req["tbs_request"]["request_extensions"]:
if ext["extn_id"].native == "nonce":
response_extensions.append(
ocsp.ResponseDataExtension({
'extn_id': "nonce",
'critical': False,
'extn_value': ext["extn_value"]
})
)
except ValueError: # https://github.com/wbond/asn1crypto/issues/56
pass
2017-05-25 19:20:29 +00:00
responses = []
for item in ocsp_req["tbs_request"]["request_list"]:
serial = item["req_cert"]["serial_number"].native
try:
link_target = os.readlink(os.path.join(config.SIGNED_BY_SERIAL_DIR, "%x.pem" % serial))
assert link_target.startswith("../")
assert link_target.endswith(".pem")
path, buf, cert, signed, expires = authority.get_signed(link_target[3:-4])
if serial != cert.serial_number:
logger.error("Certificate store integrity check failed, %s refers to certificate with serial %x" % (link_target, cert.serial_number))
raise EnvironmentError("Integrity check failed")
2017-05-25 19:20:29 +00:00
status = ocsp.CertStatus(name='good', value=None)
except EnvironmentError:
try:
path, buf, cert, signed, expires, revoked = authority.get_revoked(serial)
2017-05-25 19:20:29 +00:00
status = ocsp.CertStatus(
name='revoked',
value={
'revocation_time': revoked,
'revocation_reason': "key_compromise",
2017-05-25 19:20:29 +00:00
})
except EnvironmentError:
status = ocsp.CertStatus(name="unknown", value=None)
responses.append({
'cert_id': {
'hash_algorithm': {
'algorithm': "sha1"
2017-05-25 19:20:29 +00:00
},
'issuer_name_hash': server_certificate.asn1.subject.sha1,
'issuer_key_hash': server_certificate.public_key.asn1.sha1,
'serial_number': serial,
},
'cert_status': status,
'this_update': now,
'single_extensions': []
})
response_data = ocsp.ResponseData({
'responder_id': ocsp.ResponderId(name='by_key', value=server_certificate.public_key.asn1.sha1),
'produced_at': now,
'responses': responses,
'response_extensions': response_extensions
})
resp.body = ocsp.OCSPResponse({
'response_status': "successful",
2017-05-25 19:20:29 +00:00
'response_bytes': {
'response_type': "basic_ocsp_response",
2017-05-25 19:20:29 +00:00
'response': {
'tbs_response_data': response_data,
'certs': [server_certificate.asn1],
'signature_algorithm': {'algorithm': "sha1_rsa"},
'signature': asymmetric.rsa_pkcs1v15_sign(
authority.private_key,
response_data.dump(),
"sha1"
)
2017-05-25 19:20:29 +00:00
}
}
}).dump()