1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-12-23 00:25:18 +00:00

Migrate authority setup from PyOpenSSL to cryptography.io

This commit is contained in:
Lauri Võsandi 2016-03-29 19:29:06 +03:00
parent af60fd8047
commit c644b065ef

View File

@ -17,25 +17,15 @@ from configparser import ConfigParser
from certidude import constants
from certidude.helpers import certidude_request_certificate
from certidude.common import expand_paths, ip_address, ip_network
from datetime import datetime
from datetime import datetime, timedelta
from humanize import naturaltime
from jinja2 import Environment, PackageLoader
from time import sleep
from setproctitle import setproctitle
from OpenSSL import crypto
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
# Big fat warning:
# m2crypto overflows around 2030 because on 32-bit systems
# m2crypto does not support hardware engine support (?)
# m2crypto CRL object is pretty much useless
# pyopenssl has no straight-forward methods for getting RSA key modulus
# pyopenssl 0.13 bundled with Ubuntu 14.04 has no get_extension_count() for X509Req objects
assert hasattr(crypto.X509Req(), "get_extensions"), "You're running too old version of pyopenssl, upgrade to 0.15+"
# http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml
# https://kjur.github.io/jsrsasign/
# keyUsage, extendedKeyUsage - https://www.openssl.org/docs/apps/x509v3_config.html
@ -778,16 +768,20 @@ def certidude_setup_production(username, hostname, push_server, nginx_config, uw
@click.option("--revocation-list-lifetime", default=20*60, help="Revocation list lifetime in days, 1200 seconds (20 minutes) by default")
@click.option("--organization", "-o", default=None, help="Company or organization name")
@click.option("--organizational-unit", "-ou", default=None)
@click.option("--pkcs11", default=False, is_flag=True, help="Use PKCS#11 token instead of files")
@click.option("--revoked-url", default=None, help="CRL distribution URL")
@click.option("--certificate-url", default=None, help="Authority certificate URL")
@click.option("--ocsp-responder-url", default=None, help="OCSP responder URL")
@click.option("--push-server", default="http://push.%s" % constants.DOMAIN, help="Push server, http://push.%s by default" % constants.DOMAIN)
@click.option("--email-address", default="certidude@" + FQDN, help="E-mail address of the CA")
@click.option("--directory", default=os.path.join("/var/lib/certidude", FQDN), help="Directory for authority files, /var/lib/certidude/%s/ by default" % FQDN)
@click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags")
@click.option("--outbox", default="smtp://smtp.%s" % constants.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % constants.DOMAIN)
def certidude_setup_authority(parent, country, state, locality, organization, organizational_unit, common_name, directory, certificate_lifetime, authority_lifetime, revocation_list_lifetime, pkcs11, revoked_url, certificate_url, ocsp_responder_url, push_server, email_address, outbox, server_flags):
def certidude_setup_authority(parent, country, state, locality, organization, organizational_unit, common_name, directory, certificate_lifetime, authority_lifetime, revocation_list_lifetime, revoked_url, certificate_url, push_server, email_address, outbox, server_flags):
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# Make sure common_name is valid
if not re.match(r"^[\.\-_a-zA-Z0-9]+$", common_name):
@ -804,11 +798,11 @@ def certidude_setup_authority(parent, country, state, locality, organization, or
click.echo("Generating 4096-bit RSA key...")
if pkcs11:
raise NotImplementedError("Hardware token support not yet implemented!")
else:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 4096)
key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
backend=default_backend()
)
if not revoked_url:
revoked_url = "http://%s/api/revoked/" % common_name
@ -819,85 +813,59 @@ def certidude_setup_authority(parent, country, state, locality, organization, or
ca_key = os.path.join(directory, "ca_key.pem")
ca_crt = os.path.join(directory, "ca_crt.pem")
ca = crypto.X509()
ca.set_version(2) # This corresponds to X.509v3
ca.set_serial_number(1)
ca.get_subject().CN = common_name
if country:
ca.get_subject().C = country
if state:
ca.get_subject().ST = state
if locality:
ca.get_subject().L = locality
if organization:
ca.get_subject().O = organization
if organizational_unit:
ca.get_subject().OU = organizational_unit
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(authority_lifetime * 24 * 60 * 60)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
# add_extensions shall be called only once and
# there has to be only one subjectAltName!
ca.add_extensions([
crypto.X509Extension(
b"basicConstraints",
True,
b"CA:TRUE"),
crypto.X509Extension(
b"keyUsage",
True,
b"digitalSignature, keyCertSign, cRLSign"),
crypto.X509Extension(
b"subjectKeyIdentifier",
False,
b"hash",
subject = ca),
crypto.X509Extension(
b"subjectAltName",
False,
(u"DNS: %s, email: %s" % (common_name, email_address)).encode("ascii"))
subject = issuer = x509.Name([
x509.NameAttribute(o, value) for o, value in (
(NameOID.COUNTRY_NAME, country),
(NameOID.STATE_OR_PROVINCE_NAME, state),
(NameOID.LOCALITY_NAME, locality),
(NameOID.ORGANIZATION_NAME, organization),
(NameOID.COMMON_NAME, common_name),
) if value
])
builder = x509.CertificateBuilder(
).subject_name(subject
).issuer_name(issuer
).public_key(key.public_key()
).not_valid_before(datetime.utcnow()
).not_valid_after(
datetime.utcnow() + timedelta(days=authority_lifetime)
).serial_number(1
).add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True,
).add_extension(x509.KeyUsage(
digital_signature=True,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False), critical=True,
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName(common_name),
x509.RFC822Name(email_address)
]),
critical=False,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False
).add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()),
critical=False
)
if server_flags:
ca.add_extensions([
crypto.X509Extension(
b"extendedKeyUsage",
False,
b"serverAuth,1.3.6.1.5.5.8.2.2")
])
builder = builder.add_extension(x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.CLIENT_AUTH,
ObjectIdentifier("1.3.6.1.5.5.8.2.2")]))
ca.add_extensions([
crypto.X509Extension(
b"authorityKeyIdentifier",
False,
b"keyid:always",
issuer = ca)
])
cert = builder.sign(key, hashes.SHA512(), default_backend())
if ocsp_responder_url:
raise NotImplementedError()
"""
ocsp_responder_url = "http://%s/api/ocsp/" % common_name
authority_info_access = "OCSP;URI:%s" % ocsp_responder_url
ca.add_extensions([
crypto.X509Extension(
b"authorityInfoAccess",
False,
authority_info_access.encode("ascii"))
])
"""
click.echo("Signing %s..." % ca.get_subject())
# openssl x509 -in ca_crt.pem -outform DER | sha256sum
# openssl x509 -fingerprint -in ca_crt.pem
ca.sign(key, "sha512")
click.echo("Signing %s..." % cert.subject)
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
os.setgid(gid)
@ -918,12 +886,16 @@ def certidude_setup_authority(parent, country, state, locality, organization, or
with open(certidude_conf, "w") as fh:
fh.write(env.get_template("certidude.conf").render(vars()))
with open(ca_crt, "wb") as fh:
fh.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
fh.write(cert.public_bytes(serialization.Encoding.PEM))
# Set permission bits to 600
os.umask(0o177)
with open(ca_key, "wb") as fh:
fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
fh.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption() # TODO: Implement passphrase
))
click.echo()
click.echo("Use following commands to inspect the newly created files:")