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:
parent
fa27253b50
commit
7012f5b365
@ -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
34
certidude/api/bundle.py
Normal 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"))
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
|
@ -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 }}
|
||||||
|
@ -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 }}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user