diff --git a/certidude/api/__init__.py b/certidude/api/__init__.py index f1de85a..9d657d9 100644 --- a/certidude/api/__init__.py +++ b/certidude/api/__init__.py @@ -100,6 +100,7 @@ class SessionResource(AuthorityHandler): except IOError: signer_username = None + # TODO: dedup yield dict( serial = "%x" % cert.serial_number, organizational_unit = cert.subject.native.get("organizational_unit_name"), diff --git a/certidude/api/signed.py b/certidude/api/signed.py index 2b42cc6..65bc5cc 100644 --- a/certidude/api/signed.py +++ b/certidude/api/signed.py @@ -5,7 +5,7 @@ import json import hashlib from certidude.auth import login_required, authorize_admin from certidude.decorators import csrf_protection -from xattr import getxattr +from xattr import listxattr, getxattr from .utils import AuthorityHandler logger = logging.getLogger(__name__) @@ -34,14 +34,28 @@ class SignedCertificateDetailResource(AuthorityHandler): signer_username = getxattr(path, "user.signature.username").decode("ascii") except IOError: signer_username = None + + attributes = {} + for key in listxattr(path): + if key.startswith(b"user.machine."): + attributes[key[13:].decode("ascii")] = getxattr(path, key).decode("ascii") + + # TODO: dedup resp.body = json.dumps(dict( common_name = cn, signer = signer_username, - serial_number = "%x" % cert.serial_number, + serial = "%x" % cert.serial_number, organizational_unit = cert.subject.native.get("organizational_unit_name"), 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())) + sha256sum = hashlib.sha256(buf).hexdigest(), + attributes = attributes or None, + lease = None, + extensions = dict([ + (e["extn_id"].native, e["extn_value"].native) + for e in cert["tbs_certificate"]["extensions"] + if e["extn_value"] in ("extended_key_usage",)]) + )) logger.debug("Served certificate %s to %s as application/json", cn, req.context.get("remote_addr")) else: diff --git a/certidude/api/token.py b/certidude/api/token.py index 13ce10a..1cc40f5 100644 --- a/certidude/api/token.py +++ b/certidude/api/token.py @@ -41,7 +41,7 @@ class TokenResource(AuthorityHandler): common_name = csr["certification_request_info"]["subject"].native["common_name"] assert common_name == username or common_name.startswith(username + "@"), "Invalid common name %s" % common_name try: - _, resp.body = self.authority._sign(csr, body) + _, resp.body = self.authority._sign(csr, body, profile="default") resp.set_header("Content-Type", "application/x-pem-file") logger.info("Autosigned %s as proven by token ownership", common_name) except FileExistsError: diff --git a/certidude/authority.py b/certidude/authority.py index 1769540..52a4859 100644 --- a/certidude/authority.py +++ b/certidude/authority.py @@ -68,7 +68,7 @@ def self_enroll(): from certidude import authority from certidude.common import drop_privileges drop_privileges() - authority.sign(common_name, skip_push=True, overwrite=True) + authority.sign(common_name, skip_push=True, overwrite=True, profile="srv") sys.exit(0) else: os.waitpid(pid, 0) @@ -307,7 +307,7 @@ def delete_request(common_name): config.LONG_POLL_PUBLISH % hashlib.sha256(buf).hexdigest(), headers={"User-Agent": "Certidude API"}) -def sign(common_name, skip_notify=False, skip_push=False, overwrite=False, profile="default", signer=None): +def sign(common_name, skip_notify=False, skip_push=False, overwrite=False, profile=None, signer=None): """ Sign certificate signing request by it's common name """ @@ -325,12 +325,12 @@ def sign(common_name, skip_notify=False, skip_push=False, overwrite=False, profi os.unlink(req_path) return cert, buf -def _sign(csr, buf, skip_notify=False, skip_push=False, overwrite=False, profile="default", signer=None): +def _sign(csr, buf, skip_notify=False, skip_push=False, overwrite=False, profile=None, signer=None): # TODO: CRLDistributionPoints, OCSP URL, Certificate URL if profile not in config.PROFILES: raise ValueError("Invalid profile supplied '%s'" % profile) - assert buf.startswith(b"-----BEGIN CERTIFICATE REQUEST-----") + assert buf.startswith(b"-----BEGIN ") assert isinstance(csr, CertificationRequest) csr_pubkey = asymmetric.load_public_key(csr["certification_request_info"]["subject_pk_info"]) common_name = csr["certification_request_info"]["subject"].native["common_name"] diff --git a/certidude/cli.py b/certidude/cli.py index 8f84219..bdfeab0 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -956,7 +956,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, os.system("apt-get install -qq -y cython3 python3-dev python3-mimeparse \ python3-markdown python3-pyxattr python3-jinja2 python3-cffi \ software-properties-common libsasl2-modules-gssapi-mit npm nodejs \ - libkrb5-dev libldap2-dev libsasl2-dev gawk libncurses5-dev") + libkrb5-dev libldap2-dev libsasl2-dev gawk libncurses5-dev rsync") os.system("pip3 install -q --upgrade gssapi falcon humanize ipaddress simplepam") os.system("pip3 install -q --pre --upgrade python-ldap") @@ -1308,11 +1308,12 @@ def certidude_list(verbose, show_key_type, show_extensions, show_path, show_sign @click.command("sign", help="Sign certificate") @click.argument("common_name") +@click.option("--profile", "-p", default=None, help="Profile") @click.option("--overwrite", "-o", default=False, is_flag=True, help="Revoke valid certificate with same CN") -def certidude_sign(common_name, overwrite): +def certidude_sign(common_name, overwrite, profile): from certidude import authority drop_privileges() - cert = authority.sign(common_name, overwrite=overwrite) + cert = authority.sign(common_name, overwrite=overwrite, profile=profile) @click.command("revoke", help="Revoke certificate") diff --git a/certidude/static/views/lease.html b/certidude/static/views/lease.html index 2795e77..51348bd 100644 --- a/certidude/static/views/lease.html +++ b/certidude/static/views/lease.html @@ -1,4 +1,4 @@ - + Last seen at