>From cce2c2ea05142fe71c418e5288506dd8dfce4a14 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Sat, 7 Nov 2015 00:09:19 +0100 Subject: [PATCH] Added mechanism to import existing CA certificate. The deployment procedure for external CA has been modified such that it generates the CA CSR before starting the server. This allows the same procedure to be used to import CA certificate from an existing server. It also removes the requirement to keep the server running while waiting to get the CSR signed by an external CA. https://fedorahosted.org/pki/ticket/456 --- base/common/python/pki/nss.py | 247 ++++++++++++++++++--- .../certsrv/system/ConfigurationRequest.java | 12 + .../cms/servlet/csadmin/ConfigurationUtils.java | 101 +++++++++ .../dogtagpki/server/rest/SystemConfigService.java | 38 +++- base/server/etc/default.cfg | 10 +- base/server/python/pki/server/__init__.py | 5 +- .../python/pki/server/deployment/pkihelper.py | 70 +++--- .../server/deployment/scriptlets/configuration.py | 131 ++++++++++- .../server/deployment/scriptlets/finalization.py | 12 +- 9 files changed, 560 insertions(+), 66 deletions(-) diff --git a/base/common/python/pki/nss.py b/base/common/python/pki/nss.py index f36b771f85eb45641022d6033c23a88aca50757a..f5384bc66be3ed90448b060d4d61105e4bf61421 100644 --- a/base/common/python/pki/nss.py +++ b/base/common/python/pki/nss.py @@ -32,12 +32,19 @@ CSR_FOOTER = '-----END NEW CERTIFICATE REQUEST-----' CERT_HEADER = '-----BEGIN CERTIFICATE-----' CERT_FOOTER = '-----END CERTIFICATE-----' +PKCS7_HEADER = '-----BEGIN PKCS7-----' +PKCS7_FOOTER = '-----END PKCS7-----' + def convert_data(data, input_format, output_format, header=None, footer=None): + if input_format == output_format: + return data + if input_format == 'base64' and output_format == 'pem': # split a single line into multiple lines + data = data.rstrip('\r\n') lines = [data[i:i+64] for i in range(0, len(data), 64)] return '%s\n%s\n%s\n' % (header, '\n'.join(lines), footer) @@ -65,11 +72,32 @@ def convert_cert(cert_data, input_format, output_format): return convert_data(cert_data, input_format, output_format, CERT_HEADER, CERT_FOOTER) +def convert_pkcs7(pkcs7_data, input_format, output_format): + + return convert_data(pkcs7_data, input_format, output_format, PKCS7_HEADER, PKCS7_FOOTER) + +def get_file_type(filename): + + with open(filename, 'r') as f: + data = f.read() + + if data.startswith(CSR_HEADER): + return 'csr' + + if data.startswith(CERT_HEADER): + return 'cert' + + if data.startswith(PKCS7_HEADER): + return 'pkcs7' + + return None + class NSSDatabase(object): - def __init__(self, directory, password=None, password_file=None): + def __init__(self, directory, token='internal', password=None, password_file=None): self.directory = directory + self.token = token self.tmpdir = tempfile.mkdtemp() @@ -88,29 +116,38 @@ class NSSDatabase(object): shutil.rmtree(self.tmpdir) def add_cert(self, - nickname, cert_file, - trust_attributes='u,u,u'): + nickname, + cert_file, + trust_attributes=',,'): - subprocess.check_call([ + cmd = [ 'certutil', '-A', '-d', self.directory, + '-h', self.token, + '-f', self.password_file, '-n', nickname, '-i', cert_file, '-t', trust_attributes - ]) + ] + + subprocess.check_call(cmd) def modify_cert(self, nickname, - trust_attributes='u,u,u'): + trust_attributes): - subprocess.check_call([ + cmd = [ 'certutil', '-M', '-d', self.directory, + '-h', self.token, + '-f', self.password, '-n', nickname, '-t', trust_attributes - ]) + ] + + subprocess.check_call(cmd) def create_noise(self, noise_file, size=2048): @@ -123,27 +160,56 @@ class NSSDatabase(object): def create_request(self, subject_dn, - noise_file, - request_file): + request_file, + noise_file=None, + key_type=None, + key_size=None, + curve=None, + hash_alg=None): tmpdir = tempfile.mkdtemp() try: + if not noise_file: + noise_file = os.path.join(tmpdir, 'noise.bin') + if key_size: + size = key_size + else: + size = 2048 + self.create_noise( + noise_file=noise_file, + size=size) + binary_request_file = os.path.join(tmpdir, 'request.bin') - b64_request_file = os.path.join(tmpdir, 'request.b64') - # generate binary request - subprocess.check_call([ + cmd = [ 'certutil', '-R', '-d', self.directory, + '-h', self.token, '-f', self.password_file, '-s', subject_dn, - '-z', noise_file, - '-o', binary_request_file - ]) + '-o', binary_request_file, + '-z', noise_file + ] + + if key_type: + cmd.extend(['-k', key_type]) + + if key_size: + cmd.extend(['-g', str(key_size)]) + + if curve: + cmd.extend(['-q', curve]) + + if hash_alg: + cmd.extend(['-Z', hash_alg]) + + # generate binary request + subprocess.check_call(cmd) # encode binary request in base-64 + b64_request_file = os.path.join(tmpdir, 'request.b64') subprocess.check_call([ 'BtoA', binary_request_file, b64_request_file]) @@ -167,11 +233,12 @@ class NSSDatabase(object): serial='1', validity=240): - p = subprocess.Popen([ + cmd = [ 'certutil', '-C', '-x', '-d', self.directory, + '-h', self.token, '-f', self.password_file, '-c', subject_dn, '-a', @@ -184,7 +251,9 @@ class NSSDatabase(object): '-3', '--extSKID', '--extAIA' - ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + ] + + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) keystroke = '' @@ -245,7 +314,7 @@ class NSSDatabase(object): rc = p.wait() if rc: - raise Exception('Failed to generate self-signed CA certificate. RC: %d' + rc) + raise Exception('Failed to generate self-signed CA certificate. RC: %d' % rc) def get_cert(self, nickname, output_format='pem'): @@ -258,13 +327,17 @@ class NSSDatabase(object): else: raise Exception('Unsupported output format: %s' % output_format) - cert_data = subprocess.check_output([ + cmd = [ 'certutil', '-L', '-d', self.directory, + '-h', self.token, + '-f', self.password_file, '-n', nickname, output_format_option - ]) + ] + + cert_data = subprocess.check_output(cmd) if output_format == 'base64': cert_data = base64.b64encode(cert_data) @@ -273,12 +346,127 @@ class NSSDatabase(object): def remove_cert(self, nickname): - subprocess.check_call([ + cmd = [ 'certutil', '-D', '-d', self.directory, + '-h', self.token, + '-f', self.password_file, '-n', nickname - ]) + ] + + subprocess.check_call(cmd) + + def import_cert_chain(self, nickname, cert_chain_file, trust_attributes=None): + + tmpdir = tempfile.mkdtemp() + + try: + file_type = get_file_type(cert_chain_file) + + if file_type == 'cert': # import single PEM cert + self.add_cert( + nickname=nickname, + cert_file=cert_chain_file, + trust_attributes=trust_attributes) + return self.get_cert( + nickname=nickname, + output_format='base64') + + elif file_type == 'pkcs7': # import PKCS #7 cert chain + return self.import_pkcs7( + pkcs7_file=cert_chain_file, + nickname=nickname, + trust_attributes=trust_attributes, + output_format='base64') + + else: # import PKCS #7 data without header/footer + with open(cert_chain_file, 'r') as f: + base64_data = f.read() + pkcs7_data = convert_pkcs7(base64_data, 'base64', 'pem') + + tmp_cert_chain_file = os.path.join(tmpdir, 'cert_chain.p7b') + with open(tmp_cert_chain_file, 'w') as f: + f.write(pkcs7_data) + + self.import_pkcs7( + pkcs7_file=tmp_cert_chain_file, + nickname=nickname, + trust_attributes=trust_attributes) + + return base64_data + + finally: + shutil.rmtree(tmpdir) + + def import_pkcs7(self, pkcs7_file, nickname, trust_attributes=None, output_format='pem'): + + tmpdir = tempfile.mkdtemp() + + try: + # export certs from PKCS #7 into PEM output + output = subprocess.check_output([ + 'openssl', + 'pkcs7', + '-print_certs', + '-in', pkcs7_file + ]) + + # parse PEM output into separate PEM certificates + certs = [] + lines = [] + state = 'header' + + for line in output.splitlines(): + + if state == 'header': + if line != CERT_HEADER: + # ignore header lines + pass + else: + # save cert header + lines.append(line) + state = 'body' + + elif state == 'body': + if line != CERT_FOOTER: + # save cert body + lines.append(line) + else: + # save cert footer + lines.append(line) + + # construct PEM cert + cert = '\n'.join(lines) + certs.append(cert) + lines = [] + state = 'header' + + # import PEM certs into NSS database + counter = 1 + for cert in certs: + + cert_file = os.path.join(tmpdir, 'cert%d.pem' % counter) + with open(cert_file, 'w') as f: + f.write(cert) + + if counter == 1: + n = nickname + else: + n = '%s #%d' % (nickname, counter) + + self.add_cert(n, cert_file, trust_attributes) + + counter += 1 + + # convert PKCS #7 data to the requested format + with open(pkcs7_file, 'r') as f: + data = f.read() + + return convert_pkcs7(data, 'pem', output_format) + + finally: + shutil.rmtree(tmpdir) def import_pkcs12(self, pkcs12_file, pkcs12_password=None, pkcs12_password_file=None): @@ -296,13 +484,16 @@ class NSSDatabase(object): else: raise Exception('Missing PKCS #12 password') - subprocess.check_call([ + cmd = [ 'pk12util', '-d', self.directory, + '-h', self.token, '-k', self.password_file, '-i', pkcs12_file, '-w', password_file - ]) + ] + + subprocess.check_call(cmd) finally: shutil.rmtree(tmpdir) @@ -323,14 +514,16 @@ class NSSDatabase(object): else: raise Exception('Missing PKCS #12 password') - subprocess.check_call([ + cmd = [ 'pk12util', '-d', self.directory, '-k', self.password_file, '-o', pkcs12_file, '-w', password_file, '-n', nickname - ]) + ] + + subprocess.check_call(cmd) finally: shutil.rmtree(tmpdir) diff --git a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java index 7c6c339f5ddff40018c4d14c97ca63e12e9b9289..8c9da6f373be192a6b5cf99a3cf8cd6ce288c3aa 100644 --- a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java +++ b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java @@ -178,6 +178,9 @@ public class ConfigurationRequest { protected String adminCert; @XmlElement + protected Boolean external; + + @XmlElement protected String standAlone; @XmlElement @@ -754,6 +757,14 @@ public class ConfigurationRequest { this.adminCert = adminCert; } + public Boolean isExternal() { + return external; + } + + public void setExternal(Boolean external) { + this.external = external; + } + public boolean getStandAlone() { return (standAlone != null && standAlone.equalsIgnoreCase("true")); } @@ -945,6 +956,7 @@ public class ConfigurationRequest { ", adminCert=" + adminCert + ", importAdminCert=" + importAdminCert + ", generateServerCert=" + generateServerCert + + ", external=" + external + ", standAlone=" + standAlone + ", stepTwo=" + stepTwo + ", authdbBaseDN=" + authdbBaseDN + diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java index 88118adf8a7535442d8f1f678ce14f6f6ac07e51..91dad159bb39605d094c87c1958cc57772dbb732 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java @@ -126,6 +126,7 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -133,6 +134,8 @@ import com.netscape.certsrv.client.ClientConfig; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.PKIConnection; import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.certdb.ICertRecord; +import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.dbs.crldb.ICRLIssuingPointRecord; import com.netscape.certsrv.key.KeyData; import com.netscape.certsrv.ldap.ILdapConnFactory; @@ -2248,6 +2251,54 @@ public class ConfigurationUtils { certObj.setCertChain(certChainStr); } + public static KeyPair loadKeyPair(String nickname) throws Exception { + + CMS.debug("ConfigurationUtils: loadKeyPair(" + nickname + ")"); + + CryptoManager cm = CryptoManager.getInstance(); + + X509Certificate cert = cm.findCertByNickname(nickname); + PublicKey publicKey = cert.getPublicKey(); + PrivateKey privateKey = cm.findPrivKeyByCert(cert); + + return new KeyPair(publicKey, privateKey); + } + + public static void storeKeyPair(IConfigStore config, String tag, KeyPair pair) + throws TokenException, EBaseException { + + CMS.debug("ConfigurationUtils: storeKeyPair(" + tag + ")"); + + PublicKey publicKey = pair.getPublic(); + + if (publicKey instanceof RSAPublicKey) { + + RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; + + byte modulus[] = rsaPublicKey.getModulus().toByteArray(); + config.putString(PCERT_PREFIX + tag + ".pubkey.modulus", + CryptoUtil.byte2string(modulus)); + + byte exponent[] = rsaPublicKey.getPublicExponent().toByteArray(); + config.putString(PCERT_PREFIX + tag + ".pubkey.exponent", + CryptoUtil.byte2string(exponent)); + + } else { // ECC + + CMS.debug("ConfigurationUtils: Public key class: " + publicKey.getClass().getName()); + byte encoded[] = publicKey.getEncoded(); + config.putString(PCERT_PREFIX + tag + ".pubkey.encoded", CryptoUtil.byte2string(encoded)); + } + + PrivateKey privateKey = (PrivateKey) pair.getPrivate(); + byte id[] = privateKey.getUniqueID(); + String kid = CryptoUtil.byte2string(id); + config.putString(PCERT_PREFIX + tag + ".privkey.id", kid); + + String keyAlgo = config.getString(PCERT_PREFIX + tag + ".signingalgorithm"); + setSigningAlgorithm(tag, keyAlgo, config); + } + public static void createECCKeyPair(String token, String curveName, IConfigStore config, String ct) throws NoSuchAlgorithmException, NoSuchTokenException, TokenException, CryptoManager.NotInitializedException, EPropertyNotFound, EBaseException { @@ -2812,6 +2863,20 @@ public class ConfigurationUtils { } } + public static void loadCertRequest(IConfigStore config, String tag, Cert cert) throws Exception { + + CMS.debug("ConfigurationUtils.loadCertRequest(" + tag + ")"); + + String subjectDN = config.getString(PCERT_PREFIX + tag + ".dn"); + cert.setDN(subjectDN); + + String subsystem = config.getString(PCERT_PREFIX + tag + ".subsystem"); + String certreq = config.getString(subsystem + "." + tag + ".certreq"); + String formattedCertreq = CryptoUtil.reqFormat(certreq); + + cert.setRequest(formattedCertreq); + } + public static void handleCertRequest(IConfigStore config, String certTag, Cert cert) throws EPropertyNotFound, EBaseException, InvalidKeyException, NotInitializedException, TokenException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException, SignatureException, IOException { @@ -2953,6 +3018,42 @@ public class ConfigurationUtils { return pubk; } + public static void loadCert(IConfigStore config, Cert cert) throws Exception { + + String tag = cert.getCertTag(); + CMS.debug("ConfigurationUtils: loadCert(" + tag + ")"); + + CryptoManager cm = CryptoManager.getInstance(); + X509Certificate x509Cert = cm.findCertByNickname(cert.getNickname()); + + if (!x509Cert.getSubjectDN().equals(x509Cert.getIssuerDN())) { + CMS.debug("ConfigurationUtils: " + tag + " cert is not self-signed"); + + String subsystem = config.getString(PCERT_PREFIX + tag + ".subsystem"); + String certChain = config.getString(subsystem + ".external_ca_chain.cert"); + cert.setCertChain(certChain); + + return; + } + + CMS.debug("ConfigurationUtils: " + tag + " cert is self-signed"); + + // When importing existing self-signed CA certificate, create a + // certificate record to reserve the serial number. Otherwise it + // might conflict with system certificates to be created later. + + X509CertImpl x509CertImpl = new X509CertImpl(x509Cert.getEncoded()); + + ICertificateAuthority ca = (ICertificateAuthority) CMS.getSubsystem(ICertificateAuthority.ID); + ICertificateRepository cr = ca.getCertificateRepository(); + + BigInteger serialNo = x509Cert.getSerialNumber(); + MetaInfo meta = new MetaInfo(); + + ICertRecord record = cr.createCertRecord(serialNo, x509CertImpl, meta); + cr.addCertificateRecord(record); + } + public static int handleCerts(Cert cert) throws IOException, EBaseException, CertificateException, NotInitializedException, TokenException, InvalidKeyException { String certTag = cert.getCertTag(); diff --git a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java index a0138681a39baeff272d75408dbee9a74d0529dc..697196a6ea6beb22210fed1f5680f7d02cea1533 100644 --- a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java +++ b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java @@ -20,6 +20,7 @@ package org.dogtagpki.server.rest; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; +import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.util.ArrayList; @@ -420,7 +421,13 @@ public class SystemConfigService extends PKIService implements SystemConfigResou } cs.commit(false); - if (!request.getStepTwo()) { + if (request.isExternal() && tag.equals("signing")) { // external/existing CA + // load key pair for existing and externally-signed signing cert + CMS.debug("SystemConfigService: loading signing cert key pair"); + KeyPair pair = ConfigurationUtils.loadKeyPair(certData.getNickname()); + ConfigurationUtils.storeKeyPair(cs, tag, pair); + + } else if (!request.getStepTwo()) { if (keytype.equals("ecc")) { String curvename = certData.getKeyCurveName() != null ? certData.getKeyCurveName() : cs.getString("keys.ecc.curve.default"); @@ -443,7 +450,15 @@ public class SystemConfigService extends PKIService implements SystemConfigResou cert.setSubsystem(cs.getString("preop.cert." + tag + ".subsystem")); cert.setType(cs.getString("preop.cert." + tag + ".type")); - if (!request.getStepTwo()) { + if (request.isExternal() && tag.equals("signing")) { // external/existing CA + + // update configuration for existing or externally-signed signing certificate + String certStr = cs.getString("ca." + tag + ".cert" ); + cert.setCert(certStr); + CMS.debug("SystemConfigService: certificate " + tag + ": " + certStr); + ConfigurationUtils.updateConfig(cs, tag); + + } else if (!request.getStepTwo()) { ConfigurationUtils.configCert(null, null, null, cert); } else { @@ -465,8 +480,16 @@ public class SystemConfigService extends PKIService implements SystemConfigResou CMS.debug("Step 2: certStr for '" + tag + "' is " + certStr); } - // Handle Cert Requests for everything EXCEPT Stand-alone PKI (Step 2) - if (request.getStandAlone()) { + if (request.isExternal() && tag.equals("signing")) { // external/existing CA + + CMS.debug("SystemConfigService: Loading cert request for " + tag + " cert"); + ConfigurationUtils.loadCertRequest(cs, tag, cert); + + CMS.debug("SystemConfigService: Loading cert " + tag); + ConfigurationUtils.loadCert(cs, cert); + + } else if (request.getStandAlone()) { + // Handle Cert Requests for everything EXCEPT Stand-alone PKI (Step 2) if (!request.getStepTwo()) { // Stand-alone PKI (Step 1) ConfigurationUtils.handleCertRequest(cs, tag, cert); @@ -489,6 +512,13 @@ public class SystemConfigService extends PKIService implements SystemConfigResou ConfigurationUtils.updateCloneConfig(); } + if (request.isExternal() && tag.equals("signing")) { // external/existing CA + CMS.debug("SystemConfigService: External CA has signing cert"); + hasSigningCert.setValue(true); + certs.add(cert); + continue; + } + // to determine if we have the signing cert when using an external ca // this will only execute on a ca or stand-alone pki String b64 = certData.getCert(); diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg index ddd2d83670dd191f38b8905cdea03172bbbc1e95..1c1ae92b323d67dc5fb810df79bbdbbb0b6c26e7 100644 --- a/base/server/etc/default.cfg +++ b/base/server/etc/default.cfg @@ -22,6 +22,7 @@ sensitive_parameters= pki_client_pkcs12_password pki_clone_pkcs12_password pki_ds_password + pki_external_pkcs12_password pki_one_time_pin pki_pin pki_replication_password @@ -365,10 +366,13 @@ pki_req_ext_add=False pki_req_ext_oid=1.3.6.1.4.1.311.20.2 pki_req_ext_critical=False pki_req_ext_data=1E0A00530075006200430041 -pki_external_csr_path=%(pki_instance_configuration_path)s/ca_signing.csr +pki_external_csr_path= pki_external_step_two=False -pki_external_ca_cert_chain_path=%(pki_instance_configuration_path)s/external_ca_chain.cert -pki_external_ca_cert_path=%(pki_instance_configuration_path)s/external_ca.cert +pki_external_ca_cert_chain_path= +pki_external_ca_cert_chain_nickname=caSigningCert External CA +pki_external_ca_cert_path= +pki_external_pkcs12_path= +pki_external_pkcs12_password= pki_import_admin_cert=False pki_ocsp_signing_key_algorithm=SHA256withRSA pki_ocsp_signing_key_size=2048 diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index d55a3691d180ede7dd1731b7490957c816bd8a3b..bf592dcd59bf07314b94447d5da7ddbdf0077c8b 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -328,10 +328,11 @@ class PKIInstance(object): return password - def open_nssdb(self): + def open_nssdb(self, token='internal'): return pki.nss.NSSDatabase( directory=self.nssdb_dir, - password=self.get_password('internal')) + token=token, + password=self.get_password(token)) def get_subsystem(self, name): for subsystem in self.subsystems: diff --git a/base/server/python/pki/server/deployment/pkihelper.py b/base/server/python/pki/server/deployment/pkihelper.py index 61f04d215ba3eba48b7e18733fd58b29555ced83..9c9b40454a41b42f2c089f045ac9ac662093a409 100644 --- a/base/server/python/pki/server/deployment/pkihelper.py +++ b/base/server/python/pki/server/deployment/pkihelper.py @@ -757,8 +757,7 @@ class ConfigurationFile: # External CA if not self.external_step_two: # External CA (Step 1) - self.confirm_data_exists("pki_external_csr_path") - self.confirm_missing_file("pki_external_csr_path") + # The pki_external_csr_path is optional. # generic extension support in CSR - for external CA if self.add_req_ext: self.confirm_data_exists("pki_req_ext_oid") @@ -766,10 +765,9 @@ class ConfigurationFile: self.confirm_data_exists("pki_req_ext_data") else: # External CA (Step 2) - self.confirm_data_exists("pki_external_ca_cert_chain_path") - self.confirm_file_exists("pki_external_ca_cert_chain_path") - self.confirm_data_exists("pki_external_ca_cert_path") - self.confirm_file_exists("pki_external_ca_cert_path") + # The pki_external_ca_cert_chain_path and + # pki_external_ca_cert_path are optional. + pass elif not self.skip_configuration and self.standalone: if not self.external_step_two: # Stand-alone PKI Admin CSR (Step 1) @@ -3813,17 +3811,7 @@ class ConfigClient: if not isinstance(certs, list): certs = [certs] for cdata in certs: - if (self.subsystem == "CA" and self.external and - not self.external_step_two): - # External CA (Step 1) - if cdata['tag'].lower() == "signing": - # Save 'External CA Signing Certificate' CSR (Step 1) - self.save_system_csr( - cdata['request'], - log.PKI_CONFIG_EXTERNAL_CSR_SAVE, - self.mdict['pki_external_csr_path']) - return - elif self.standalone and not self.external_step_two: + if self.standalone and not self.external_step_two: # Stand-alone PKI (Step 1) if cdata['tag'].lower() == "audit_signing": # Save Stand-alone PKI 'Audit Signing Certificate' CSR @@ -3991,8 +3979,17 @@ class ConfigClient: data.token = self.mdict['pki_token_name'] data.tokenPassword = self.mdict['pki_token_password'] data.subsystemName = self.mdict['pki_subsystem_name'] + + data.external = self.external data.standAlone = self.standalone - data.stepTwo = self.external_step_two + + if self.standalone: + # standalone installation uses two-step process (ticket #1698) + data.stepTwo = self.external_step_two + + else: + # other installations use only one step in the configuration servlet + data.stepTwo = False # Cloning parameters if self.mdict['pki_instance_type'] == "Tomcat": @@ -4122,25 +4119,46 @@ class ConfigClient: self.mdict['pki_req_ext_critical'] cert1.req_ext_data = \ self.mdict['pki_req_ext_data'] - if self.external_step_two: - # External CA (Step 2) or Stand-alone PKI (Step 2) - if not self.subsystem == "CA": - # Stand-alone PKI (Step 2) - cert1 = pki.system.SystemCertData() - cert1.tag = self.mdict['pki_ca_signing_tag'] - # Load the External CA or Stand-alone PKI + + if self.external and self.external_step_two: # external/existing CA step 2 + + # If specified, load the externally-signed CA cert + if self.mdict['pki_external_ca_cert_path']: + self.load_system_cert( + cert1, + log.PKI_CONFIG_EXTERNAL_CA_LOAD, + self.mdict['pki_external_ca_cert_path']) + + # If specified, load the external CA cert chain + if self.mdict['pki_external_ca_cert_chain_path']: + self.load_system_cert_chain( + cert1, + log.PKI_CONFIG_EXTERNAL_CA_CHAIN_LOAD, + self.mdict['pki_external_ca_cert_chain_path']) + + systemCerts.append(cert1) + + elif self.standalone and self.external_step_two: # standalone KRA/OCSP step 2 + + cert1 = pki.system.SystemCertData() + cert1.tag = self.mdict['pki_ca_signing_tag'] + + # Load the stand-alone PKI # 'External CA Signing Certificate' (Step 2) self.load_system_cert( cert1, log.PKI_CONFIG_EXTERNAL_CA_LOAD, self.mdict['pki_external_ca_cert_path']) - # Load the External CA or Stand-alone PKI + + # Load the stand-alone PKI # 'External CA Signing Certificate Chain' (Step 2) self.load_system_cert_chain( cert1, log.PKI_CONFIG_EXTERNAL_CA_CHAIN_LOAD, self.mdict['pki_external_ca_cert_chain_path']) + systemCerts.append(cert1) + elif self.subsystem == "CA": # PKI CA or Subordinate CA systemCerts.append(cert1) diff --git a/base/server/python/pki/server/deployment/scriptlets/configuration.py b/base/server/python/pki/server/deployment/scriptlets/configuration.py index c6e89023560fc92cb9bd451d9b7f05818807da8a..c28fb13b20349a9acd569f0ee26adecb9c16ff98 100644 --- a/base/server/python/pki/server/deployment/scriptlets/configuration.py +++ b/base/server/python/pki/server/deployment/scriptlets/configuration.py @@ -21,13 +21,18 @@ from __future__ import absolute_import import json +import re # PKI Deployment Imports from .. import pkiconfig as config from .. import pkimessages as log from .. import pkiscriptlet -import pki.system + import pki.encoder +import pki.nss +import pki.server +import pki.system +import pki.util # PKI Deployment Configuration Scriptlet @@ -81,6 +86,130 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet): deployer.mdict['pki_client_secmod_database'], password_file=deployer.mdict['pki_client_password_conf']) + instance = pki.server.PKIInstance(deployer.mdict['pki_instance_name']) + instance.load() + + subsystem = instance.get_subsystem(deployer.mdict['pki_subsystem'].lower()) + + token = deployer.mdict['pki_ca_signing_token'] + if token == 'Internal Key Storage Token': + token = 'internal' + + nssdb = instance.open_nssdb(token) + + external = config.str2bool(deployer.mdict['pki_external']) + step_one = not config.str2bool(deployer.mdict['pki_external_step_two']) + step_two = not step_one + + try: + if external and step_one: # external/existing CA step 1 + + key_type = deployer.mdict['pki_ca_signing_key_type'] + key_alg = deployer.mdict['pki_ca_signing_key_algorithm'] + + if key_type == 'rsa': + key_size = int(deployer.mdict['pki_ca_signing_key_size']) + curve = None + + m = re.match(r'(.*)withRSA', key_alg) + if not m: + raise Exception('Invalid key algorithm: %s' % key_alg) + hash_alg = m.group(1) + + elif key_type == 'ec' or key_type == 'ecc': + key_type = 'ec' + key_size = None + curve = deployer.mdict['pki_ca_signing_key_size'] + + m = re.match(r'(.*)withEC', key_alg) + if not m: + raise Exception('Invalid key algorithm: %s' % key_alg) + hash_alg = m.group(1) + + else: + raise Exception('Invalid key type: %s' % key_type) + + # If filename specified, generate CA cert request and + # import it into CS.cfg. + request_file = deployer.mdict['pki_external_csr_path'] + if request_file: + nssdb.create_request( + subject_dn=deployer.mdict['pki_ca_signing_subject_dn'], + request_file=request_file, + key_type=key_type, + key_size=key_size, + curve=curve, + hash_alg=hash_alg) + with open(request_file) as f: + signing_csr = f.read() + signing_csr = pki.nss.convert_csr(signing_csr, 'pem', 'base64') + subsystem.config['ca.signing.certreq'] = signing_csr + + subsystem.save() + + elif external and step_two: # external/existing CA step 2 + + # If specified, import existing CA cert request into CS.cfg. + request_file = deployer.mdict['pki_external_csr_path'] + if request_file: + with open(request_file) as f: + signing_csr = f.read() + signing_csr = pki.nss.convert_csr(signing_csr, 'pem', 'base64') + subsystem.config['ca.signing.certreq'] = signing_csr + + # If specified, import external CA cert into NSS database. + external_ca_cert_chain_nickname = deployer.mdict['pki_external_ca_cert_chain_nickname'] + external_ca_cert_chain_file = deployer.mdict['pki_external_ca_cert_chain_path'] + if external_ca_cert_chain_file: + cert_chain = nssdb.import_cert_chain( + nickname=external_ca_cert_chain_nickname, + cert_chain_file=external_ca_cert_chain_file, + trust_attributes='CT,C,C') + subsystem.config['ca.external_ca_chain.cert'] = cert_chain + + # If specified, import externally-signed CA cert into NSS database. + signing_nickname = deployer.mdict['pki_ca_signing_nickname'] + signing_cert_file = deployer.mdict['pki_external_ca_cert_path'] + if signing_cert_file: + nssdb.add_cert( + nickname=signing_nickname, + cert_file=signing_cert_file, + trust_attributes='CT,C,C') + + # If specified, import CA cert and key from PKCS #12 file into NSS database. + pkcs12_file = deployer.mdict['pki_external_pkcs12_path'] + if pkcs12_file: + pkcs12_password = deployer.mdict['pki_external_pkcs12_password'] + nssdb.import_pkcs12(pkcs12_file, pkcs12_password) + + # Export CA cert from NSS database and import it into CS.cfg. + signing_cert_data = nssdb.get_cert( + nickname=signing_nickname, + output_format='base64') + subsystem.config['ca.signing.nickname'] = signing_nickname + subsystem.config['ca.signing.tokenname'] = deployer.mdict['pki_ca_signing_token'] + subsystem.config['ca.signing.cert'] = signing_cert_data + subsystem.config['ca.signing.cacertnickname'] = signing_nickname + subsystem.config['ca.signing.defaultSigningAlgorithm'] = deployer.mdict['pki_ca_signing_signing_algorithm'] + + subsystem.save() + + else: # self-signed CA + + # To be implemented in ticket #1692. + + # Generate CA cert request. + # Self sign CA cert. + # Import self-signed CA cert into NSS database. + + pass + + finally: + nssdb.close() + + if external and step_one: + return self.rv + # Start/Restart this Tomcat PKI Process # Optionally prepare to enable a java debugger # (e. g. - 'eclipse'): diff --git a/base/server/python/pki/server/deployment/scriptlets/finalization.py b/base/server/python/pki/server/deployment/scriptlets/finalization.py index 56ddf0219d37dc7258e95464aff9ae925456a1a8..3c4f469aced9eec7928cf2c1a27ac43ebe5e1886 100644 --- a/base/server/python/pki/server/deployment/scriptlets/finalization.py +++ b/base/server/python/pki/server/deployment/scriptlets/finalization.py @@ -67,9 +67,15 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet): if len(deployer.instance.tomcat_instance_subsystems()) == 1: # Modify contents of 'serverCertNick.conf' (if necessary) deployer.servercertnick_conf.modify() - # Optionally, programmatically 'restart' the configured PKI instance - if config.str2bool(deployer.mdict['pki_restart_configured_instance']): - deployer.systemd.restart() + + external = config.str2bool(deployer.mdict['pki_external']) + step_one = not config.str2bool(deployer.mdict['pki_external_step_two']) + + if not (external and step_one): + # Optionally, programmatically 'restart' the configured PKI instance + if config.str2bool(deployer.mdict['pki_restart_configured_instance']): + deployer.systemd.restart() + # Optionally, 'purge' the entire temporary client infrastructure # including the client NSS security databases and password files # -- 2.4.3