mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 17:39:12 +00:00 
			
		
		
		
	Make user certificate enrollment configurable
This commit is contained in:
		| @@ -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 }} | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user