>From 863c1a227a352cb3c14022b14e112e8a732f79ec Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu@redhat.com>
Date: Wed, 15 Apr 2015 10:58:08 -0700
Subject: [PATCH] Ticket 1316 Allow adding SAN to server cert during the
 install process

---
 .../conf/serverCert.profile.exampleWithSANpattern  | 68 ++++++++++++++++
 .../profiles/ca/caInternalAuthServerCert.cfg       | 23 ++++++
 .../netscape/certsrv/system/SystemCertData.java    | 13 +++
 .../cms/profile/def/SubjectAltNameExtDefault.java  | 11 ++-
 .../com/netscape/cms/servlet/csadmin/CertUtil.java | 93 ++++++++++++++++++++--
 .../cms/servlet/csadmin/ConfigurationUtils.java    | 13 ++-
 .../dogtagpki/server/rest/SystemConfigService.java | 10 +++
 base/server/etc/default.cfg                        |  3 +
 .../python/pki/server/deployment/pkihelper.py      |  9 +++
 .../python/pki/server/deployment/pkiparser.py      |  3 +
 10 files changed, 236 insertions(+), 10 deletions(-)
 create mode 100644 base/ca/shared/conf/serverCert.profile.exampleWithSANpattern

diff --git a/base/ca/shared/conf/serverCert.profile.exampleWithSANpattern b/base/ca/shared/conf/serverCert.profile.exampleWithSANpattern
new file mode 100644
index 0000000000000000000000000000000000000000..5ca44270edb1ab365467f016054c1bae05d88377
--- /dev/null
+++ b/base/ca/shared/conf/serverCert.profile.exampleWithSANpattern
@@ -0,0 +1,68 @@
+#
+# Server Certificate
+#
+id=serverCert.profile
+name=All Purpose SSL server cert Profile
+description=This profile creates an SSL server certificate that is valid for SSL servers
+profileIDMapping=caServerCert
+profileSetIDMapping=serverCertSet
+list=2,4,5,6,7,8
+2.default.class=com.netscape.cms.profile.def.ValidityDefault
+2.default.name=Validity Default
+2.default.params.range=720
+2.default.params.startTime=0
+4.default.class=com.netscape.cms.profile.def.AuthorityKeyIdentifierExtDefault
+4.default.name=Authority Key Identifier Default
+5.default.class=com.netscape.cms.profile.def.AuthInfoAccessExtDefault
+5.default.name=AIA Extension Default
+5.default.params.authInfoAccessADEnable_0=true
+5.default.params.authInfoAccessADLocationType_0=URIName
+5.default.params.authInfoAccessADLocation_0=
+5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1
+5.default.params.authInfoAccessCritical=false
+5.default.params.authInfoAccessNumADs=1
+6.default.class=com.netscape.cms.profile.def.KeyUsageExtDefault
+6.default.name=Key Usage Default
+6.default.params.keyUsageCritical=true
+6.default.params.keyUsageDigitalSignature=true
+6.default.params.keyUsageNonRepudiation=true
+6.default.params.keyUsageDataEncipherment=true
+6.default.params.keyUsageKeyEncipherment=true
+6.default.params.keyUsageKeyAgreement=false
+6.default.params.keyUsageKeyCertSign=false
+6.default.params.keyUsageCrlSign=false
+6.default.params.keyUsageEncipherOnly=false
+6.default.params.keyUsageDecipherOnly=false
+7.default.class=com.netscape.cms.profile.def.ExtendedKeyUsageExtDefault
+7.default.name=Extended Key Usage Extension Default
+7.default.params.exKeyUsageCritical=false
+7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.1
+# allows SAN to be specified from client side
+# need to:
+# 1. add 8 to list above
+# 2. change below to reflect the number of general names, and
+#    turn each corresponding subjAltExtPattern_<num> to true
+#      8.default.params.subjAltNameNumGNs
+8.default.class=com.netscape.cms.profile.def.SubjectAltNameExtDefault
+8.default.name=Subject Alternative Name Extension Default
+8.default.params.subjAltExtGNEnable_0=true
+8.default.params.subjAltExtPattern_0=$request.req_san_pattern_0$
+8.default.params.subjAltExtType_0=DNSName
+8.default.params.subjAltExtGNEnable_1=true
+8.default.params.subjAltExtPattern_1=$request.req_san_pattern_1$
+8.default.params.subjAltExtType_1=DNSName
+8.default.params.subjAltExtGNEnable_2=true
+8.default.params.subjAltExtPattern_2=$request.req_san_pattern_2$
+8.default.params.subjAltExtType_2=DNSName
+8.default.params.subjAltExtGNEnable_3=true
+8.default.params.subjAltExtPattern_3=$request.req_san_pattern_3$
+8.default.params.subjAltExtType_3=DNSName
+8.default.params.subjAltExtType_4=OtherName
+8.default.params.subjAltExtSource_4=UUID4
+8.default.params.subjAltExtPattern_4=(IA5String)1.2.3.4,$server.source$
+8.default.params.subjAltExtGNEnable_4=true
+8.default.params.subjAltExtType_5=DNSName
+8.default.params.subjAltExtPattern_5=myhost.example.com
+8.default.params.subjAltExtGNEnable_5=true
+8.default.params.subjAltNameExtCritical=false
+8.default.params.subjAltNameNumGNs=6
diff --git a/base/ca/shared/profiles/ca/caInternalAuthServerCert.cfg b/base/ca/shared/profiles/ca/caInternalAuthServerCert.cfg
index 71935108080b8dce4fab2ef3901fa0f1582c2801..f145325f0c840ee406bdae0e1e7e52f62ac3fb3b 100644
--- a/base/ca/shared/profiles/ca/caInternalAuthServerCert.cfg
+++ b/base/ca/shared/profiles/ca/caInternalAuthServerCert.cfg
@@ -8,6 +8,7 @@ name=Security Domain Server Certificate Enrollment
 input.list=i1,i2
 input.i1.class_id=certReqInputImpl
 input.i2.class_id=submitterInfoInputImpl
