Add key curve hash to bootstrap, code style fixes

This commit is contained in:
Marvin Martinson 2021-08-19 08:08:07 +00:00
parent 31d4dad2be
commit 443c168cbd
4 changed files with 67 additions and 44 deletions

View File

@ -1,5 +1,4 @@
import hashlib
import logging
from pinecrypt.server import authority, const, config
from pinecrypt.server.common import cert_to_dn
from pinecrypt.server.decorators import serialize
@ -7,6 +6,7 @@ from pinecrypt.server.mongolog import LogHandler
logger = LogHandler()
class BootstrapResource(object):
@serialize
def on_get(self, req, resp):
@ -31,6 +31,9 @@ class BootstrapResource(object):
esp=config.get("Globals", "STRONGSWAN_ESP")["value"],
),
certificate=dict(
key_size=const.KEY_SIZE,
curve=const.CURVE_NAME,
hash_algorithm=const.CERTIFICATE_HASH_ALGORITHM,
algorithm=authority.public_key.algorithm,
common_name=authority.certificate.subject.native["common_name"],
distinguished_name=cert_to_dn(authority.certificate),

View File

@ -1,5 +1,4 @@
import click
import logging
import os
import re
import socket
@ -16,7 +15,7 @@ from csrbuilder import CSRBuilder, pem_armor_csr
from datetime import datetime, timedelta
from bson.objectid import ObjectId
#logger = logging.getLogger(__name__)
# logger = logging.getLogger(__name__)
logger = LogHandler()
# Cache CA certificate
@ -36,18 +35,18 @@ def self_enroll(skip_notify=False):
common_name = const.HOSTNAME
try:
cert, cert_doc, pem_buf = get_signed(common_name=common_name,namespace=const.AUTHORITY_NAMESPACE)
cert, cert_doc, pem_buf = get_signed(common_name=common_name, namespace=const.AUTHORITY_NAMESPACE)
self_public_key = asymmetric.load_public_key(cert["tbs_certificate"]["subject_public_key_info"])
private_key = asymmetric.load_private_key(const.SELF_KEY_PATH)
except (NameError, FileNotFoundError, errors.CertificateDoesNotExist) as error: # certificate or private key not found
except (NameError, FileNotFoundError, errors.CertificateDoesNotExist): # certificate or private key not found
click.echo("Generating private key for frontend: %s" % const.SELF_KEY_PATH)
with open(const.SELF_KEY_PATH, 'wb') as fh:
with open(const.SELF_KEY_PATH, "wb") as fh:
if public_key.algorithm == "ec":
self_public_key, private_key = asymmetric.generate_pair("ec", curve=public_key.curve)
elif public_key.algorithm == "rsa":
self_public_key, private_key = asymmetric.generate_pair("rsa", bit_size=public_key.bit_size)
else:
raise NotImplemented("CA certificate public key algorithm %s not supported" % public_key.algorithm)
raise NotImplementedError("CA certificate public key algorithm %s not supported" % public_key.algorithm)
fh.write(asymmetric.dump_private_key(private_key, None))
else:
now = datetime.utcnow().replace(tzinfo=pytz.UTC)
@ -55,13 +54,13 @@ def self_enroll(skip_notify=False):
click.echo("Self certificate still valid, delete to self-enroll again")
return
builder = CSRBuilder({"common_name": common_name}, self_public_key)
builder.hash_algo = const.CERTIFICATE_HASH_ALGORITHM
request = builder.build(private_key)
now = datetime.utcnow().replace(tzinfo=pytz.UTC)
d ={}
d = {}
d["submitted"] = now
d["common_name"] = common_name
d["request_buf"] = request.dump()
@ -69,13 +68,14 @@ def self_enroll(skip_notify=False):
d["user"] = {}
doc = db.certificates.find_one_and_update({
"common_name":d["common_name"]
"common_name": d["common_name"]
}, {
"$set": d,
"$setOnInsert": {
"created": now,
"ip": [],
}},
"ip": []
}
},
upsert=True,
return_document=db.return_new)
@ -98,6 +98,7 @@ def get_common_name_id(cn):
return str(doc["_id"])
def list_revoked(limit=0):
# TODO: sort recent to oldest
for cert_revoked_doc in db.certificates.find({"status": "revoked"}):
@ -108,9 +109,10 @@ def list_revoked(limit=0):
if limit <= 0:
return
# TODO: it should be possible to regex search common_name directly from mongodb
def list_signed(common_name=None):
for cert_doc in db.certificates.find({"status" : "signed"}):
for cert_doc in db.certificates.find({"status": "signed"}):
if common_name:
if common_name.startswith("^"):
if not re.match(common_name, cert_doc["common_name"]):
@ -121,24 +123,28 @@ def list_signed(common_name=None):
cert = x509.Certificate.load(cert_doc["cert_buf"])
yield cert_doc, cert
def list_requests():
for request in db.certificates.find({"status": "csr"}):
csr = CertificationRequest.load(request["request_buf"])
yield csr, request, "." in request["common_name"]
def list_replicas():
"""
Return list of Mongo objects referring to all active replicas
"""
for doc in db.certificates.find({"status" : "signed", "profile.ou": "Gateway"}):
for doc in db.certificates.find({"status": "signed", "profile.ou": "Gateway"}):
yield doc
def get_ca_cert():
fh = open(const.AUTHORITY_CERTIFICATE_PATH, "rb")
server_certificate = asymmetric.load_certificate(fh.read())
fh.close()
return server_certificate
def get_request(id):
if not id:
raise ValueError("Invalid id parameter %s" % id)
@ -146,11 +152,12 @@ def get_request(id):
csr_doc = db.certificates.find_one({"_id": ObjectId(id), "status": "csr"})
if not csr_doc:
raise errors.RequestDoesNotExist("Certificate signing request with id %s does not exist" % id)
raise errors.RequestDoesNotExist("Certificate signing request with id %s does not exist" % id)
csr = CertificationRequest.load(csr_doc["request_buf"])
return csr, csr_doc, pem_armor_csr(csr)
def get_by_serial(serial):
serial_string = "%x" % serial
query = {"serial_number": serial_string}
@ -163,6 +170,7 @@ def get_by_serial(serial):
cert = x509.Certificate.load(cert_doc["cert_buf"])
return cert_doc, cert
def get_signed(mongo_id=False, common_name=False, namespace=const.AUTHORITY_NAMESPACE):
if mongo_id:
@ -189,13 +197,13 @@ def get_revoked(serial):
if isinstance(serial, int):
serial = "%x" % serial
query = {"serial_number":serial, "status": "revoked"}
query = {"serial_number": serial, "status": "revoked"}
cert_doc = db.certificates.find_one(query)
if not cert_doc:
raise errors.CertificateDoesNotExist
cert_pem_buf = pem.armor("CERTIFICATE",cert_doc["cert_buf"])
cert_pem_buf = pem.armor("CERTIFICATE", cert_doc["cert_buf"])
return cert_doc, cert_pem_buf
@ -221,10 +229,9 @@ def store_request(buf, overwrite=False, address="", user="", namespace=const.MAC
if not re.match(const.RE_COMMON_NAME, common_name):
raise ValueError("Invalid common name %s" % repr(common_name))
query = {"common_name": common_name, "status": "csr"}
doc = db.certificates.find_one(query)
d ={}
d = {}
user_object = {}
if doc and not overwrite:
@ -255,7 +262,7 @@ def store_request(buf, overwrite=False, address="", user="", namespace=const.MAC
d["user"] = user_object
doc = db.certificates.find_one_and_update({
"common_name":d["common_name"]
"common_name": d["common_name"]
}, {
"$set": d,
"$setOnInsert": {
@ -267,6 +274,7 @@ def store_request(buf, overwrite=False, address="", user="", namespace=const.MAC
return doc
def revoke(mongo_id, reason, user="root"):
"""
Revoke valid certificate
@ -275,8 +283,8 @@ def revoke(mongo_id, reason, user="root"):
common_name = cert_doc["common_name"]
if reason not in ("key_compromise", "ca_compromise", "affiliation_changed",
"superseded", "cessation_of_operation", "certificate_hold",
"remove_from_crl", "privilege_withdrawn"):
"superseded", "cessation_of_operation", "certificate_hold",
"remove_from_crl", "privilege_withdrawn"):
raise ValueError("Invalid revocation reason %s" % reason)
logger.info("Revoked certificate %s by %s", common_name, user)
@ -288,9 +296,9 @@ def revoke(mongo_id, reason, user="root"):
else:
raise ValueError("No common name or Id specified")
prev = db.certificates.find_one(query)
newValue = { "$set": { "status": "revoked", "revocation_reason": reason, "revoked": datetime.utcnow().replace(tzinfo=pytz.UTC)} }
db.certificates.find_one_and_update(query,newValue)
# prev = db.certificates.find_one(query)
newValue = {"$set": {"status": "revoked", "revocation_reason": reason, "revoked": datetime.utcnow().replace(tzinfo=pytz.UTC)}}
db.certificates.find_one_and_update(query, newValue)
attach_cert = pem_buf, "application/x-pem-file", common_name + ".crt"
@ -299,6 +307,7 @@ def revoke(mongo_id, reason, user="root"):
serial_hex="%x" % cert.serial_number,
common_name=common_name)
def export_crl(pem=True):
builder = CertificateListBuilder(
const.AUTHORITY_CRL_URL,
@ -309,7 +318,7 @@ def export_crl(pem=True):
# Get revoked certificates from database
for cert_revoked_doc in db.certificates.find({"status": "revoked"}):
builder.add_certificate(
int(cert_revoked_doc["serial"][:-4],16),
int(cert_revoked_doc["serial"][:-4], 16),
datetime.utcfromtimestamp(cert_revoked_doc["revoked"]).replace(tzinfo=pytz.UTC),
cert_revoked_doc["revocation_reason"]
)
@ -331,10 +340,11 @@ def delete_request(id, user="root"):
if not doc:
logger.info("Signing request with id %s not found" % (
id))
id))
raise errors.RequestDoesNotExist
res = db.certificates.delete_one(query)
# TODO return if was success or failure
db.certificates.delete_one(query)
logger.info("Rejected signing request %s %s by %s" % (doc["common_name"],
id, user))
@ -345,11 +355,10 @@ def sign(profile, skip_notify=False, overwrite=False, signer=None, namespace=con
if mongo_id:
csr_doc = db.certificates.find_one({"_id": ObjectId(mongo_id)})
csr = CertificationRequest.load(csr_doc["request_buf"])
csr_buf_pem = pem.armor("CERTIFICATE REQUEST",csr_doc["request_buf"])
csr_buf_pem = pem.armor("CERTIFICATE REQUEST", csr_doc["request_buf"])
else:
raise ValueError("ID missing, what CSR to sign")
assert isinstance(csr, CertificationRequest)
csr_pubkey = asymmetric.load_public_key(csr["certification_request_info"]["subject_pk_info"])
@ -371,8 +380,8 @@ def sign(profile, skip_notify=False, overwrite=False, signer=None, namespace=con
if prev:
if overwrite:
newValue = { "$set": { "status": "revoked", "revoked": datetime.utcnow().replace(tzinfo=pytz.UTC), "revocation_reason": "superseded"} }
doc = db.certificates.find_one_and_update(query,newValue,return_document=db.return_new)
newValue = {"$set": {"status": "revoked", "revoked": datetime.utcnow().replace(tzinfo=pytz.UTC), "revocation_reason": "superseded"}}
doc = db.certificates.find_one_and_update(query, newValue, return_document=db.return_new)
overwritten = True
else:
raise FileExistsError("Will not overwrite existing certificate")
@ -382,6 +391,9 @@ def sign(profile, skip_notify=False, overwrite=False, signer=None, namespace=con
ou=profile["ou"]), csr_pubkey)
builder.serial_number = generate_serial()
if csr["signature_algorithm"].hash_algo == const.CERTIFICATE_HASH_ALGORITHM:
builder.hash_algo = const.CERTIFICATE_HASH_ALGORITHM
now = datetime.utcnow().replace(tzinfo=pytz.UTC)
builder.begin_date = now - const.CLOCK_SKEW_TOLERANCE
builder.end_date = now + timedelta(days=profile["lifetime"])
@ -408,7 +420,7 @@ def sign(profile, skip_notify=False, overwrite=False, signer=None, namespace=con
# Write certificate to database
# DER format cert
cert_der_bytes = asymmetric.dump_certificate(end_entity_cert,encoding="der")
cert_der_bytes = asymmetric.dump_certificate(end_entity_cert, encoding="der")
d = {
"common_name": common_name,

View File

@ -10,7 +10,6 @@ else:
import falcon
import click
import logging
import os
import pymongo
import signal
@ -27,14 +26,12 @@ from pinecrypt.server import const, mongolog, mailer, db
from pinecrypt.server.middleware import NormalizeMiddleware, PrometheusEndpoint
from pinecrypt.server.common import cn_to_dn, generate_serial
from pinecrypt.server.mongolog import LogHandler
#from pinecrypt.server.logger import CertidudeLogger
from time import sleep
from wsgiref.simple_server import make_server
#logger = logging.getLogger(__name__)
#logger = CertidudeLogger()
logger = LogHandler()
def graceful_exit(signal_number, stack_frame):
print("Received signal %d, exiting now" % signal_number)
sys.exit(0)
@ -218,7 +215,7 @@ def pinecone_serve_builder():
@click.command("provision", help="Provision keys")
def pinecone_provision():
#First thing init mongo db
# First thing init mongo db
click.echo("Provisioning MongoDB replicaset")
# WTF https://github.com/docker-library/mongo/issues/339
c = pymongo.MongoClient("localhost", 27017)
@ -236,9 +233,8 @@ def pinecone_provision():
config = {"_id": "rs0", "members": [
{"_id": index, "host": "%s:%s" % (ip_port[0], ip_port[1])} for index, ip_port in enumerate(mongo_uri["nodelist"])]}
# config = {"_id":"rs0", "members": [
# {"_id": 0, "host": "127.0.0.1:27017"}]}
# config = {"_id":"rs0", "members": [
# {"_id": 0, "host": "127.0.0.1:27017"}]}
print("Provisioning MongoDB replicaset: %s" % repr(config))
try:
@ -263,6 +259,7 @@ def pinecone_provision():
# https://technet.microsoft.com/en-us/library/aa998840(v=exchg.141).aspx
builder = CertificateBuilder(distinguished_name, public_key)
builder.hash_algo = const.CERTIFICATE_HASH_ALGORITHM
builder.self_signed = True
builder.ca = True
builder.serial_number = generate_serial()

View File

@ -35,8 +35,13 @@ REPLICAS = [j for j in os.getenv("REPLICAS", "").split(",") if j]
if not MONGO_URI:
MONGO_URI = "mongodb://127.0.0.1:27017/default?replicaSet=rs0"
KEY_SIZE = 4096
CURVE_NAME = "secp384r1"
# Are set later, based on key type
KEY_SIZE = None
CURVE_NAME = None
# python CSRbuilder supports right now sha1, sha256 sha512
# CERTIFICATE_HASH_ALGORITHM = 'sha111'
CERTIFICATE_HASH_ALGORITHM = "sha512"
# Kerberos-like clock skew tolerance
CLOCK_SKEW_TOLERANCE = timedelta(minutes=5)
@ -76,7 +81,7 @@ AUTHORITY_ORGANIZATION = os.getenv("AUTHORITY_ORGANIZATION")
AUTHORITY_LIFETIME_DAYS = 20 * 365
# Advertise following IP addresses via DNS record
ADVERTISE_ADDRESS = [j for j in os.getenv("ADVERTISE_ADDRESS", "").split(",") if j]
ADVERTISE_ADDRESS = [j for j in os.getenv("ADVERTISE_ADDRESS", "").split(",") if j]
if not ADVERTISE_ADDRESS:
ADVERTISE_ADDRESS = set()
for fam, _, _, _, addrs in socket.getaddrinfo(FQDN, None):
@ -100,6 +105,12 @@ AUTHORITY_OCSP_URL = "http://%s/api/ocsp/" % AUTHORITY_NAMESPACE
AUTHORITY_OCSP_DISABLED = os.getenv("AUTHORITY_OCSP_DISABLED", False)
AUTHORITY_KEYTYPE = getenv_in("AUTHORITY_KEYTYPE", "rsa", "ec")
if AUTHORITY_KEYTYPE == "rsa":
KEY_SIZE = 4096
if AUTHORITY_KEYTYPE == "ec":
CURVE_NAME = "secp384r1"
# Tokens
TOKEN_URL = "https://%(authority_name)s/#action=enroll&title=dev.lan&token=%(token)s&subject=%(subject_username)s&protocols=%(protocols)s"
TOKEN_LIFETIME = 3600 * 24