diff --git a/certidude/cli.py b/certidude/cli.py index f621a69..30a7a51 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -777,13 +777,14 @@ def certidude_setup_production(username, hostname, push_server, nginx_config, uw @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("--crl-distribution-url", default=None, help="CRL distribution URL") +@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="", help="Streaming nginx push server") +@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/ by default") @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, crl_distribution_url, ocsp_responder_url, push_server, email_address, outbox): +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): # Make sure common_name is valid if not re.match(r"^[\.\-_a-zA-Z0-9]+$", common_name): @@ -806,14 +807,14 @@ def certidude_setup_authority(parent, country, state, locality, organization, or key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, 4096) - if not crl_distribution_url: - crl_distribution_url = "http://%s/api/revoked/" % common_name + if not revoked_url: + revoked_url = "http://%s/api/revoked/" % common_name + if not certificate_url: + certificate_url = "http://%s/api/certificate/" % common_name # File paths ca_key = os.path.join(directory, "ca_key.pem") ca_crt = os.path.join(directory, "ca_crt.pem") - ca_crl = os.path.join(directory, "ca_crl.pem") - crl_distribution_points = "URI:%s" % crl_distribution_url ca = crypto.X509() ca.set_version(2) # This corresponds to X.509v3 @@ -846,7 +847,7 @@ def certidude_setup_authority(parent, country, state, locality, organization, or crypto.X509Extension( b"keyUsage", True, - b"keyCertSign, cRLSign"), + b"digitalSignature, keyCertSign, cRLSign"), crypto.X509Extension( b"extendedKeyUsage", False, @@ -856,10 +857,6 @@ def certidude_setup_authority(parent, country, state, locality, organization, or False, b"hash", subject = ca), - crypto.X509Extension( - b"crlDistributionPoints", - False, - crl_distribution_points.encode("ascii")), crypto.X509Extension( b"subjectAltName", False, @@ -905,18 +902,10 @@ def certidude_setup_authority(parent, country, state, locality, organization, or # Create subdirectories with 770 permissions os.umask(0o007) - for subdir in ("signed", "requests", "revoked"): + for subdir in ("signed", "requests", "revoked", "expired"): if not os.path.exists(os.path.join(directory, subdir)): os.mkdir(os.path.join(directory, subdir)) - # Create CRL and serial file with 644 permissions - os.umask(0o133) - with open(ca_crl, "wb") as fh: - crl = crypto.CRL() - fh.write(crl.export(ca, key, days=revocation_list_lifetime)) - with open(os.path.join(directory, "serial"), "w") as fh: - fh.write("1") - # Set permission bits to 640 os.umask(0o137) with open(certidude_conf, "w") as fh: @@ -932,7 +921,6 @@ def certidude_setup_authority(parent, country, state, locality, organization, or click.echo() click.echo("Use following commands to inspect the newly created files:") click.echo() - click.echo(" openssl crl -inform PEM -text -noout -in %s | less" % ca_crl) click.echo(" openssl x509 -text -noout -in %s | less" % ca_crt) click.echo(" openssl rsa -check -in %s" % ca_key) click.echo(" openssl verify -CAfile %s %s" % (ca_crt, ca_crt)) diff --git a/certidude/config.py b/certidude/config.py index 01087df..8da160e 100644 --- a/certidude/config.py +++ b/certidude/config.py @@ -39,10 +39,13 @@ SIGNED_DIR = cp.get("authority", "signed dir") REVOKED_DIR = cp.get("authority", "revoked dir") OUTBOX = cp.get("authority", "outbox") + CERTIFICATE_BASIC_CONSTRAINTS = "CA:FALSE" CERTIFICATE_KEY_USAGE_FLAGS = "digitalSignature,keyEncipherment" CERTIFICATE_EXTENDED_KEY_USAGE_FLAGS = "clientAuth" CERTIFICATE_LIFETIME = int(cp.get("signature", "certificate lifetime")) +CERTIFICATE_AUTHORITY_URL = cp.get("signature", "certificate url") +CERTIFICATE_CRL_URL = cp.get("signature", "revoked url") REVOCATION_LIST_LIFETIME = int(cp.get("signature", "revocation list lifetime")) diff --git a/certidude/signer.py b/certidude/signer.py index c098321..8b7c16d 100644 --- a/certidude/signer.py +++ b/certidude/signer.py @@ -14,7 +14,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.serialization import Encoding from datetime import datetime, timedelta -from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID +from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID, AuthorityInformationAccessOID import random DN_WHITELIST = NameOID.COMMON_NAME, NameOID.GIVEN_NAME, NameOID.SURNAME, \ @@ -49,7 +49,15 @@ def raw_sign(private_key, ca_cert, request, basic_constraints, lifetime, key_usa b"authorityKeyIdentifier", False, b"keyid:always", - issuer = ca_cert) + issuer = ca_cert), + crypto.X509Extension( + b"authorityInfoAccess", + False, + ("caIssuers;URI: %s" % config.CERTIFICATE_AUTHORITY_URL).encode("ascii")), + crypto.X509Extension( + b"crlDistributionPoints", + False, + ("URI: %s" % config.CERTIFICATE_CRL_URL).encode("ascii")) ]) @@ -101,7 +109,7 @@ def raw_sign(private_key, ca_cert, request, basic_constraints, lifetime, key_usa # Generate random serial cert.set_serial_number(random.randint(SERIAL_MIN, SERIAL_MAX)) - cert.sign(private_key, 'sha256') + cert.sign(private_key, 'sha512') return cert @@ -175,6 +183,26 @@ class SignHandler(asynchat.async_chat): ).add_extension( x509.SubjectKeyIdentifier.from_public_key(request.public_key()), critical=False + ).add_extension( + x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier( + config.CERTIFICATE_AUTHORITY_URL) + ) + ]), + critical=False + ).add_extension( + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + config.CERTIFICATE_CRL_URL)], + relative_name=None, + crl_issuer=None, + reasons=None) + ]), + critical=False ).add_extension( x509.AuthorityKeyIdentifier.from_issuer_public_key( self.server.certificate.public_key()), diff --git a/certidude/templates/certidude.conf b/certidude/templates/certidude.conf index bbfa228..da1e348 100644 --- a/certidude/templates/certidude.conf +++ b/certidude/templates/certidude.conf @@ -1,26 +1,49 @@ [authentication] +# The authentiction backend specifies how the user is authenticated, +# in case of 'pam' simplepam.authenticate is used to authenticate against +# sshd PAM service. In case of 'kerberos' SPNEGO is used to authenticate +# user against eg. Active Directory or Samba4. + backends = pam -#backends = kerberos -#backends = ldap -#backends = kerberos ldap -#backends = kerberos pam +;backends = kerberos +;backends = ldap +;backends = kerberos ldap +;backends = kerberos pam [accounts] +# The accounts backend specifies how the user's given name, surname and e-mail +# address are looked up. In case of 'posix' basically 'getent passwd' is performed, +# in case of 'ldap' a search is performed on LDAP server specified in /etc/ldap/ldap.conf +# with Kerberos credential cache initialized at path specified by 'ldap gssapi credential cache' + backend = posix -#backend = ldap +;backend = ldap +ldap gssapi credential cache = /run/certidude/krb5cc [authorization] +# The authorization backend specifies how the users are authorized. +# In case of 'posix' simply group membership is asserted, +# in case of 'ldap' search filter with username as placeholder is applied. + backend = posix -#backend = ldap -ldap gssapi credential cache = /run/certidude/krb5cc -ldap computer filter = (&(objectclass=user)(objectclass=computer)(samaccountname=%s)) -ldap user filter = (&(objectclass=user)(objectclass=person)(samaccountname=%s)) -ldap admins filter = (&(memberOf=cn=Domain Admins,cn=Users,dc=example,dc=com)(samaccountname=%s)) posix user group = users posix admin group = sudo + +;backend = ldap +ldap computer filter = (&(objectclass=user)(objectclass=computer)(samaccountname=%s)) +ldap user filter = (&(objectclass=user)(objectclass=person)(samaccountname=%s)) +ldap admin filter = (&(memberOf=cn=Domain Admins,cn=Users,dc=example,dc=com)(samaccountname=%s)) + +# Users are allowed to log in from user subnets user subnets = 0.0.0.0/0 + +# Authority administrators are allowed to sign and revoke certificates from these subnets admin subnets = 0.0.0.0/0 + +# Certificate signing requests are allowed to be submitted from these subnets request subnets = 0.0.0.0/0 + +# Certificates are automatically signed for these subnets autosign subnets = 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [logging] @@ -39,14 +62,18 @@ database = sqlite://{{ directory }}/db.sqlite [signature] certificate lifetime = 1825 revocation list lifetime = 1 +certificate url = {{ certificate_url }} +revoked url = {{ revoked_url }} [push] -server = +server = {{ push_server }} [authority] private key path = {{ ca_key }} certificate path = {{ ca_crt }} + requests dir = {{ directory }}/requests/ signed dir = {{ directory }}/signed/ revoked dir = {{ directory }}/revoked/ +expired dir = {{ directory }}/expired/ outbox = {{ outbox }}