>From 594ef4dfc40a7c3f063aabc61623b617ca5bff10 Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu@redhat.com>
Date: Thu, 25 Sep 2014 09:03:58 -0700
Subject: [PATCH] ticket #1110 pkispawn (configuration) does not provide CA
 extensions in subordinate certificate signing requests (CSR)

---
 .../netscape/certsrv/system/SystemCertData.java    | 40 +++++++++++
 .../cms/servlet/csadmin/ConfigurationUtils.java    | 78 +++++++++++++++++++++-
 .../dogtagpki/server/rest/SystemConfigService.java |  9 +++
 base/server/etc/default.cfg                        |  5 ++
 .../python/pki/server/deployment/pkihelper.py      | 25 +++++++
 .../python/pki/server/deployment/pkiparser.py      |  3 +
 .../com/netscape/cmsutil/crypto/CryptoUtil.java    | 49 +++++++++++++-
 7 files changed, 207 insertions(+), 2 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/system/SystemCertData.java b/base/common/src/com/netscape/certsrv/system/SystemCertData.java
index a509e3fb546edb4cedc215a7f27d5a5630cf74d1..064d8e190384d98fbb037bb2660276f34e20486a 100644
--- a/base/common/src/com/netscape/certsrv/system/SystemCertData.java
+++ b/base/common/src/com/netscape/certsrv/system/SystemCertData.java
@@ -43,6 +43,9 @@ public class SystemCertData {
     public static final String SUBJECT_DN = "subjectDN";
     public static final String CERT = "cert";
     public static final String CERT_CHAIN = "certChain";
+    public static final String REQUEST_EXT_OID = "req_ext_oid";
+    public static final String REQUEST_EXT_CRITICAL = "req_ext_critial";
+    public static final String REQUEST_EXT_DATA = "req_ext_data";
 
     @XmlElement
     protected String tag;
@@ -80,6 +83,15 @@ public class SystemCertData {
     @XmlElement
     protected String certChain;
 
+    @XmlElement
+    protected String req_ext_oid;
+
+    @XmlElement
+    protected String req_ext_critical;
+
+    @XmlElement
+    protected String req_ext_data;
+
     public SystemCertData() {
         // required for JAXB
     }
@@ -97,6 +109,10 @@ public class SystemCertData {
         subjectDN = form.getFirst(SUBJECT_DN);
         cert = form.getFirst(CERT);
         certChain = form.getFirst(CERT_CHAIN);
+        //support extension in CSR
+        req_ext_oid = form.getFirst(REQUEST_EXT_OID);
+        req_ext_critical = form.getFirst(REQUEST_EXT_CRITICAL);
+        req_ext_data = form.getFirst(REQUEST_EXT_DATA);
     }
 
     /**
@@ -267,4 +283,28 @@ public class SystemCertData {
         this.certChain = certChain;
     }
 
+    /**
+     * @return the req_ext_oid
+     */
+    public String getReqExtOID() {
+        return req_ext_oid;
+    }
+
+    /**
+     * @return the req_ext_data
+     */
+    public String getReqExtData() {
+        return req_ext_data;
+    }
+
+    /**
+     * @return the req_ext_critical
+     */
+    public boolean getReqExtCritical() {
+        if (req_ext_critical.equals("true"))
+            return true;
+        else
+            return false;
+    }
+
 }
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 06728e24b8b8674ede2af7e21b09c1e1b66614bb..f44323896aece9a0b8df9d9f4e6ae48f7baa77b5 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
@@ -74,8 +74,14 @@ import netscape.security.pkcs.ContentInfo;
 import netscape.security.pkcs.PKCS10;
 import netscape.security.pkcs.PKCS7;
 import netscape.security.pkcs.SignerInfo;
+import netscape.security.util.DerOutputStream;
+import netscape.security.util.ObjectIdentifier;
 import netscape.security.x509.AlgorithmId;
+import netscape.security.x509.BasicConstraintsExtension;
 import netscape.security.x509.CertificateChain;
+import netscape.security.x509.Extension;
+import netscape.security.x509.Extensions;
+import netscape.security.x509.KeyUsageExtension;
 import netscape.security.x509.X500Name;
 import netscape.security.x509.X509CertImpl;
 import netscape.security.x509.X509Key;
@@ -2680,9 +2686,11 @@ public class ConfigurationUtils {
             EBaseException, InvalidKeyException, NotInitializedException, TokenException, NoSuchAlgorithmException,
             NoSuchProviderException, CertificateException, SignatureException, IOException {
 
+        CMS.debug("ConfigurationUtils: handleCertRequest() begins");
         // get public key
         String pubKeyType = config.getString(PCERT_PREFIX + certTag + ".keytype");
         String algorithm = config.getString(PCERT_PREFIX + certTag + ".keyalgorithm");
+
         X509Key pubk = null;
         if (pubKeyType.equals("rsa")) {
             pubk = getRSAX509Key(config, certTag);
@@ -2713,7 +2721,13 @@ public class ConfigurationUtils {
         String caDN = config.getString(PCERT_PREFIX + certTag + ".dn");
 
         cert.setDN(caDN);
-        PKCS10 certReq = CryptoUtil.createCertificationRequest(caDN, pubk, privk, algorithm);
+
+        Extensions exts = null;
+        if (certTag.equals("signing")) {
+            CMS.debug("handleCertRequest: certTag is siging -- about to call createBasicCAExtensions()");
+            exts = createBasicCAExtensions(config);
+        }
+        PKCS10 certReq = CryptoUtil.createCertificationRequest(caDN, pubk, privk, algorithm, exts);
 
         CMS.debug("handleCertRequest: created cert request");
         byte[] certReqb = certReq.toByteArray();
@@ -2727,6 +2741,68 @@ public class ConfigurationUtils {
 
     }
 
+    /*
+     * createBasicCAExtensions creates the basic Extensions needed for a CSR to a
+     * CA signing certificate
+     */
+    private static Extensions createBasicCAExtensions(IConfigStore config) throws IOException {
+        Extensions exts = new Extensions();
+        CMS.debug("ConfigurationUtils: createBasicCAExtensions: begins");
+
+        // create BasicConstraintsExtension
+        BasicConstraintsExtension bcExt = new BasicConstraintsExtension(true, -1);
+        exts.add(bcExt);
+
+        // create KeyUsageExtension
+        boolean[] kuBits = new boolean[KeyUsageExtension.NBITS];
+        for (int i = 0; i < kuBits.length; i++) {
+            kuBits[i] = false;
+        }
+        kuBits[KeyUsageExtension.DIGITAL_SIGNATURE_BIT] = true;
+        kuBits[KeyUsageExtension.NON_REPUDIATION_BIT] = true;
+        kuBits[KeyUsageExtension.KEY_CERTSIGN_BIT] = true;
+        kuBits[KeyUsageExtension.CRL_SIGN_BIT] = true;
+        KeyUsageExtension kuExt = new KeyUsageExtension(true, kuBits);
+        exts.add(kuExt);
+
+        /* save this for later when we want to allow more selection for pkispawn configuration
+        // create NSCertTypeExtension
+        boolean[] nsBits = new boolean[NSCertTypeExtension.NBITS];
+        for (int i = 0; i < nsBits.length; i++) {
+            nsBits[i] = false;
+        }
+        nsBits[NSCertTypeExtension.SSL_CA_BIT] = true;
+        NSCertTypeExtension nsctExt = new NSCertTypeExtension(false, nsBits);
+        exts.add(nsctExt);
+        */
+
+        // add a generic extension
+        Extension genExt = null;
+        try {
+            String oidString = config.getString(PCERT_PREFIX + "signing.ext.oid");
+            String dataString = config.getString(PCERT_PREFIX + "signing.ext.data");
+            boolean critical = false;
+            if (oidString != null && dataString != null) {
+                CMS.debug("ConfigurationUtils: createBasicCAExtensions: processing generic extension");
+                critical = config.getBoolean("preop.cert.signing.ext.critical");
+                ObjectIdentifier oid = new ObjectIdentifier(oidString);
+
+                byte data[] = CryptoUtil.hexString2Bytes(dataString);
+                DerOutputStream out = new DerOutputStream();
+                out.putOctetString(data);
+                genExt = new Extension(oid, critical, out.toByteArray());
+                out.close();
+
+                exts.add(genExt);
+                CMS.debug("ConfigurationUtils: createBasicCAExtensions: generic extension added: " + oidString);
+            }
+        } catch (EBaseException e) {
+            CMS.debug("ConfigurationUtils: createBasicCAExtensions: generic extension not processed:" + e);
+        }
+
+        return exts;
+    }
+
     public static X509Key getECCX509Key(IConfigStore config, String certTag) throws EPropertyNotFound, EBaseException,
             InvalidKeyException {
         X509Key pubk = null;
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 96a6cf0bfaa76bec8599cfdfef98c061f7e400f7..fa762774af30c75d8929d854982f3d144a479821 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
@@ -309,6 +309,14 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
                     if (systemCert.getTag().equals(tag)) {
                         certData = systemCert;
                         CMS.debug("Found data for '" + tag + "'");
+                        if (tag.equals("signing") &&
+                                certData.getReqExtOID() != null &&
+                                certData.getReqExtData() != null) {
+                            CMS.debug("SystemConfigService:processCerts: adding request extension to config");
+                            cs.putString("preop.cert.signing.ext.oid", certData.getReqExtOID());
+                            cs.putString("preop.cert.signing.ext.data", certData.getReqExtData());
+                            cs.putBoolean("preop.cert.signing.ext.critical", certData.getReqExtCritical());
+                        }
                         break;
                     }
                 }
@@ -382,6 +390,7 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
                 cs.putString("preop.cert." + tag + ".signingalgorithm", signingalgorithm);
                 cs.putString("preop.cert." + tag + ".nickname", nickname);
                 cs.putString("preop.cert." + tag + ".dn", dn);
+                cs.commit(false);
 
                 if (!request.getStepTwo()) {
                     if (keytype.equals("ecc")) {
diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg
index 9047e6e7c75a2af746d157fd62ade84ad6f4b703..686cc59f9eeb4b1d29b9b57073d7d233729009c7 100644
--- a/base/server/etc/default.cfg
+++ b/base/server/etc/default.cfg
@@ -347,6 +347,11 @@ pki_ca_signing_signing_algorithm=SHA256withRSA
 pki_ca_signing_subject_dn=cn=CA Signing Certificate,o=%(pki_security_domain_name)s
 pki_ca_signing_token=Internal Key Storage Token
 pki_external=False
+pki_req_ext_add=False
+# MS subca request ext data
+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_step_two=False
 pki_external_ca_cert_chain_path=%(pki_instance_configuration_path)s/external_ca_chain.cert
diff --git a/base/server/python/pki/server/deployment/pkihelper.py b/base/server/python/pki/server/deployment/pkihelper.py
index a35b8f34701b0d1fcd07d4263ca9861ce8c7dfea..b4c728a87999224f526e2f6bb3a12488ea6adf41 100644
--- a/base/server/python/pki/server/deployment/pkihelper.py
+++ b/base/server/python/pki/server/deployment/pkihelper.py
@@ -447,7 +447,16 @@ class ConfigurationFile:
         self.mdict = deployer.mdict
         # set useful 'boolean' object variables for this class
         self.clone = config.str2bool(self.mdict['pki_clone'])
+        # generic extension support in CSR - for external CA
+        self.add_req_ext = config.str2bool(
+            self.mdict['pki_req_ext_add'])
         self.external = config.str2bool(self.mdict['pki_external'])
+        if self.external:
+            # generic extension support in CSR - for external CA
+            if self.add_req_ext:
+                self.req_ext_oid = self.mdict['pki_req_ext_oid']
+                self.req_ext_critical = self.mdict['pki_req_ext_critical']
+                self.req_ext_data = self.mdict['pki_req_ext_data']
         self.external_step_two = config.str2bool(
             self.mdict['pki_external_step_two'])
         self.skip_configuration = config.str2bool(
@@ -660,6 +669,11 @@ class ConfigurationFile:
                     # External CA (Step 1)
                     self.confirm_data_exists("pki_external_csr_path")
                     self.confirm_missing_file("pki_external_csr_path")
+                    # generic extension support in CSR - for external CA
+                    if self.add_req_ext:
+                        self.confirm_data_exists("pki_req_ext_oid")
+                        self.confirm_data_exists("pki_req_ext_critical")
+                        self.confirm_data_exists("pki_req_ext_data")
                 else:
                     # External CA (Step 2)
                     self.confirm_data_exists("pki_external_ca_cert_chain_path")
@@ -3397,6 +3411,9 @@ class ConfigClient:
         self.subordinate = config.str2bool(self.mdict['pki_subordinate'])
         # set useful 'string' object variables for this class
         self.subsystem = self.mdict['pki_subsystem']
+        # generic extension support in CSR - for external CA
+        self.add_req_ext = config.str2bool(
+            self.mdict['pki_req_ext_add'])
 
     def configure_pki_data(self, data):
         config.pki_log.info(
@@ -3715,6 +3732,14 @@ class ConfigClient:
                     cert1 = self.create_system_cert("ca_signing")
                     cert1.signingAlgorithm = \
                         self.mdict['pki_ca_signing_signing_algorithm']
+                    # generic extension support in CSR - for external CA
+                    if self.add_req_ext:
+                        cert1.req_ext_oid = \
+                            self.mdict['pki_req_ext_oid']
+                        cert1.req_ext_critical = \
+                            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":
diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
index 049ebf5188a9acba839eb3b30ea357cf128f2946..de224eb9a67181d6adccf39d6e59c28702cc0837 100644
--- a/base/server/python/pki/server/deployment/pkiparser.py
+++ b/base/server/python/pki/server/deployment/pkiparser.py
@@ -569,6 +569,9 @@ class PKIConfigParser:
             if not 'pki_external' in self.mdict or\
                not len(self.mdict['pki_external']):
                 self.mdict['pki_external'] = "false"
+            if not 'pki_req_ext_add' in self.mdict or\
+               not len(self.mdict['pki_req_ext_add']):
+                self.mdict['pki_req_ext_add'] = "false"
             if not 'pki_external_step_two' in self.mdict or\
                not len(self.mdict['pki_external_step_two']):
                 self.mdict['pki_external_step_two'] = "false"
diff --git a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
index ee077872ff140726a5c65d11435a806ddd9e021f..cf2bafac1e50780c20eb1104890edf430340c3ba 100644
--- a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
+++ b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
@@ -46,7 +46,10 @@ import java.util.Vector;
 import javax.crypto.SecretKey;
 
 import netscape.security.pkcs.PKCS10;
+import netscape.security.pkcs.PKCS10Attribute;
+import netscape.security.pkcs.PKCS10Attributes;
 import netscape.security.pkcs.PKCS7;
+import netscape.security.pkcs.PKCS9Attribute;
 import netscape.security.util.BigInt;
 import netscape.security.util.DerInputStream;
 import netscape.security.util.DerOutputStream;
@@ -62,6 +65,7 @@ import netscape.security.x509.CertificateSubjectName;
 import netscape.security.x509.CertificateValidity;
 import netscape.security.x509.CertificateVersion;
 import netscape.security.x509.CertificateX509Key;
+import netscape.security.x509.Extensions;
 import netscape.security.x509.X500Name;
 import netscape.security.x509.X500Signer;
 import netscape.security.x509.X509CertImpl;
@@ -1179,12 +1183,36 @@ public class CryptoUtil {
             throws NoSuchAlgorithmException, NoSuchProviderException,
                 InvalidKeyException, IOException, CertificateException,
                 SignatureException {
+        return createCertificationRequest(subjectName, pubk, prik, alg, null);
+    }
+
+    /*
+     * This createCertificationRequest() allows extensions to be added to the CSR
+     */
+    public static PKCS10 createCertificationRequest(String subjectName,
+            X509Key pubk, PrivateKey prik, String alg, Extensions exts)
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+                InvalidKeyException, IOException, CertificateException,
+                SignatureException {
         X509Key key = pubk;
         java.security.Signature sig = java.security.Signature.getInstance(alg,
                 "Mozilla-JSS");
 
         sig.initSign(prik);
-        PKCS10 pkcs10 = new PKCS10(key);
+        PKCS10 pkcs10 = null;
+
+        if (exts != null) {
+            PKCS10Attribute attr = new
+                    PKCS10Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID,
+                            exts);
+            PKCS10Attributes attrs = new PKCS10Attributes();
+
+            attrs.setAttribute(attr.getAttributeValue().getName(), attr);
+
+            pkcs10 = new PKCS10(key, attrs);
+        } else {
+            pkcs10 = new PKCS10(key);
+        }
         X500Name name = new X500Name(subjectName);
         X500Signer signer = new X500Signer(sig, name);
 
@@ -1350,6 +1378,25 @@ public class CryptoUtil {
     }
 
     /**
+     * Converts string containing pairs of characters in the range of '0'
+     * to '9', 'a' to 'f' to an array of bytes such that each pair of
+     * characters in the string represents an individual byte
+     */
+    public static byte[] hexString2Bytes(String string) {
+        if (string == null)
+            return null;
+        int stringLength = string.length();
+        if ((stringLength == 0) || ((stringLength % 2) != 0))
+            return null;
+        byte[] bytes = new byte[(stringLength / 2)];
+        for (int i = 0, b = 0; i < stringLength; i += 2, ++b) {
+            String nextByte = string.substring(i, (i + 2));
+            bytes[b] = (byte) Integer.parseInt(nextByte, 0x10);
+        }
+        return bytes;
+    }
+
+    /**
      * Retrieves a private key from a unique key ID.
      */
     public static PrivateKey findPrivateKeyFromID(byte id[])
-- 
1.8.4.2

