mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-31 19:00:54 +00:00 
			
		
		
		
	Added key rollover code.
This commit is contained in:
		
							parent
							
								
									94aab7c5e2
								
							
						
					
					
						commit
						e6657d6ebe
					
				| @ -364,6 +364,77 @@ def ssl_get_csr(domain): | ||||
| 	ssl_private_key = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_private_key.pem')) | ||||
| 	return create_csr(domain, ssl_private_key, request.form.get('countrycode', ''), env) | ||||
| 
 | ||||
| @app.route('/ssl/renew/<domain>', methods=['POST']) | ||||
| @authorized_personnel_only | ||||
| def ssl_renew(domain): | ||||
| 	from exclusiveprocess import Lock | ||||
| 	from utils import load_environment | ||||
| 	from ssl_certificates import provision_certificates | ||||
| 	existing_key = request.form.get('existing_key') | ||||
| 	Lock(die=True).forever() | ||||
| 	env = load_environment() | ||||
| 	if existing_key == "yes": | ||||
| 		status = provision_certificates(env, limit_domains=[], domain_to_be_renewed=domain) | ||||
| 		app.logger.warning("renew without new key=", status)  # TODO: remove this line after testing | ||||
| 	elif existing_key == "no": | ||||
| 		import glob | ||||
| 		try: | ||||
| 			# steps followed | ||||
| 			# 1. take a backup of the current /home/user-data/ssl/ folder to be safe | ||||
| 			# 2. renew all the existing certificates from CSR generated from the existing next_ssl_private_key | ||||
| 			# 3. if the renew is successful, replace the current ssl_private_key with the next_ssl_private_key and | ||||
| 			# 4. generate the next_ssl_private_key | ||||
| 			# 5. if any error occurs, copy everything from the /home/user-data/ssl-backup folder to /home/user-data/ssl | ||||
| 
 | ||||
| 			# step 1 | ||||
| 			files = glob.glob(env["STORAGE_ROOT"] + "/ssl/*") | ||||
| 			for file in files: | ||||
| 				subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl-backup/"]) | ||||
| 
 | ||||
| 			# step 2 | ||||
| 			status = provision_certificates(env, limit_domains=[], new_key=True) | ||||
| 
 | ||||
| 			# step 3 and 4 is in post_install_func method of ssl_certificates.py | ||||
| 			app.logger.warning("renew with new key=", status)  # TODO: remove this line after proper testing | ||||
| 		except Exception as e: | ||||
| 			import traceback | ||||
| 			files = glob.glob(env["STORAGE_ROOT"] + "/ssl-backup/*") | ||||
| 			for file in files: | ||||
| 				subprocess.check_output(["cp", "-r", file, env["STORAGE_ROOT"] + "/ssl/"]) | ||||
| 			app.logger.warning(traceback.print_exc())  # TODO: remove this line after proper testing | ||||
| 			return json_response({ | ||||
| 				"title": "Error", | ||||
| 				"log": "Sorry, something is not right!", | ||||
| 			}) | ||||
| 	else: | ||||
| 		return json_response({ | ||||
| 			"title": "Error", | ||||
| 			"log": "Sorry, something is not right!", | ||||
| 		}) | ||||
| 
 | ||||
| 	for item in status: | ||||
| 		if isinstance(status, str): | ||||
| 			continue | ||||
| 		else: | ||||
| 			if domain in item['domains']: | ||||
| 				if item['result'] == 'skipped': | ||||
| 					return json_response({ | ||||
| 						"title": item["result"].capitalize(), | ||||
| 						"log": "\n".join(item['log']), | ||||
| 					}) | ||||
| 				elif item['result'] == 'installed': | ||||
| 					return json_response({ | ||||
| 						"title": item["result"].capitalize(), | ||||
| 						"log": "Your certificate containing these domains " + ",".join( | ||||
| 							item['domains']) + " have been renewed", | ||||
| 					}) | ||||
| 				else: | ||||
| 					return json_response({ | ||||
| 						"title": item["result"].capitalize(), | ||||
| 						"log": "\n".join(item['log']) | ||||
| 					}) | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/ssl/install', methods=['POST']) | ||||
| @authorized_personnel_only | ||||
| def ssl_install_cert(): | ||||
| @ -604,7 +675,8 @@ def log_failed_login(request): | ||||
| # APP | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
| 	if "DEBUG" in os.environ: app.debug = True | ||||
| 	app.debug = True  # TODO: remove this line and uncomment the next line after testing | ||||
| 	# if "DEBUG" in os.environ: app.debug = True | ||||
| 	if "APIKEY" in os.environ: auth_service.key = os.environ["APIKEY"] | ||||
| 
 | ||||
