[Pki-devel] [PATCH] 657 Refactored CA certificate generation.

Endi Sukma Dewata edewata at redhat.com
Mon Nov 23 19:54:20 UTC 2015


Thanks for the feedback. New patch attached.

On 11/16/2015 7:48 PM, Christina Fu wrote:
> 1 in base/server/python/pki/server/deployment/scriptlets/configuration.py
> doesn't this just add the leaf cert rather than the whole chain? In
> other words, if your chain contains 2 or more certs, only the leaf subca
> cert is added, isn't it?
>
> +                    nssdb.add_cert(
> +                        nickname=external_ca_nickname,
> +                        cert_file=external_ca_cert_chain_file,
> +                        trust_attributes='CTu,CTu,CTu')

Fixed. The new patch now supports PKCS #7 file, a single PEM cert, and 
the base-64 PKCS #7 data generated by getCertChain servlet.

> 2 Also in the same file
> + # If specified, import externally-signed CA cert in NSS database.
> ...
> Shouldn't there be a case when the externally signed ca keys were
> generated on the hsm, you'd then need to import the issued externally
> signed ca cert into the hsm db as well?

If the externally-signed CA cert is specified in 
pki_external_ca_cert_path parameter it will be imported into the NSS 
database, regardless whether HSM is used. The code now calls certutil 
with -h option to specify the target token. Alternatively, the 
certificate can be imported manually before starting step 2. I've 
updated the docs: 
http://pki.fedoraproject.org/wiki/Installing_with_Externaly-Signed_CA_Certificate

> 3 base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
> I"m not seeing the following method being called, yet the getExternal()
> is being called...did I miss something?
>
> +    public void setExternal(Boolean external) {
>
> +        this.external = external;
> +    }

The external attribute is set in Python in pkihelper.py:3983:

   data.external = self.external

The value will be sent to the server via REST interface. The 
getExternal() will read that value.

> 4. base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
> +    public static void loadCert(Cert cert) throws Exception {
> ...
> +        // create certificate record to reserve the serial number in internal database
> +        ICertRecord record = cr.createCertRecord(serialNo, x509CertImpl, meta);
> +        cr.addCertificateRecord(record);
>
> In case of an externally signed ca or existing ca, why would you need to
> reserve the serial number or even add in the certificate repository?

Fixed. This code is actually only needed when importing existing 
self-signed CA cert. This way when the code generates the system 
certificates it will not conflict with the CA cert's serial number both 
in NSS database and in internal LDAP database. For existing 
non-self-signed CA cert or externally signed CA cert the code will not 
create the LDAP record.

> 5.
> Finally, please add comments to explain the cases for clarification...
> such as "stand-alone v.s. external; step 1, step 2, etc."  For example,
> it seems the "external" could imply "existing" as well in terms of ca
> cert, you might want to put in comment.

Yes, the "external" code handles both external CA and existing CA cases. 
I've added some inline comments. Please let me know if we need more.

-- 
Endi S. Dewata
-------------- next part --------------
>From cce2c2ea05142fe71c418e5288506dd8dfce4a14 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
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



More information about the Pki-devel mailing list