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 | ||||
|                     for j in config.REQUEST_SUBNETS]), | ||||
|             authority = dict( | ||||
|                 user_certificate_enrollment=config.USER_CERTIFICATE_ENROLLMENT, | ||||
|                 user_mutliple_certificates=config.USER_MULTIPLE_CERTIFICATES, | ||||
|                 outbox = config.OUTBOX, | ||||
|                 certificate = authority.certificate, | ||||
|                 events = config.PUSH_EVENT_SOURCE % config.PUSH_TOKEN, | ||||
| @@ -103,18 +105,6 @@ class StaticResource(object): | ||||
|             resp.status = falcon.HTTP_404 | ||||
|             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 | ||||
|  | ||||
| class NormalizeMiddleware(object): | ||||
| @@ -129,7 +119,7 @@ class NormalizeMiddleware(object): | ||||
|  | ||||
| def certidude_app(): | ||||
|     from certidude import config | ||||
|  | ||||
|     from .bundle import BundleResource | ||||
|     from .revoked import RevocationListResource | ||||
|     from .signed import SignedCertificateListResource, SignedCertificateDetailResource | ||||
|     from .request import RequestListResource, RequestDetailResource | ||||
| @@ -143,7 +133,6 @@ def certidude_app(): | ||||
|  | ||||
|     # Certificate authority API calls | ||||
|     app.add_route("/api/ocsp/", CertificateStatusResource()) | ||||
|     app.add_route("/api/bundle/", BundleResource()) | ||||
|     app.add_route("/api/certificate/", CertificateAuthorityResource()) | ||||
|     app.add_route("/api/revoked/", RevocationListResource()) | ||||
|     app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource()) | ||||
| @@ -156,6 +145,10 @@ def certidude_app(): | ||||
|     app.add_route("/api/lease/", LeaseResource()) | ||||
|     app.add_route("/api/whois/", WhoisResource()) | ||||
|  | ||||
|     # Optional user enrollment API call | ||||
|     if config.USER_CERTIFICATE_ENROLLMENT: | ||||
|         app.add_route("/api/bundle/", BundleResource()) | ||||
|  | ||||
|     log_handlers = [] | ||||
|     if config.LOGGING_BACKEND == "sql": | ||||
|         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) | ||||
|         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( | ||||
|             "certificate-signed.md", | ||||
|             to= "%s %s <%s>" % (cert.given_name, cert.surname, cert.email_address) if | ||||
|                 cert.given_name and cert.surname else cert.email_address, | ||||
|             to=recipient, | ||||
|             attachments=(cert,), | ||||
|             certificate=cert) | ||||
|  | ||||
|   | ||||
| @@ -39,6 +39,12 @@ SIGNED_DIR = cp.get("authority", "signed dir") | ||||
| REVOKED_DIR = cp.get("authority", "revoked dir") | ||||
| 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_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 += ") 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 += "</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")) { | ||||
| 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 += "</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")) { | ||||
| output += "\n    "; | ||||
| 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> | ||||
|  | ||||
| {% if session.authority.user_certificate_enrollment %} | ||||
| <p>You can click <a href="/api/bundle/">here</a> to generate bundle | ||||
| for current user account.</p> | ||||
| {% endif %} | ||||
|  | ||||
| {% 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>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: | ||||
| {% if session.authority.outbox %} | ||||
|     {{ session.authority.outbox }} | ||||
|   | ||||
| @@ -71,6 +71,16 @@ long poll = {{ push_server }}/lp/%s | ||||
| publish = {{ push_server }}/pub?id=%s | ||||
|  | ||||
| [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 }} | ||||
| certificate path = {{ ca_crt }} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user