| 	if not app.debug: | ||||
|  | ||||
| @ -174,9 +174,11 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en | ||||
| 
 | ||||
| 		# Add a DANE TLSA record for SMTP. | ||||
| 		records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used.")) | ||||
| 		records.append(("_25._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used.")) | ||||
| 
 | ||||
| 		# Add a DANE TLSA record for HTTPS, which some browser extensions might make use of. | ||||
| 		records.append(("_443._tcp", "TLSA", build_tlsa_record(env), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it.")) | ||||
| 		records.append(("_443._tcp", "TLSA", build_tlsa_record(env, from_cert=False), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it.")) | ||||
| 
 | ||||
| 		# Add a SSHFP records to help SSH key validation. One per available SSH key on this system. | ||||
| 		for value in build_sshfp_records(): | ||||
| @ -367,7 +369,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en | ||||
| 
 | ||||
| ######################################################################## | ||||
| 
 | ||||
| def build_tlsa_record(env): | ||||
| def build_tlsa_record(env, from_cert=True): | ||||
| 	# A DANE TLSA record in DNS specifies that connections on a port | ||||
| 	# must use TLS and the certificate must match a particular criteria. | ||||
| 	# | ||||
| @ -390,11 +392,16 @@ def build_tlsa_record(env): | ||||
| 	from ssl_certificates import load_cert_chain, load_pem | ||||
| 	from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat | ||||
| 
 | ||||
| 	fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem") | ||||
| 	cert = load_pem(load_cert_chain(fn)[0]) | ||||
| 
 | ||||
| 	subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) | ||||
| 	# We could have also loaded ssl_private_key.pem and called priv_key.public_key().public_bytes(...) | ||||
| 	if from_cert: | ||||
| 		fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem") | ||||
| 		cert = load_pem(load_cert_chain(fn)[0]) | ||||
| 		subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) | ||||
| 	else: | ||||
| 		# this is for Double TLSA scheme of key rollover. | ||||
| 		# More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html) | ||||
| 		fn = os.path.join(env["STORAGE_ROOT"], "ssl", "next_ssl_private_key.pem") | ||||
| 		private_key = load_pem(open(fn, 'rb').read()) | ||||
| 		subject_public_key = private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) | ||||
| 
 | ||||
| 	pk_hash = hashlib.sha256(subject_public_key).hexdigest() | ||||
| 
 | ||||
| @ -862,6 +869,8 @@ def set_custom_dns_record(qname, rtype, value, action, env): | ||||
| 
 | ||||
| 			if not re.search(DOMAIN_RE, value): | ||||
| 				raise ValueError("Invalid value.") | ||||
| 		elif rtype == "TLSA": | ||||
| 			pass | ||||
| 		elif rtype in ("CNAME", "TXT", "SRV", "MX", "SSHFP", "CAA"): | ||||
| 			# anything goes | ||||
| 			pass | ||||
|  | ||||
| @ -45,7 +45,7 @@ def get_ssl_certificates(env): | ||||
| 
 | ||||
| 	# Remember stuff. | ||||
| 	private_keys = { } | ||||
| 	certificates = [ ] | ||||
| 	certificates = [] | ||||
| 
 | ||||
| 	# Scan each of the files to find private keys and certificates. | ||||
| 	# We must load all of the private keys first before processing | ||||
| @ -73,6 +73,7 @@ def get_ssl_certificates(env): | ||||
| 	domains = { } | ||||
| 	for cert in certificates: | ||||
| 		# What domains is this certificate good for? | ||||
| 		# cert_domains = cert common name + all SANs, primary_domain = cert common name | ||||
| 		cert_domains, primary_domain = get_certificate_domains(cert) | ||||
| 		cert._primary_domain = primary_domain | ||||
| 
 | ||||
| @ -186,8 +187,16 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True | ||||
| 	from web_update import get_web_domains | ||||
| 	from status_checks import query_dns, normalize_ip | ||||
| 
 | ||||
| 	# existing_certs = all valid certificates that are in /home/user-data/ssl or 1 level deep | ||||
| 	# validity indicator -> private key exists for the cert public key?, not_expired? | ||||
| 	# if multiple valid certs exist, then the one with the furthest expiry date and filename | ||||
| 	# lexicographically smallest is returned | ||||
| 	existing_certs = get_ssl_certificates(env) | ||||
| 
 | ||||
| 	# this function returns the list of domain names of all the email addresses and adds | ||||
| 	# autoconfig, www, mta-sts, autodiscover subdomain to each of these domains | ||||
| 	# if exclude_dns_elsewhere flag is set, then all the domains having A/AAAA record not on this machine | ||||
| 	# are excluded | ||||
| 	plausible_web_domains = get_web_domains(env, exclude_dns_elsewhere=False) | ||||
| 	actual_web_domains = get_web_domains(env) | ||||
| 
 | ||||
| @ -212,7 +221,8 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True | ||||
| 			# how Let's Encrypt will connect. | ||||
| 			bad_dns = [] | ||||
| 			for rtype, value in [("A", env["PUBLIC_IP"]), ("AAAA", env.get("PUBLIC_IPV6"))]: | ||||
| 				if not value: continue # IPv6 is not configured | ||||
| 				if not value: | ||||
| 					continue  # IPv6 is not configured | ||||
| 				response = query_dns(domain, rtype) | ||||
| 				if response != normalize_ip(value): | ||||
| 					bad_dns.append("%s (%s)" % (response, rtype)) | ||||
| @ -226,6 +236,7 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True | ||||
| 				# DNS is all good. | ||||
| 
 | ||||
| 				# Check for a good existing cert. | ||||
| 				# existing_cert = existing cert for domain | ||||
| 				existing_cert = get_domain_ssl_files(domain, existing_certs, env, use_main_cert=False, allow_missing_cert=True) | ||||
| 				if existing_cert: | ||||
| 					existing_cert_check = check_certificate(domain, existing_cert['certificate'], existing_cert['private-key'], | ||||
| @ -242,19 +253,30 @@ def get_certificates_to_provision(env, limit_domains=None, show_valid_certs=True | ||||
| 
 | ||||
| 	return (domains_to_provision, domains_cant_provision) | ||||
| 
 | ||||
| def provision_certificates(env, limit_domains): | ||||
| def provision_certificates(env, limit_domains, domain_to_be_renewed=None, new_key=False): | ||||
| 	# What domains should we provision certificates for? And what | ||||
| 	# errors prevent provisioning for other domains. | ||||
| 	domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains) | ||||
| 
 | ||||
| 	# Build a list of what happened on each domain or domain-set. | ||||
| 	ret = [] | ||||
| 	for domain, error in domains_cant_provision.items(): | ||||
| 		ret.append({ | ||||
| 			"domains": [domain], | ||||
| 			"log": [error], | ||||
| 			"result": "skipped", | ||||
| 		}) | ||||
| 	is_tlsa_update_required = False | ||||
| 	if new_key: | ||||
| 		from web_update import get_web_domains | ||||
| 		domains = get_web_domains(env) | ||||
| 	elif domain_to_be_renewed: | ||||
| 		existing_certs = get_ssl_certificates(env) | ||||
| 		existing_cert = get_domain_ssl_files(domain_to_be_renewed, existing_certs, env, use_main_cert=False, allow_missing_cert=True) | ||||
| 		domains, primary_domain = get_certificate_domains(load_pem(load_cert_chain(existing_cert["certificate"])[0])) | ||||
| 	else: | ||||
| 		# domains = domains for which a certificate can be provisioned | ||||
| 		# domains_cant_provision = domains for which a certificate can't be provisioned and the reason | ||||
| 		domains, domains_cant_provision = get_certificates_to_provision(env, limit_domains=limit_domains) | ||||
| 
 | ||||
| 		# Build a list of what happened on each domain or domain-set. | ||||
| 		for domain, error in domains_cant_provision.items(): | ||||
| 			ret.append({ | ||||
| 				"domains": [domain], | ||||
| 				"log": [error], | ||||
| 				"result": "skipped", | ||||
| 			}) | ||||
| 
 | ||||
| 	# Break into groups by DNS zone: Group every domain with its parent domain, if | ||||
| 	# its parent domain is in the list of domains to request a certificate for. | ||||
| @ -309,6 +331,8 @@ def provision_certificates(env, limit_domains): | ||||
| 			# Create a CSR file for our master private key so that certbot | ||||
| 			# uses our private key. | ||||
| 			key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem') | ||||
| 			if new_key: | ||||
| 				key_file = os.path.join(env['STORAGE_ROOT'], 'ssl', 'next_ssl_private_key.pem') | ||||
| 			with tempfile.NamedTemporaryFile() as csr_file: | ||||
| 				# We could use openssl, but certbot requires | ||||
| 				# that the CN domain and SAN domains match | ||||
| @ -345,9 +369,9 @@ def provision_certificates(env, limit_domains): | ||||
| 						"certbot", | ||||
| 						"certonly", | ||||
| 						#"-v", # just enough to see ACME errors | ||||
| 						"--non-interactive", # will fail if user hasn't registered during Mail-in-a-Box setup | ||||
| 						"--non-interactive",  # will fail if user hasn't registered during Mail-in-a-Box setup | ||||
| 
 | ||||
| 						"-d", ",".join(domain_list), # first will be main domain | ||||
| 						"-d", ",".join(domain_list),  # first will be main domain | ||||
| 
 | ||||
| 						"--csr", csr_file.name, # use our private key; unfortunately this doesn't work with auto-renew so we need to save cert manually | ||||
| 						"--cert-path", os.path.join(d, 'cert'), # we only use the full chain | ||||
| @ -363,6 +387,8 @@ def provision_certificates(env, limit_domains): | ||||
| 
 | ||||
| 			ret[-1]["log"].append(certbotret) | ||||
| 			ret[-1]["result"] = "installed" | ||||
| 			if new_key and env['PRIMARY_HOSTNAME'] in domains: | ||||
| 				is_tlsa_update_required = True | ||||
| 		except subprocess.CalledProcessError as e: | ||||
| 			ret[-1]["log"].append(e.output.decode("utf8")) | ||||
| 			ret[-1]["result"] = "error" | ||||
| @ -371,7 +397,7 @@ def provision_certificates(env, limit_domains): | ||||
| 			ret[-1]["result"] = "error" | ||||
| 
 | ||||
| 	# Run post-install steps. | ||||
| 	ret.extend(post_install_func(env)) | ||||
| 	ret.extend(post_install_func(env, is_tlsa_update_required=is_tlsa_update_required)) | ||||
| 
 | ||||
| 	# Return what happened with each certificate request. | ||||
| 	return ret | ||||
| @ -466,7 +492,7 @@ def install_cert_copy_file(fn, env): | ||||
| 	shutil.move(fn, ssl_certificate) | ||||
| 
 | ||||
| 
 | ||||
| def post_install_func(env): | ||||
| def post_install_func(env, is_tlsa_update_required=False): | ||||
| 	ret = [] | ||||
| 
 | ||||
| 	# Get the certificate to use for PRIMARY_HOSTNAME. | ||||
| @ -496,6 +522,25 @@ def post_install_func(env): | ||||
| 		# The DANE TLSA record will remain valid so long as the private key | ||||
| 		# hasn't changed. We don't ever change the private key automatically. | ||||
| 		# If the user does it, they must manually update DNS. | ||||
| 		if is_tlsa_update_required: | ||||
| 			from dns_update import do_dns_update, set_custom_dns_record, build_tlsa_record | ||||
| 			subprocess.check_output([ | ||||
| 				"mv", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem", | ||||
| 					  env["STORAGE_ROOT"] + "/ssl/ssl_private_key.pem" | ||||
| 			]) | ||||
| 			subprocess.check_output([ | ||||
| 				"openssl", "genrsa", | ||||
| 				"-out", env["STORAGE_ROOT"] + "/ssl/next_ssl_private_key.pem", | ||||
| 				"2048"]) | ||||
| 			qname1 = "_25._tcp." + env['PRIMARY_HOSTNAME'] | ||||
| 			qname2 = "_443._tcp." + env['PRIMARY_HOSTNAME'] | ||||
| 			rtype = "TLSA" | ||||
| 			value = build_tlsa_record(env, from_cert=False) | ||||
| 			action = "add" | ||||
| 			if set_custom_dns_record(qname1, rtype, value, action, env): | ||||
| 				set_custom_dns_record(qname2, rtype, value, action, env) | ||||
| 				ret.append(do_dns_update(env)) | ||||
| 
 | ||||
| 
 | ||||
| 	# Update the web configuration so nginx picks up the new certificate file. | ||||
| 	from web_update import do_web_update | ||||
|  | ||||
| @ -454,9 +454,9 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): | ||||
| 
 | ||||
| 	# Check the TLSA record. | ||||
| 	tlsa_qname = "_25._tcp." + domain | ||||
| 	tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None) | ||||
| 	tlsa25 = query_dns(tlsa_qname, "TLSA", nxdomain=None).split('; ') | ||||
| 	tlsa25_expected = build_tlsa_record(env) | ||||
| 	if tlsa25 == tlsa25_expected: | ||||
| 	if tlsa25_expected in tlsa25: | ||||
| 		output.print_ok("""The DANE TLSA record for incoming mail is correct (%s).""" % tlsa_qname,) | ||||
| 	elif tlsa25 is None: | ||||
| 		if has_dnssec: | ||||
|  | ||||
| @ -40,7 +40,10 @@ | ||||
| 
 | ||||