+input.i3.class_id=subjectAltNameExtInputImpl
 output.list=o1
 output.o1.class_id=certOutputImpl
 policyset.list=serverCertSet
@@ -84,3 +85,25 @@ policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA25
 policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl
 policyset.serverCertSet.8.default.name=Signing Alg
 policyset.serverCertSet.8.default.params.signingAlg=-
+# allows SAN to be specified from client side
+# need to:
+# 1. add i3 to input.list above
+# 2. add 9 to policyset.serverCertSet.list above
+# 3. change below to reflect the number of general names, and
+#    turn each corresponding subjAltExtPattern_<num> to true
+#      policyset.serverCertSet.9.default.params.subjAltNameNumGNs
+policyset.serverCertSet.9.constraint.class_id=noConstraintImpl
+policyset.serverCertSet.9.constraint.name=No Constraint
+policyset.serverCertSet.9.default.class_id=subjectAltNameExtDefaultImpl
+policyset.serverCertSet.9.default.name=Subject Alternative Name Extension Default
+policyset.serverCertSet.9.default.params.subjAltExtGNEnable_0=true
+policyset.serverCertSet.9.default.params.subjAltExtPattern_0=$request.req_san_pattern_0$
+policyset.serverCertSet.9.default.params.subjAltExtType_0=DNSName
+policyset.serverCertSet.9.default.params.subjAltExtGNEnable_1=false
+policyset.serverCertSet.9.default.params.subjAltExtPattern_1=$request.req_san_pattern_1$
+policyset.serverCertSet.9.default.params.subjAltExtType_1=DNSName
+policyset.serverCertSet.9.default.params.subjAltExtGNEnable_2=false
+policyset.serverCertSet.9.default.params.subjAltExtPattern_2=$request.req_san_pattern_2$
+policyset.serverCertSet.9.default.params.subjAltExtType_2=DNSName
+policyset.serverCertSet.9.default.params.subjAltNameExtCritical=false
+policyset.serverCertSet.9.default.params.subjAltNameNumGNs=1
diff --git a/base/common/src/com/netscape/certsrv/system/SystemCertData.java b/base/common/src/com/netscape/certsrv/system/SystemCertData.java
index 064d8e190384d98fbb037bb2660276f34e20486a..ded3822a929a1ddb42612d997e0956cfb5a647b1 100644
--- a/base/common/src/com/netscape/certsrv/system/SystemCertData.java
+++ b/base/common/src/com/netscape/certsrv/system/SystemCertData.java
@@ -46,6 +46,7 @@ public class SystemCertData {
     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";
+    public static final String SERVER_CERT_SAN = "san_server_cert";
 
     @XmlElement
     protected String tag;
@@ -92,6 +93,9 @@ public class SystemCertData {
     @XmlElement
     protected String req_ext_data;
 
+    @XmlElement
+    protected String san_server_cert;
+
     public SystemCertData() {
         // required for JAXB
     }
@@ -113,6 +117,8 @@ public class SystemCertData {
         req_ext_oid = form.getFirst(REQUEST_EXT_OID);
         req_ext_critical = form.getFirst(REQUEST_EXT_CRITICAL);
         req_ext_data = form.getFirst(REQUEST_EXT_DATA);
+        //support SAN in server cert
+        san_server_cert = form.getFirst(SERVER_CERT_SAN);
     }
 
     /**
@@ -307,4 +313,11 @@ public class SystemCertData {
             return false;
     }
 
+    /**
+     * @return the server cert SAN
+     */
+    public String getServerCertSAN() {
+        return san_server_cert;
+    }
+
 }
diff --git a/base/server/cms/src/com/netscape/cms/profile/def/SubjectAltNameExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/SubjectAltNameExtDefault.java
index 240f86a13a600ba1179b274fc71f988c9a3bf6c6..ca3d05f25ccde4729b4a02ee9279f5996b83d53f 100644
--- a/base/server/cms/src/com/netscape/cms/profile/def/SubjectAltNameExtDefault.java
+++ b/base/server/cms/src/com/netscape/cms/profile/def/SubjectAltNameExtDefault.java
@@ -450,6 +450,7 @@ public class SubjectAltNameExtDefault extends EnrollExtDefault {
                 if (!pattern.equals("")) {
                     CMS.debug("SubjectAltNameExtDefault: createExtension() pattern="+ pattern);
                     String gname = "";
+                    String gtype = "";
 
                     // cfu - see if this is server-generated (e.g. UUID4)
                     // to use this feature, use $server.source$ in pattern
@@ -479,16 +480,18 @@ public class SubjectAltNameExtDefault extends EnrollExtDefault {
                     } else {
                         if (request != null) {
                             gname = mapPattern(request, pattern);
+                            gtype = mapPattern(request, type);
                         }
                     }
 
-                    if (gname.equals("") || gname.contains("$")) {
-                        CMS.debug("ubjectAltNameExtDefault: mapPattern()failed. Not added. gname="+ gname);
+                    if (gname.equals("") ||
+                        gname.startsWith(CONFIG_SAN_REQ_PATTERN_PREFIX)) {
+                        CMS.debug("SubjectAltNameExtDefault: gname is empty,not added.");
                         continue;
                     }
-                    CMS.debug("SubjectAltNameExtDefault: createExtension got gname=" + gname);
+                    CMS.debug("SubjectAltNameExtDefault: createExtension got gname=" +gname + " with type=" + gtype);
 
-                    GeneralNameInterface n = parseGeneralName(type + ":" + gname);
+                    GeneralNameInterface n = parseGeneralName(gtype + ":" + gname);
 
                     CMS.debug("adding gname: " + gname);
                     if (n != null) {
diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
index 22f092973b92137f7df677dad6e87f020dc28681..4215b249b725d8e0a98745cfba584bf4fb50429f 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java
@@ -26,6 +26,7 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.Properties;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -206,6 +207,81 @@ public class CertUtil {
         }
     }
 
+ 
+    // Dynamically inject the SubjectAlternativeName extension to a
+    // local/self-signed master CA's request for its SSL Server Certificate.
+    //
+    // Since this information may vary from instance to
+    // instance, obtain the necessary information from the
+    // 'service.sslserver.san' value(s) in the instance's
+    // CS.cfg, process these values converting each item into
+    // its individual SubjectAlternativeName components, and
+    // inject these values into the local request.
+    //
+    public static void injectSANextensionIntoRequest(IConfigStore config,
+                           IRequest req) throws Exception {
+        CMS.debug("CertUtil::injectSANextensionIntoRequest() - injecting SAN " +
+                  "entries into request . . .");
+        int i = 0;
+        String sanHostnames = config.getString("service.sslserver.san");
+        StringTokenizer st = new StringTokenizer(sanHostnames, ",");
+        while (st.hasMoreTokens()) {
+            String sanHostname = st.nextToken();
+            CMS.debug("CertUtil: injectSANextensionIntoRequest() injecting " +
+                      "SAN hostname: " + sanHostname);
+            req.setExtData("req_san_pattern_" + i, sanHostname);
+            i++;
+        }
+        CMS.debug("CertUtil: injectSANextensionIntoRequest() " + "injected " +
+                  i + " SAN entries into request.");
+    }
+
+    // Dynamically apply the SubjectAlternativeName extension to a
+    // remote PKI instance's request for its SSL Server Certificate.
+    //
+    // Since this information may vary from instance to
+    // instance, obtain the necessary information from the
+    // 'service.sslserver.san' value(s) in the instance's
+    // CS.cfg, process these values converting each item into
+    // its individual SubjectAlternativeName components, and
+    // build an SSL Server Certificate URL extension consisting
+    // of this information.
+    //
+    // 03/27/2013 - Should consider removing this
+    //              "buildSANSSLserverURLExtension()"
+    //              method if it becomes possible to
+    //              embed a certificate extension into
+    //              a PKCS #10 certificate request.
+    //
+    public static String buildSANSSLserverURLExtension(IConfigStore config)
+           throws Exception {
+        String url = "";
+        String entries = "";
+
+        CMS.debug("CertUtil: buildSANSSLserverURLExtension() " +
+                  "building SAN SSL Server Certificate URL extension . . .");
+        int i = 0;
+        String sanHostnames = config.getString("service.sslserver.san");
+        StringTokenizer st = new StringTokenizer(sanHostnames, ",");
+        while (st.hasMoreTokens()) {
+            String sanHostname = st.nextToken();
+            CMS.debug("CertUtil: buildSANSSLserverURLExtension() processing " +
+                      "SAN hostname: " + sanHostname);
+            // Add the DNSName for all SANs
+            entries = entries +
+                      "&req_san_pattern_" + i + "=" + sanHostname;
+            i++;
+        }
+
+        url = "&req_san_entries=" + i + entries;
+
+        CMS.debug("CertUtil: buildSANSSLserverURLExtension() " + "placed " +
+                  i + " SAN entries into SSL Server Certificate URL.");
+
+        return url;
+    }
+
+
     /*
      * create requests so renewal can work on these initial certs
      */
@@ -375,6 +451,9 @@ public class CertUtil {
         IRequest req = null;
 
         try {
+            Boolean injectSAN = config.getBoolean(
+                                      "service.injectSAN", false);
+            CMS.debug("createLocalCert: injectSAN=" + injectSAN);
             String dn = config.getString(prefix + certTag + ".dn");
             String keyAlgorithm = null;
             Date date = new Date();
@@ -426,6 +505,10 @@ public class CertUtil {
                 queue = ca.getRequestQueue();
                 if (queue != null) {
                     req = createLocalRequest(queue, serialNo.toString(), info);
+                    if (certTag.equals("sslserver") &&
+                        injectSAN == true) {
+                          injectSANextensionIntoRequest(config, req);
+                    }
                     CMS.debug("CertUtil profile name= " + profile);
                     req.setExtData("req_key", x509key.toString());
 
@@ -498,7 +581,7 @@ public class CertUtil {
             }
         } catch (Exception e) {
             CMS.debug(e);
-            CMS.debug("NamePanel configCert() exception caught:" + e.toString());
+            CMS.debug("CertUtil createLocalCert() exception caught:" + e.toString());
         }
 
         if (cr == null) {
@@ -520,22 +603,22 @@ public class CertUtil {
                     cert.getSerialNumber(), cert, meta);
         } catch (Exception e) {
             CMS.debug(
-                    "NamePanel configCert: failed to add metainfo. Exception: " + e.toString());
+                    "CertUtil createLocalCert: failed to add metainfo. Exception: " + e.toString());
         }
 
         try {
             cr.addCertificateRecord(record);
             CMS.debug(
-                    "NamePanel configCert: finished adding certificate record.");
+                    "CertUtil createLocalCert: finished adding certificate record.");
         } catch (Exception e) {
             CMS.debug(
-                    "NamePanel configCert: failed to add certificate record. Exception: "
+                    "CertUtil createLocalCert: failed to add certificate record. Exception: "
                             + e.toString());
             try {
                 cr.deleteCertificateRecord(record.getSerialNumber());
                 cr.addCertificateRecord(record);
             } catch (Exception ee) {
-                CMS.debug("NamePanel update: Exception: " + ee.toString());
+                CMS.debug("CertUtil createLocalCert: Exception: " + ee.toString());
             }
         }
 
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 21aaf203bb89a330698cdbc43e4dd1fa8f36854b..1765ba7a61237297f502c5ac582415fa59ab6db7 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
@@ -2492,11 +2492,22 @@ public class ConfigurationUtils {
                     } catch (Exception ee) {
                     }
 
+                    String sslserver_extension = "";
+                    Boolean injectSAN = config.getBoolean(
+                                      "service.injectSAN", false);
+                    CMS.debug("ConfigurationUtils: injectSAN="+injectSAN);
+                    if (certTag.equals("sslserver") &&
+                        injectSAN == true) {
+                        sslserver_extension = 
+                            CertUtil.buildSANSSLserverURLExtension(config);
+                    }
+
                     String content =
                             "requestor_name="
                                     + sysType + "-" + machineName + "-" + securePort + "&profileId=" + profileId
                                     + "&cert_request_type=pkcs10&cert_request=" + URLEncoder.encode(pkcs10, "UTF-8")
-                                    + "&xmlOutput=true&sessionID=" + session_id;
+                                    + "&xmlOutput=true&sessionID=" + session_id
+                                    + sslserver_extension;
                     cert = CertUtil.createRemoteCert(ca_hostname, ca_port,
                             content, response, panel);
                     if (cert == 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 7067c24ec5a9ba2e27e246277b3d42d4f838e0a8..12dd54dac37f9677ca9cddfefc9c870a53ca671b 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
@@ -410,6 +410,16 @@ 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);
+
+                // support injecting SAN into server cert
+                if ( tag.equals("sslserver") && certData.getServerCertSAN() != null) {
+                    CMS.debug("updateConfiguration(): san_server_cert found");
+                    cs.putString("service.injectSAN", "true");
+                    cs.putString("service.sslserver.san", certData.getServerCertSAN());
+                } else {
+                    if ( tag.equals("sslserver"))
+                        CMS.debug("SystemConfigService:processCerts(): san_server_cert not found for tag sslserver");
+                }
                 cs.commit(false);
 
                 if (!request.getStepTwo()) {
diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg
index 8771c09b0e0f2ab87c06b616859591f3f231711b..4e3bd2a88b64527b3e5c426104ef80bb23b2e647 100644
--- a/base/server/etc/default.cfg
+++ b/base/server/etc/default.cfg
@@ -101,6 +101,9 @@ pki_security_domain_https_port=8443
 pki_security_domain_name=%(pki_dns_domainname)s Security Domain
 pki_security_domain_password=
 pki_security_domain_user=caadmin
+#for supporting server cert SAN injection
+pki_san_inject=False
+pki_san_server_cert=
 pki_skip_configuration=False
 pki_skip_installation=False
 pki_ssl_server_key_algorithm=SHA256withRSA
diff --git a/base/server/python/pki/server/deployment/pkihelper.py b/base/server/python/pki/server/deployment/pkihelper.py
index 665922c64f78ae723876cad93b622c66c910e5db..15e1f0ed6ad4def9efa8a1b92934255896e77940 100644
--- a/base/server/python/pki/server/deployment/pkihelper.py
+++ b/base/server/python/pki/server/deployment/pkihelper.py
@@ -463,6 +463,11 @@ class ConfigurationFile:
             self.mdict['pki_skip_configuration'])
         self.standalone = config.str2bool(self.mdict['pki_standalone'])
         self.subordinate = config.str2bool(self.mdict['pki_subordinate'])
+        # server cert san injection support
+        self.san_inject = config.str2bool(self.mdict['pki_san_inject'])
+        if self.san_inject:
+            self.confirm_data_exists('pki_san_server_cert')
+            self.san_server_cert = self.mdict['pki_san_server_cert']
         # set useful 'string' object variables for this class
         self.subsystem = self.mdict['pki_subsystem']
 
@@ -3432,6 +3437,7 @@ class ConfigClient:
         self.add_req_ext = config.str2bool(
             self.mdict['pki_req_ext_add'])
         self.security_domain_type = self.mdict['pki_security_domain_type']
+        self.san_inject = config.str2bool(self.mdict['pki_san_inject'])
 
     def configure_pki_data(self, data):
         config.pki_log.info(
@@ -4127,6 +4133,9 @@ class ConfigClient:
         cert.nickname = self.mdict["pki_%s_nickname" % tag]
         cert.subjectDN = self.mdict["pki_%s_subject_dn" % tag]
         cert.token = self.mdict["pki_%s_token" % tag]
+        if tag == 'ssl_server' and self.san_inject:
+            cert.san_server_cert = \
+                self.mdict['pki_san_server_cert']
         return cert
 
     def retrieve_existing_server_cert(self, cfg_file):
diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
index e37b0e4a563030145a39cd911064830926f79dc3..e93f1717e09904d021fbdccdb3124b6a7fd8f6a1 100644
--- a/base/server/python/pki/server/deployment/pkiparser.py
+++ b/base/server/python/pki/server/deployment/pkiparser.py
@@ -588,6 +588,9 @@ class PKIConfigParser:
             if not 'pki_subordinate' in self.mdict or\
                not len(self.mdict['pki_subordinate']):
                 self.mdict['pki_subordinate'] = "false"
+            if not 'pki_san_inject' in self.mdict or\
+               not len(self.mdict['pki_san_inject']):
+                self.mdict['pki_san_inject'] = "false"
 
             # PKI Target (slot substitution) name/value pairs
             self.mdict['pki_target_cs_cfg'] = \
-- 
1.8.4.2

