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

Make user certificate enrollment configurable

This commit is contained in:
Lauri Võsandi 2016-04-01 01:55:51 +03:00
parent fa27253b50
commit 7012f5b365
7 changed files with 105 additions and 18 deletions

View File

@ -56,6 +56,8 @@ class SessionResource(object):
[req.context.get("remote_addr") in j [req.context.get("remote_addr") in j
for j in config.REQUEST_SUBNETS]), for j in config.REQUEST_SUBNETS]),
authority = dict( authority = dict(
user_certificate_enrollment=config.USER_CERTIFICATE_ENROLLMENT,
user_mutliple_certificates=config.USER_MULTIPLE_CERTIFICATES,
outbox = config.OUTBOX, outbox = config.OUTBOX,
certificate = authority.certificate, certificate = authority.certificate,
events = config.PUSH_EVENT_SOURCE % config.PUSH_TOKEN, events = config.PUSH_EVENT_SOURCE % config.PUSH_TOKEN,
@ -103,18 +105,6 @@ class StaticResource(object):
resp.status = falcon.HTTP_404 resp.status = falcon.HTTP_404
resp.body = "File '%s' not found" % req.path resp.body = "File '%s' not found" % req.path
class BundleResource(object):
@login_required
def on_get(self, req, resp):
common_name = req.context["user"].mail
logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user"))
resp.set_header("Content-Type", "application/x-pkcs12")
resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii"))
resp.body, cert = authority.generate_pkcs12_bundle(common_name,
owner=req.context.get("user"))
import ipaddress import ipaddress
class NormalizeMiddleware(object): class NormalizeMiddleware(object):
@ -129,7 +119,7 @@ class NormalizeMiddleware(object):
def certidude_app(): def certidude_app():
from certidude import config from certidude import config
from .bundle import BundleResource
from .revoked import RevocationListResource from .revoked import RevocationListResource
from .signed import SignedCertificateListResource, SignedCertificateDetailResource from .signed import SignedCertificateListResource, SignedCertificateDetailResource
from .request import RequestListResource, RequestDetailResource from .request import RequestListResource, RequestDetailResource
@ -143,7 +133,6 @@ def certidude_app():
# Certificate authority API calls # Certificate authority API calls
app.add_route("/api/ocsp/", CertificateStatusResource()) app.add_route("/api/ocsp/", CertificateStatusResource())
app.add_route("/api/bundle/", BundleResource())
app.add_route("/api/certificate/", CertificateAuthorityResource()) app.add_route("/api/certificate/", CertificateAuthorityResource())
app.add_route("/api/revoked/", RevocationListResource()) app.add_route("/api/revoked/", RevocationListResource())
app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource()) app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource())
@ -156,6 +145,10 @@ def certidude_app():
app.add_route("/api/lease/", LeaseResource()) app.add_route("/api/lease/", LeaseResource())
app.add_route("/api/whois/", WhoisResource()) app.add_route("/api/whois/", WhoisResource())
# Optional user enrollment API call
if config.USER_CERTIFICATE_ENROLLMENT:
app.add_route("/api/bundle/", BundleResource())
log_handlers = [] log_handlers = []
if config.LOGGING_BACKEND == "sql": if config.LOGGING_BACKEND == "sql":
from certidude.mysqllog import LogHandler from certidude.mysqllog import LogHandler

34
certidude/api/bundle.py Normal file
View File

@ -0,0 +1,34 @@
import logging
import hashlib
from certidude import config, authority
from certidude.auth import login_required
logger = logging.getLogger("api")
KEYWORDS = (
("Android", "android"),
("iPhone", "iphone"),
("iPad", "ipad"),
("Ubuntu", "ubuntu"),
)
class BundleResource(object):
@login_required
def on_get(self, req, resp):
common_name = req.context["user"].name
if config.USER_MULTIPLE_CERTIFICATES:
for key, value in KEYWORDS:
if key in req.user_agent:
device_identifier = value
break
else:
device_identifier = "unknown-device"
common_name += "@" + device_identifier + "-" + \
hashlib.sha256(req.user_agent).hexdigest()[:8]
logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user"))
resp.set_header("Content-Type", "application/x-pkcs12")
resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii"))
resp.body, cert = authority.generate_pkcs12_bundle(common_name,
owner=req.context.get("user"))

View File

@ -26,10 +26,16 @@ def publish_certificate(func):
cert = func(csr, *args, **kwargs) cert = func(csr, *args, **kwargs)
assert isinstance(cert, Certificate), "notify wrapped function %s returned %s" % (func, type(cert)) assert isinstance(cert, Certificate), "notify wrapped function %s returned %s" % (func, type(cert))
if cert.given_name and cert.surname and cert.email_address:
recipient = "%s %s <%s>" % (cert.given_name, cert.surname, cert.email_address)
elif cert.email_address:
recipient = cert.email_address
else:
recipient = None
mailer.send( mailer.send(
"certificate-signed.md", "certificate-signed.md",
to= "%s %s <%s>" % (cert.given_name, cert.surname, cert.email_address) if to=recipient,
cert.given_name and cert.surname else cert.email_address,
attachments=(cert,), attachments=(cert,),
certificate=cert) certificate=cert)

View File