| <h3 id="ssl_install_header">Install certificate</h3> | ||||
| 
 | ||||
| <p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. You can generate the needed CSR below.</p> | ||||
| <p>If you don't want to use our automatic Let's Encrypt integration, you can give any other certificate provider a try. Click on install certificate button | ||||
|     if there is no certificate for your intended domain or | ||||
|     click on renew or replace certificate button and click replace if there is an existing certificate and you want to replace it with a new one from a different CA. | ||||
|     You can generate the needed CSR below.</p> | ||||
| 
 | ||||
| <p>Which domain are you getting a certificate for?</p> | ||||
| 
 | ||||
| @ -101,7 +104,8 @@ function show_tls(keep_provisioning_shown) { | ||||
|       $('#ssldomain').html('<option value="">(select)</option>'); | ||||
|       $('#ssl_domains').show(); | ||||
|       for (var i = 0; i < domains.length; i++) { | ||||
|         var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td> <td class='actions'><a href='#' onclick='return ssl_install(this);' class='btn btn-xs'>Install Certificate</a></td></tr>"); | ||||
|         var row = $("<tr><th class='domain'><a href=''></a></th><td class='status'></td> " + | ||||
|                 "<td class='actions'><a href='#' onclick='return ssl_install(this);' class='btn btn-xs'>Install Certificate</a></td></tr>"); | ||||
|         tb.append(row); | ||||
|         row.attr('data-domain', domains[i].domain); | ||||
|         row.find('.domain a').text(domains[i].domain); | ||||
| @ -113,7 +117,10 @@ function show_tls(keep_provisioning_shown) { | ||||
|         row.addClass("text-" + domains[i].status); | ||||
|         row.find('.status').text(domains[i].text); | ||||
|         if (domains[i].status == "success") { | ||||
|           row.find('.actions a').addClass('btn-default').text('Replace Certificate'); | ||||
|           row.find('.actions a').addClass('btn-default').text('Renew or replace Certificate'); | ||||
|           row.find('.actions a').addClass('btn-default').on("click", function () { | ||||
|               ssl_renew_or_replace_modal(this); | ||||
|           }); | ||||
|         } else { | ||||
|           row.find('.actions a').addClass('btn-primary').text('Install Certificate'); | ||||
|         } | ||||
| @ -131,6 +138,65 @@ function ssl_install(elem) { | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| function ssl_renew_or_replace_modal(elem) { | ||||
|     show_modal_confirm( | ||||
|     "Options", | ||||
|     "Do you want to replace the certificate with a new one or just renew this one?", | ||||
|     ["Replace", "Renew"], | ||||
|     function () { | ||||
|         ssl_install(elem); | ||||
|     }, | ||||
|     function () { | ||||
|         ssl_cert_renew(elem); | ||||
|     }); | ||||
| } | ||||
| function ssl_cert_renew(elem) { | ||||
|     var domain = $(elem).parents('tr').attr('data-domain'); | ||||
|     show_modal_confirm( | ||||
|     "Options", | ||||
|     "Do you want to renew with the existing key?", | ||||
|     ["Yes", "No"], | ||||
|     function () { | ||||
|         ajax_with_indicator(true); | ||||
|         api( | ||||
|         "/ssl/renew/" + domain, | ||||
|         "POST", | ||||
|         { | ||||
|             existing_key: "yes" | ||||
|         }, | ||||
|         function(data) { | ||||
|             $('#ajax_loading_indicator').stop(true).hide(); | ||||
|           show_modal_error(data["title"], data["log"]); | ||||
|           show_tls(true); | ||||
|         }, | ||||
|         function () { | ||||
|             $('#ajax_loading_indicator').stop(true).hide(); | ||||
|             show_modal_error("Error", "Something is not right, sorry!"); | ||||
|             show_tls(true); | ||||
|         }); | ||||
|     }, | ||||
|     function () { | ||||
|         ajax_with_indicator(true); | ||||
|         api( | ||||
|         "/ssl/renew/" + domain, | ||||
|         "POST", | ||||
|         { | ||||
|             existing_key: "no" | ||||
|         }, | ||||
|         function(data) { | ||||
|             $('#ajax_loading_indicator').stop(true).hide(); | ||||
|             show_modal_error(data["title"], data["log"]); | ||||
|             show_tls(true); | ||||
|         }, | ||||
|         function () { | ||||
|             $('#ajax_loading_indicator').stop(true).hide(); | ||||
|             show_modal_error("Error", "Something is not right, sorry!"); | ||||
|             show_tls(true); | ||||
|         } | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function show_csr() { | ||||
|  // Can't show a CSR until both inputs are entered. | ||||
|  if ($('#ssldomain').val() == "") return; | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| # | ||||
| # The Diffie-Hellman cipher bits are used for SMTP and HTTPS, when a | ||||
| # Diffie-Hellman cipher is selected during TLS negotiation. Diffie-Hellman | ||||
| # provides Perfect Forward Secrecy.  | ||||
| # provides Perfect Forward Secrecy. | ||||
| 
 | ||||
| source setup/functions.sh # load our functions | ||||
| source /etc/mailinabox.conf # load global vars | ||||
| @ -66,6 +66,13 @@ if [ ! -f $STORAGE_ROOT/ssl/ssl_private_key.pem ]; then | ||||
| 		openssl genrsa -out $STORAGE_ROOT/ssl/ssl_private_key.pem 2048) | ||||
| fi | ||||
| 
 | ||||
| # for Double TLSA scheme. More details here (https://mail.sys4.de/pipermail/dane-users/2018-February/000440.html) | ||||
| if [ ! -f $STORAGE_ROOT/ssl/next_ssl_private_key.pem ]; then | ||||
| 	# Set the umask so the key file is never world-readable. | ||||
| 	(umask 077; hide_output \ | ||||
| 		openssl genrsa -out $STORAGE_ROOT/ssl/next_ssl_private_key.pem 2048) | ||||
| fi | ||||
| 
 | ||||
| # Generate a self-signed SSL certificate because things like nginx, dovecot, | ||||
| # etc. won't even start without some certificate in place, and we need nginx | ||||
| # so we can offer the user a control panel to install a better certificate. | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user