@ -39,6 +39,12 @@ SIGNED_DIR = cp.get("authority", "signed dir")
REVOKED_DIR = cp.get("authority", "revoked dir") REVOKED_DIR = cp.get("authority", "revoked dir")
OUTBOX = cp.get("authority", "outbox") OUTBOX = cp.get("authority", "outbox")
USER_CERTIFICATE_ENROLLMENT = {
"forbidden": False, "single allowed": True, "multiple allowed": True }[
cp.get("authority", "user certificate enrollment")]
USER_MULTIPLE_CERTIFICATES = {
"forbidden": False, "single allowed": False, "multiple allowed": True }[
cp.get("authority", "user certificate enrollment")]
CERTIFICATE_BASIC_CONSTRAINTS = "CA:FALSE" CERTIFICATE_BASIC_CONSTRAINTS = "CA:FALSE"
CERTIFICATE_KEY_USAGE_FLAGS = "digitalSignature,keyEncipherment" CERTIFICATE_KEY_USAGE_FLAGS = "digitalSignature,keyEncipherment"

View File

@ -469,11 +469,34 @@ output += " (";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"user")),"name"), env.opts.autoescape); output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"user")),"name"), env.opts.autoescape);
output += ") settings</h2>\n\n<p>Mails will be sent to: "; output += ") settings</h2>\n\n<p>Mails will be sent to: ";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"user")),"mail"), env.opts.autoescape); output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"user")),"mail"), env.opts.autoescape);
output += "</p>\n\n<p>You can click <a href=\"/api/bundle/\">here</a> to generate bundle\nfor current user account.</p>\n\n"; output += "</p>\n\n";
if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"user_certificate_enrollment")) {
output += "\n<p>You can click <a href=\"/api/bundle/\">here</a> to generate bundle\nfor current user account.</p>\n";
;
}
output += "\n\n";
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")) { if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")) {
output += "\n\n<h2>Authority certificate</h2>\n\n<p>Several things such as CRL location and e-mails are hardcoded into\nthe <a href=\"/api/certificate\">certificate</a> and\nas such require complete reset of X509 infrastructure if some of them needs to be changed:</p>\n\n<p>Mails will appear from: "; output += "\n\n<h2>Authority certificate</h2>\n\n<p>Several things such as CRL location and e-mails are hardcoded into\nthe <a href=\"/api/certificate\">certificate</a> and\nas such require complete reset of X509 infrastructure if some of them needs to be changed:</p>\n\n<p>Mails will appear from: ";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"certificate")),"email_address"), env.opts.autoescape); output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"certificate")),"email_address"), env.opts.autoescape);
output += "</p>\n\n\n<h2>Authority settings</h2>\n\n<p>These can be reconfigured via /etc/certidude/server.conf on the server.</p>\n\n<p>Outgoing mail server:\n"; output += "</p>\n\n\n<h2>Authority settings</h2>\n\n<p>These can be reconfigured via /etc/certidude/server.conf on the server.</p>\n\n<p>User certificate enrollment:\n";
if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"user_certificate_enrollment")) {
output += "\n ";
if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"user_mutliple_certificates")) {
output += "\n multiple\n ";
;
}
else {
output += "\n single\n ";
;
}
output += "\nallowed\n";
;
}
else {
output += "\nforbidden\n";
;
}
output += "\n</p>\n\n<p>Outgoing mail server:\n";
if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"outbox")) { if(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"outbox")) {
output += "\n "; output += "\n ";
output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"outbox"), env.opts.autoescape); output += runtime.suppressValue(runtime.memberLookup((runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "session")),"authority")),"outbox"), env.opts.autoescape);

View File

@ -4,8 +4,10 @@
<p>Mails will be sent to: {{ session.user.mail }}</p> <p>Mails will be sent to: {{ session.user.mail }}</p>
{% if session.authority.user_certificate_enrollment %}
<p>You can click <a href="/api/bundle/">here</a> to generate bundle <p>You can click <a href="/api/bundle/">here</a> to generate bundle
for current user account.</p> for current user account.</p>
{% endif %}
{% if session.authority %} {% if session.authority %}
@ -22,6 +24,19 @@ as such require complete reset of X509 infrastructure if some of them needs to b
<p>These can be reconfigured via /etc/certidude/server.conf on the server.</p> <p>These can be reconfigured via /etc/certidude/server.conf on the server.</p>
<p>User certificate enrollment:
{% if session.authority.user_certificate_enrollment %}
{% if session.authority.user_mutliple_certificates %}
multiple
{% else %}
single
{% endif %}
allowed
{% else %}
forbidden
{% endif %}
</p>
<p>Outgoing mail server: <p>Outgoing mail server:
{% if session.authority.outbox %} {% if session.authority.outbox %}
{{ session.authority.outbox }} {{ session.authority.outbox }}

View File

@ -71,6 +71,16 @@ long poll = {{ push_server }}/lp/%s
publish = {{ push_server }}/pub?id=%s publish = {{ push_server }}/pub?id=%s
[authority] [authority]
# User certificate enrollment specifies whether logged in users are allowed to
# request bundles. In case of 'single allowed' the common name of the
# certificate is set to username, this should work well with REMOTE_USER
# enabled web apps running behind Apache/nginx.
# In case of 'multiple allowed' the common name is set to username@device-identifier.
;user certificate enrollment = forbidden
;user certificate enrollment = single allowed
user certificate enrollment = multiple allowed
private key path = {{ ca_key }} private key path = {{ ca_key }}
certificate path = {{ ca_crt }} certificate path = {{ ca_crt }}