Repository: nifi
Updated Branches:
  refs/heads/master 71e2061b5 -> 6fc30900b


NIFI-3331 TLS Toolkit - add the possibility to define SAN in issued 
certificates.
Added unit tests for SAN inclusion in 
CertificateUtils#generateIssuedCertificate() and 
TlsHelper#generateCertificationRequest().
Fixed typos.

This closes #1491.

Signed-off-by: Andy LoPresto <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/6fc30900
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/6fc30900
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/6fc30900

Branch: refs/heads/master
Commit: 6fc30900b94308b8651eee368c39e272fe8e40de
Parents: 71e2061
Author: Andy LoPresto <[email protected]>
Authored: Thu Feb 9 14:47:39 2017 +0100
Committer: Andy LoPresto <[email protected]>
Committed: Tue Feb 14 23:09:51 2017 -0800

----------------------------------------------------------------------
 .../nifi/security/util/CertificateUtils.java    |  50 ++++++
 .../security/util/CertificateUtilsTest.groovy   | 170 ++++++++++++-------
 .../toolkit/tls/configuration/TlsConfig.java    |   9 +
 ...lsCertificateAuthorityClientCommandLine.java |  23 ++-
 .../TlsCertificateSigningRequestPerformer.java  |  36 ++--
 .../TlsCertificateAuthorityServiceHandler.java  |   4 +-
 .../tls/standalone/TlsToolkitStandalone.java    |   4 +-
 .../apache/nifi/toolkit/tls/util/TlsHelper.java |  66 ++++---
 ...sCertificateSigningRequestPerformerTest.java |   2 +-
 ...sCertificateAuthorityServiceHandlerTest.java |   2 +-
 .../nifi/toolkit/tls/util/TlsHelperTest.java    | 118 ++++++++++---
 11 files changed, 345 insertions(+), 139 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
 
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 1a26cf4..4d80336 100644
--- 
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ 
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -17,7 +17,12 @@
 package org.apache.nifi.security.util;
 
 import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
 import org.bouncycastle.asn1.x500.RDN;
 import org.bouncycastle.asn1.x500.X500Name;
@@ -25,6 +30,7 @@ import org.bouncycastle.asn1.x500.style.BCStyle;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
 import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
@@ -37,6 +43,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -124,6 +131,7 @@ public final class CertificateUtils {
             this.description = description;
         }
 
+        @Override
         public String toString() {
             return "Client Auth: " + this.description + " (" + this.value + 
")";
         }
@@ -541,6 +549,24 @@ public final class CertificateUtils {
      */
     public static X509Certificate generateIssuedCertificate(String dn, 
PublicKey publicKey, X509Certificate issuer, KeyPair issuerKeyPair, String 
signingAlgorithm, int days)
             throws CertificateException {
+        return generateIssuedCertificate(dn, publicKey, null, issuer, 
issuerKeyPair, signingAlgorithm, days);
+    }
+
+    /**
+     * Generates an issued {@link X509Certificate} from the given issuer 
certificate and {@link KeyPair}
+     *
+     * @param dn the distinguished name to use
+     * @param publicKey the public key to issue the certificate to
+     * @param extensions extensions extracted from the CSR
+     * @param issuer the issuer's certificate
+     * @param issuerKeyPair the issuer's keypair
+     * @param signingAlgorithm the signing algorithm to use
+     * @param days the number of days it should be valid for
+     * @return an issued {@link X509Certificate} from the given issuer 
certificate and {@link KeyPair}
+     * @throws CertificateException if there is an error issuing the 
certificate
+     */
+    public static X509Certificate generateIssuedCertificate(String dn, 
PublicKey publicKey, Extensions extensions, X509Certificate issuer, KeyPair 
issuerKeyPair, String signingAlgorithm, int days)
+            throws CertificateException {
         try {
             ContentSigner sigGen = new 
JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(issuerKeyPair.getPrivate());
             SubjectPublicKeyInfo subPubKeyInfo = 
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
@@ -567,6 +593,11 @@ public final class CertificateUtils {
             // (2) extendedKeyUsage extension
             certBuilder.addExtension(Extension.extendedKeyUsage, false, new 
ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, 
KeyPurposeId.id_kp_serverAuth}));
 
+            // (3) subjectAlternativeName
+            if(extensions != null && 
extensions.getExtension(Extension.subjectAlternativeName) != null) {
+                certBuilder.addExtension(Extension.subjectAlternativeName, 
false, extensions.getExtensionParsedValue(Extension.subjectAlternativeName));
+            }
+
             X509CertificateHolder certificateHolder = 
certBuilder.build(sigGen);
             return new 
JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
         } catch (CertIOException | NoSuchAlgorithmException | 
OperatorCreationException e) {
@@ -613,6 +644,25 @@ public final class CertificateUtils {
         }
     }
 
+    /**
+     * Extract extensions from CSR object
+     */
+    public static Extensions 
getExtensionsFromCSR(JcaPKCS10CertificationRequest csr) {
+        Attribute[] attributess = 
csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        for (Attribute attribute : attributess) {
+            ASN1Set attValue = attribute.getAttrValues();
+            if (attValue != null) {
+                ASN1Encodable extension = attValue.getObjectAt(0);
+                if (extension instanceof Extensions) {
+                    return (Extensions) extension;
+                } else if (extension instanceof DERSequence) {
+                    return Extensions.getInstance(extension);
+                }
+            }
+        }
+        return null;
+    }
+
     private CertificateUtils() {
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
 
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
index 81e8cca..5deb278 100644
--- 
a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
+++ 
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
@@ -16,6 +16,11 @@
  */
 package org.apache.nifi.security.util
 
+import org.bouncycastle.asn1.x509.Extension
+import org.bouncycastle.asn1.x509.Extensions
+import org.bouncycastle.asn1.x509.ExtensionsGenerator
+import org.bouncycastle.asn1.x509.GeneralName
+import org.bouncycastle.asn1.x509.GeneralNames
 import org.bouncycastle.operator.OperatorCreationException
 import org.junit.After
 import org.junit.Before
@@ -51,17 +56,18 @@ import static org.junit.Assert.assertTrue
 
 @RunWith(JUnit4.class)
 class CertificateUtilsTest extends GroovyTestCase {
-    private static final Logger logger = 
LoggerFactory.getLogger(CertificateUtilsTest.class);
+    private static final Logger logger = 
LoggerFactory.getLogger(CertificateUtilsTest.class)
 
-    private static final int KEY_SIZE = 2048;
+    private static final int KEY_SIZE = 2048
 
-    private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 
* 60 * 1000;
-    private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 
365 * 24 * 60 * 60 * 1000;
-    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
-    private static final String PROVIDER = "BC";
+    private static final int DAYS_IN_YEAR = 365
+    private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 
* 60 * 1000
+    private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 
365 * 24 * 60 * 60 * 1000
+    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"
+    private static final String PROVIDER = "BC"
 
-    private static final String SUBJECT_DN = "CN=NiFi Test 
Server,OU=Security,O=Apache,ST=CA,C=US";
-    private static final String ISSUER_DN = "CN=NiFi Test 
CA,OU=Security,O=Apache,ST=CA,C=US";
+    private static final String SUBJECT_DN = "CN=NiFi Test 
Server,OU=Security,O=Apache,ST=CA,C=US"
+    private static final String ISSUER_DN = "CN=NiFi Test 
CA,OU=Security,O=Apache,ST=CA,C=US"
 
     @BeforeClass
     static void setUpOnce() {
@@ -88,9 +94,9 @@ class CertificateUtilsTest extends GroovyTestCase {
      * @throws java.security.NoSuchAlgorithmException if the RSA algorithm is 
not available
      */
     private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
-        KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("RSA");
-        keyPairGenerator.initialize(KEY_SIZE);
-        return keyPairGenerator.generateKeyPair();
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
+        keyPairGenerator.initialize(KEY_SIZE)
+        return keyPairGenerator.generateKeyPair()
     }
 
     /**
@@ -108,8 +114,8 @@ class CertificateUtilsTest extends GroovyTestCase {
      */
     private
     static X509Certificate generateCertificate(String dn) throws IOException, 
NoSuchAlgorithmException, CertificateException, NoSuchProviderException, 
SignatureException, InvalidKeyException, OperatorCreationException {
-        KeyPair keyPair = generateKeyPair();
-        return CertificateUtils.generateSelfSignedX509Certificate(keyPair, dn, 
SIGNATURE_ALGORITHM, 365);
+        KeyPair keyPair = generateKeyPair()
+        return CertificateUtils.generateSelfSignedX509Certificate(keyPair, dn, 
SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
     }
 
     /**
@@ -129,15 +135,15 @@ class CertificateUtilsTest extends GroovyTestCase {
      */
     private
     static X509Certificate generateIssuedCertificate(String dn, 
X509Certificate issuer, KeyPair issuerKey) throws IOException, 
NoSuchAlgorithmException, CertificateException, NoSuchProviderException, 
SignatureException, InvalidKeyException, OperatorCreationException {
-        KeyPair keyPair = generateKeyPair();
-        return CertificateUtils.generateIssuedCertificate(dn, 
keyPair.getPublic(), issuer, issuerKey, SIGNATURE_ALGORITHM, 365);
+        KeyPair keyPair = generateKeyPair()
+        return CertificateUtils.generateIssuedCertificate(dn, 
keyPair.getPublic(), issuer, issuerKey, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
     }
 
     private static X509Certificate[] generateCertificateChain(String dn = 
SUBJECT_DN, String issuerDn = ISSUER_DN) {
-        final KeyPair issuerKeyPair = generateKeyPair();
+        final KeyPair issuerKeyPair = generateKeyPair()
 
-        final X509Certificate issuerCertificate = 
CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, 
SIGNATURE_ALGORITHM, 365);
-        final X509Certificate certificate = generateIssuedCertificate(dn, 
issuerCertificate, issuerKeyPair);
+        final X509Certificate issuerCertificate = 
CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, 
SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
+        final X509Certificate certificate = generateIssuedCertificate(dn, 
issuerCertificate, issuerKeyPair)
         [certificate, issuerCertificate] as X509Certificate[]
     }
 
@@ -150,7 +156,7 @@ class CertificateUtilsTest extends GroovyTestCase {
     }
 
     private static Date inFuture(int days) {
-        return new Date(System.currentTimeMillis() + 
TimeUnit.DAYS.toMillis(days));
+        return new Date(System.currentTimeMillis() + 
TimeUnit.DAYS.toMillis(days))
     }
 
     @Test
@@ -429,76 +435,72 @@ class CertificateUtilsTest extends GroovyTestCase {
         assert !dn1MatchesEmpty
     }
 
-
-
     @Test
     public void testShouldGenerateSelfSignedCert() throws Exception {
-        String dn = "CN=testDN,O=testOrg";
+        String dn = "CN=testDN,O=testOrg"
 
-        int days = 365;
-        X509Certificate x509Certificate = 
CertificateUtils.generateSelfSignedX509Certificate(generateKeyPair(), dn, 
SIGNATURE_ALGORITHM, days);
+        int days = 365
+        X509Certificate x509Certificate = 
CertificateUtils.generateSelfSignedX509Certificate(generateKeyPair(), dn, 
SIGNATURE_ALGORITHM, days)
 
-        Date notAfter = x509Certificate.getNotAfter();
-        assertTrue(notAfter.after(inFuture(days - 1)));
-        assertTrue(notAfter.before(inFuture(days + 1)));
+        Date notAfter = x509Certificate.getNotAfter()
+        assertTrue(notAfter.after(inFuture(days - 1)))
+        assertTrue(notAfter.before(inFuture(days + 1)))
 
-        Date notBefore = x509Certificate.getNotBefore();
-        assertTrue(notBefore.after(inFuture(-1)));
-        assertTrue(notBefore.before(inFuture(1)));
+        Date notBefore = x509Certificate.getNotBefore()
+        assertTrue(notBefore.after(inFuture(-1)))
+        assertTrue(notBefore.before(inFuture(1)))
 
-        assertEquals(dn, x509Certificate.getIssuerX500Principal().getName());
-        assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), 
x509Certificate.getSigAlgName().toUpperCase());
-        assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm());
+        assertEquals(dn, x509Certificate.getIssuerX500Principal().getName())
+        assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), 
x509Certificate.getSigAlgName().toUpperCase())
+        assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm())
 
-        x509Certificate.checkValidity();
+        x509Certificate.checkValidity()
     }
 
-
-
     @Test
     public void testIssueCert() throws Exception {
         int days = 365;
-        KeyPair issuerKeyPair = generateKeyPair();
-        X509Certificate issuer = 
CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, 
"CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days);
+        KeyPair issuerKeyPair = generateKeyPair()
+        X509Certificate issuer = 
CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, 
"CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days)
 
-        String dn = "CN=testIssued, O=testOrg";
+        String dn = "CN=testIssued, O=testOrg"
 
-        KeyPair keyPair = generateKeyPair();
-        X509Certificate x509Certificate = 
CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, 
issuerKeyPair, SIGNATURE_ALGORITHM, days);
-        assertEquals(dn, x509Certificate.getSubjectX500Principal().toString());
-        assertEquals(issuer.getSubjectX500Principal().toString(), 
x509Certificate.getIssuerX500Principal().toString());
-        assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey());
+        KeyPair keyPair = generateKeyPair()
+        X509Certificate x509Certificate = 
CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, 
issuerKeyPair, SIGNATURE_ALGORITHM, days)
+        assertEquals(dn, x509Certificate.getSubjectX500Principal().toString())
+        assertEquals(issuer.getSubjectX500Principal().toString(), 
x509Certificate.getIssuerX500Principal().toString())
+        assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey())
 
-        Date notAfter = x509Certificate.getNotAfter();
-        assertTrue(notAfter.after(inFuture(days - 1)));
-        assertTrue(notAfter.before(inFuture(days + 1)));
+        Date notAfter = x509Certificate.getNotAfter()
+        assertTrue(notAfter.after(inFuture(days - 1)))
+        assertTrue(notAfter.before(inFuture(days + 1)))
 
-        Date notBefore = x509Certificate.getNotBefore();
-        assertTrue(notBefore.after(inFuture(-1)));
-        assertTrue(notBefore.before(inFuture(1)));
+        Date notBefore = x509Certificate.getNotBefore()
+        assertTrue(notBefore.after(inFuture(-1)))
+        assertTrue(notBefore.before(inFuture(1)))
 
-        assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), 
x509Certificate.getSigAlgName().toUpperCase());
-        assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm());
+        assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), 
x509Certificate.getSigAlgName().toUpperCase())
+        assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm())
 
-        x509Certificate.verify(issuerKeyPair.getPublic());
+        x509Certificate.verify(issuerKeyPair.getPublic())
     }
 
     @Test
     public void reorderShouldPutElementsInCorrectOrder() {
-        String cn = "CN=testcn";
-        String l = "L=testl";
-        String st = "ST=testst";
-        String o = "O=testo";
-        String ou = "OU=testou";
-        String c = "C=testc";
-        String street = "STREET=teststreet";
-        String dc = "DC=testdc";
-        String uid = "UID=testuid";
-        String surname = "SURNAME=testsurname";
-        String initials = "INITIALS=testinitials";
-        String givenName = "GIVENNAME=testgivenname";
+        String cn = "CN=testcn"
+        String l = "L=testl"
+        String st = "ST=testst"
+        String o = "O=testo"
+        String ou = "OU=testou"
+        String c = "C=testc"
+        String street = "STREET=teststreet"
+        String dc = "DC=testdc"
+        String uid = "UID=testuid"
+        String surname = "SURNAME=testsurname"
+        String initials = "INITIALS=testinitials"
+        String givenName = "GIVENNAME=testgivenname"
         
assertEquals("$cn,$l,$st,$o,$ou,$c,$street,$dc,$uid,$surname,$givenName,$initials".toString(),
-                
CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc"));
+                
CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc"))
     }
 
     @Test
@@ -548,4 +550,40 @@ class CertificateUtilsTest extends GroovyTestCase {
             executorService.shutdown()
         }
     }
+
+    @Test
+    void testShouldGenerateIssuedCertificateWithSans() {
+        // Arrange
+        final String SUBJECT_DN = "CN=localhost"
+        final List<String> SANS = ["127.0.0.1", "nifi.nifi.apache.org"]
+        logger.info("Creating a certificate with subject: ${SUBJECT_DN} and 
SAN: ${SANS}")
+
+        final KeyPair subjectKeyPair = generateKeyPair()
+        final KeyPair issuerKeyPair = generateKeyPair()
+
+        final X509Certificate issuerCertificate = 
CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, ISSUER_DN, 
SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
+
+        // Form the SANS into GeneralName instances and populate the container 
with the array
+        def gns = SANS.collect { String san ->
+            new GeneralName(GeneralName.dNSName, san)
+        }
+        def generalNames = new GeneralNames(gns as GeneralName[])
+        logger.info("Created GeneralNames object: 
${generalNames.names*.toString()}")
+
+        // Form the Extensions object
+        ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator()
+        extensionsGenerator.addExtension(Extension.subjectAlternativeName, 
false, generalNames)
+        Extensions extensions = extensionsGenerator.generate()
+        logger.info("Generated extensions object: 
${extensions.oids()*.toString()}")
+
+        // Act
+        X509Certificate certificate = 
CertificateUtils.generateIssuedCertificate(SUBJECT_DN, subjectKeyPair.public, 
extensions, issuerCertificate, issuerKeyPair, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
+        logger.info("Issued certificate with subject: 
${certificate.getSubjectDN().name} and SAN: 
${certificate.getSubjectAlternativeNames().join(",")}")
+
+        // Assert
+        assert certificate instanceof X509Certificate
+        assert certificate.getSubjectDN().name == SUBJECT_DN
+        assert certificate.getSubjectAlternativeNames().size() == SANS.size()
+        assert 
certificate.getSubjectAlternativeNames()*.last().containsAll(SANS)
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
index 780dfa6..86084c0 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
@@ -41,6 +41,7 @@ public class TlsConfig {
     private String signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
 
     private String dn;
+    private String domainAlternativeNames;
     private String keyStore;
     private String keyStoreType = DEFAULT_KEY_STORE_TYPE;
     private String keyStorePassword;
@@ -206,4 +207,12 @@ public class TlsConfig {
             dn = calcDefaultDn(caHostname);
         }
     }
+
+    public String getDomainAlternativeNames() {
+        return domainAlternativeNames;
+    }
+
+    public void setDomainAlternativeNames(String domainAlternativeNames) {
+        this.domainAlternativeNames = domainAlternativeNames;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
index 413255e..db73b41 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
@@ -18,6 +18,12 @@
 package org.apache.nifi.toolkit.tls.service.client;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import org.apache.commons.cli.CommandLine;
 import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
 import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
@@ -28,25 +34,20 @@ import org.apache.nifi.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
 /**
  * Command line parser for a TlsClientConfig object and a main entry point to 
invoke the parser and run the CA client
  */
 public class TlsCertificateAuthorityClientCommandLine extends 
BaseCertificateAuthorityCommandLine {
     public static final String DESCRIPTION = "Generates a private key and gets 
it signed by the certificate authority.";
     public static final String CERTIFICATE_DIRECTORY = "certificateDirectory";
+    public static final String SUBJECT_ALTERNATIVE_NAMES = 
"subjectAlternativeNames";
     public static final String DEFAULT_CERTIFICATE_DIRECTORY = ".";
 
     private final Logger logger = 
LoggerFactory.getLogger(TlsCertificateAuthorityClientCommandLine.class);
     private final InputStreamFactory inputStreamFactory;
 
     private String certificateDirectory;
+    private String domainAlternativeNames;
 
     public TlsCertificateAuthorityClientCommandLine() {
         this(FileInputStream::new);
@@ -56,6 +57,7 @@ public class TlsCertificateAuthorityClientCommandLine extends 
BaseCertificateAut
         super(DESCRIPTION);
         this.inputStreamFactory = inputStreamFactory;
         addOptionWithArg("C", CERTIFICATE_DIRECTORY, "The file to write the CA 
certificate to", DEFAULT_CERTIFICATE_DIRECTORY);
+        addOptionWithArg("S", SUBJECT_ALTERNATIVE_NAMES, "Comma-separated list 
of domains to use as Subject Alternative Names in the certificate");
     }
 
     public static void main(String[] args) throws Exception {
@@ -110,6 +112,7 @@ public class TlsCertificateAuthorityClientCommandLine 
extends BaseCertificateAut
     protected CommandLine doParse(String[] args) throws 
CommandLineParseException {
         CommandLine commandLine = super.doParse(args);
         certificateDirectory = 
commandLine.getOptionValue(CERTIFICATE_DIRECTORY, 
DEFAULT_CERTIFICATE_DIRECTORY);
+        domainAlternativeNames = 
commandLine.getOptionValue(SUBJECT_ALTERNATIVE_NAMES);
         return commandLine;
     }
 
@@ -117,6 +120,10 @@ public class TlsCertificateAuthorityClientCommandLine 
extends BaseCertificateAut
         return certificateDirectory;
     }
 
+    public String getDomainAlternativeNames() {
+        return domainAlternativeNames;
+    }
+
     public TlsClientConfig createClientConfig() throws IOException {
         String configJsonIn = getConfigJsonIn();
         if (!StringUtils.isEmpty(configJsonIn)) {
@@ -129,6 +136,7 @@ public class TlsCertificateAuthorityClientCommandLine 
extends BaseCertificateAut
             TlsClientConfig tlsClientConfig = new TlsClientConfig();
             tlsClientConfig.setCaHostname(getCertificateAuthorityHostname());
             tlsClientConfig.setDn(getDn());
+            
tlsClientConfig.setDomainAlternativeNames(getDomainAlternativeNames());
             tlsClientConfig.setToken(getToken());
             tlsClientConfig.setPort(getPort());
             tlsClientConfig.setKeyStore(KEYSTORE + 
getKeyStoreType().toLowerCase());
@@ -140,4 +148,5 @@ public class TlsCertificateAuthorityClientCommandLine 
extends BaseCertificateAut
             return tlsClientConfig;
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
index 889f217..14dc46f 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
@@ -18,6 +18,16 @@
 package org.apache.nifi.toolkit.tls.service.client;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.input.BoundedInputStream;
 import org.apache.http.HttpHost;
@@ -38,17 +48,6 @@ import org.eclipse.jetty.server.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
 public class TlsCertificateSigningRequestPerformer {
     public static final String RECEIVED_RESPONSE_CODE = "Received response 
code ";
     public static final String EXPECTED_ONE_CERTIFICATE = "Expected one 
certificate";
@@ -59,23 +58,28 @@ public class TlsCertificateSigningRequestPerformer {
     private final Supplier<HttpClientBuilder> httpClientBuilderSupplier;
     private final String caHostname;
     private final String dn;
+    private final String domainAlternativeNames;
     private final String token;
     private final int port;
     private final ObjectMapper objectMapper;
     private final String signingAlgorithm;
 
     public TlsCertificateSigningRequestPerformer(TlsClientConfig 
tlsClientConfig) throws NoSuchAlgorithmException {
-        this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), 
tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), 
tlsClientConfig.getSigningAlgorithm());
+        this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), 
tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
+                tlsClientConfig.getToken(), tlsClientConfig.getPort(), 
tlsClientConfig.getSigningAlgorithm());
     }
 
     protected 
TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> 
httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) throws 
NoSuchAlgorithmException {
-        this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), 
tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), 
tlsClientConfig.getSigningAlgorithm());
+        this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), 
tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
+                tlsClientConfig.getToken(), tlsClientConfig.getPort(), 
tlsClientConfig.getSigningAlgorithm());
     }
 
-    private TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> 
httpClientBuilderSupplier, String caHostname, String dn, String token, int 
port, String signingAlgorithm) {
+    private TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> 
httpClientBuilderSupplier, String caHostname,
+                                                  String dn, String 
domainAlternativeNames, String token, int port, String signingAlgorithm) {
         this.httpClientBuilderSupplier = httpClientBuilderSupplier;
         this.caHostname = caHostname;
         this.dn = CertificateUtils.reorderDn(dn);
+        this.domainAlternativeNames = domainAlternativeNames;
         this.token = token;
         this.port = port;
         this.objectMapper = new ObjectMapper();
@@ -87,7 +91,7 @@ public class TlsCertificateSigningRequestPerformer {
      *
      * @param keyPair the keypair to generate the csr for
      * @throws IOException if there is a problem during the process
-     * @returnd the resulting certificate chain
+     * @return the resulting certificate chain
      */
     public X509Certificate[] perform(KeyPair keyPair) throws IOException {
         try {
@@ -104,7 +108,7 @@ public class TlsCertificateSigningRequestPerformer {
             String jsonResponseString;
             int responseCode;
             try (CloseableHttpClient client = httpClientBuilder.build()) {
-                JcaPKCS10CertificationRequest request = 
TlsHelper.generateCertificationRequest(dn, keyPair, signingAlgorithm);
+                JcaPKCS10CertificationRequest request = 
TlsHelper.generateCertificationRequest(dn, domainAlternativeNames, keyPair, 
signingAlgorithm);
                 TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest 
= new TlsCertificateAuthorityRequest(TlsHelper.calculateHMac(token, 
request.getPublicKey()),
                         TlsHelper.pemEncodeJcaObject(request));
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
index 27d995e..b0172ae 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
@@ -86,8 +86,8 @@ public class TlsCertificateAuthorityServiceHandler extends 
AbstractHandler {
                 if (logger.isInfoEnabled()) {
                     logger.info("Received CSR with DN " + dn);
                 }
-                X509Certificate x509Certificate = 
CertificateUtils.generateIssuedCertificate(dn,
-                        jcaPKCS10CertificationRequest.getPublicKey(), caCert, 
keyPair, signingAlgorithm, days);
+                X509Certificate x509Certificate = 
CertificateUtils.generateIssuedCertificate(dn, 
jcaPKCS10CertificationRequest.getPublicKey(),
+                        
CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationRequest), caCert, 
keyPair, signingAlgorithm, days);
                 writeResponse(objectMapper, request, response, new 
TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, 
caCert.getPublicKey()),
                         TlsHelper.pemEncodeJcaObject(x509Certificate)), 
Response.SC_OK);
                 return;

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
index 8abe5c6..aa619da 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
@@ -180,7 +180,7 @@ public class TlsToolkitStandalone {
             TlsClientManager tlsClientManager = new 
TlsClientManager(tlsClientConfig);
             KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, 
keySize);
             tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, 
CertificateUtils.generateIssuedCertificate(tlsClientConfig.calcDefaultDn(hostname),
-                    keyPair.getPublic(), certificate, caKeyPair, 
signingAlgorithm, days), certificate);
+                    keyPair.getPublic(), null, certificate, caKeyPair, 
signingAlgorithm, days), certificate);
             tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
             tlsClientManager.addClientConfigurationWriter(new 
NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new 
File(hostDir, "nifi.properties"),
                     hostname, instanceDefinition.getNumber()));
@@ -213,7 +213,7 @@ public class TlsToolkitStandalone {
                 logger.info("Generating new client certificate " + 
clientCertFile);
             }
             KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, 
keySize);
-            X509Certificate clientCert = 
CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), 
certificate, caKeyPair, signingAlgorithm, days);
+            X509Certificate clientCert = 
CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), 
null, certificate, caKeyPair, signingAlgorithm, days);
             KeyStore keyStore = 
KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString());
             keyStore.load(null, null);
             keyStore.setKeyEntry(NIFI_KEY, keyPair.getPrivate(), null, new 
Certificate[]{clientCert, certificate});

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
index f59e2c1..7465714 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
@@ -17,7 +17,33 @@
 
 package org.apache.nifi.toolkit.tls.util;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
@@ -35,25 +61,6 @@ import org.bouncycastle.util.io.pem.PemWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
 public class TlsHelper {
     private static final Logger logger = 
LoggerFactory.getLogger(TlsHelper.class);
     private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
@@ -184,8 +191,27 @@ public class TlsHelper {
         return createKeyPairGenerator(algorithm, keySize).generateKeyPair();
     }
 
-    public static JcaPKCS10CertificationRequest 
generateCertificationRequest(String requestedDn, KeyPair keyPair, String 
signingAlgorithm) throws OperatorCreationException {
+    public static JcaPKCS10CertificationRequest 
generateCertificationRequest(String requestedDn, String domainAlternativeNames,
+            KeyPair keyPair, String signingAlgorithm) throws 
OperatorCreationException {
         JcaPKCS10CertificationRequestBuilder 
jcaPKCS10CertificationRequestBuilder = new 
JcaPKCS10CertificationRequestBuilder(new X500Name(requestedDn), 
keyPair.getPublic());
+
+        // add Subject Alternative Name(s)
+        if(StringUtils.isNotBlank(domainAlternativeNames)) {
+            try {
+                List<GeneralName> namesList = new ArrayList<>();
+                for(String alternativeName : 
domainAlternativeNames.split(",")) {
+                    namesList.add(new GeneralName(GeneralName.dNSName, 
alternativeName));
+                }
+
+                GeneralNames subjectAltNames = new 
GeneralNames(namesList.toArray(new GeneralName [] {}));
+                ExtensionsGenerator extGen = new ExtensionsGenerator();
+                extGen.addExtension(Extension.subjectAlternativeName, false, 
subjectAltNames);
+                
jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
 extGen.generate());
+            } catch (IOException e) {
+                throw new OperatorCreationException("Error while adding " + 
domainAlternativeNames + " as Subject Alternative Name.", e);
+            }
+        }
+
         JcaContentSignerBuilder jcaContentSignerBuilder = new 
JcaContentSignerBuilder(signingAlgorithm);
         return new 
JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
index 37fc8e1..fb20739 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
@@ -106,7 +106,7 @@ public class TlsCertificateSigningRequestPerformerTest {
         when(tlsClientConfig.getPort()).thenReturn(testPort);
         
when(tlsClientConfig.createCertificateSigningRequestPerformer()).thenReturn(tlsCertificateSigningRequestPerformer);
         
when(tlsClientConfig.getSigningAlgorithm()).thenReturn(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
-        JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = 
TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), keyPair, 
TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+        JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = 
TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), null, keyPair, 
TlsConfig.DEFAULT_SIGNING_ALGORITHM);
         String testCsrPem = 
TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
         when(httpClientBuilderSupplier.get()).thenReturn(httpClientBuilder);
         when(httpClientBuilder.build()).thenAnswer(invocation -> {

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
index 4d4be70..00c5ec8 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
@@ -122,7 +122,7 @@ public class TlsCertificateAuthorityServiceHandlerTest {
         caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, 
"CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
         requestedDn = new 
TlsConfig().calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME);
         certificateKeyPair = 
TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, 
TlsConfig.DEFAULT_KEY_SIZE);
-        jcaPKCS10CertificationRequest = 
TlsHelper.generateCertificationRequest(requestedDn, certificateKeyPair, 
TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+        jcaPKCS10CertificationRequest = 
TlsHelper.generateCertificationRequest(requestedDn, null, certificateKeyPair, 
TlsConfig.DEFAULT_SIGNING_ALGORITHM);
         testPemEncodedCsr = 
TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
         tlsCertificateAuthorityServiceHandler = new 
TlsCertificateAuthorityServiceHandler(TlsConfig.DEFAULT_SIGNING_ALGORITHM, 
TlsConfig.DEFAULT_DAYS, testToken, caCert, keyPair, objectMapper);
         testHmac = TlsHelper.calculateHMac(testToken, 
jcaPKCS10CertificationRequest.getPublicKey());

http://git-wip-us.apache.org/repos/asf/nifi/blob/6fc30900/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
index 0a3c38f..223dbb7 100644
--- 
a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
+++ 
b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
@@ -17,21 +17,14 @@
 
 package org.apache.nifi.toolkit.tls.util;
 
-import org.apache.nifi.security.util.CertificateUtils;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMKeyPair;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.AdditionalMatchers;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -53,20 +46,43 @@ import java.security.Provider;
 import java.security.SignatureException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @RunWith(MockitoJUnitRunner.class)
 public class TlsHelperTest {
+    public static final Logger logger = 
LoggerFactory.getLogger(TlsHelperTest.class);
+
     private static final boolean originalUnlimitedCrypto = 
TlsHelper.isUnlimitedStrengthCryptographyEnabled();
 
     private int days;
@@ -280,4 +296,58 @@ public class TlsHelperTest {
             assertEquals(ioException2, e);
         }
     }
+
+    @Test
+    public void testShouldIncludeSANFromCSR() throws Exception {
+        // Arrange
+        final List<String> SAN_ENTRIES = Arrays.asList("127.0.0.1", 
"nifi.nifi.apache.org");
+        final String SAN = StringUtils.join(SAN_ENTRIES, ",");
+        final int SAN_COUNT = SAN_ENTRIES.size();
+        final String DN = "CN=localhost";
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        logger.info("Generating CSR with DN: " + DN);
+
+        // Act
+        JcaPKCS10CertificationRequest csrWithSan = 
TlsHelper.generateCertificationRequest(DN, SAN, keyPair, 
TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+        logger.info("Created CSR with SAN: " + SAN);
+        String testCsrPem = TlsHelper.pemEncodeJcaObject(csrWithSan);
+        logger.info("Encoded CSR as PEM: " + testCsrPem);
+
+        // Assert
+        String subjectName = csrWithSan.getSubject().toString();
+        logger.info("CSR Subject Name: " + subjectName);
+        assert subjectName.equals(DN);
+
+        List<String> extractedSans = extractSanFromCsr(csrWithSan);
+        assert extractedSans.size() == SAN_COUNT;
+        List<String> formattedSans = SAN_ENTRIES.stream().map(s -> "DNS: " + 
s).collect(Collectors.toList());
+        assert extractedSans.containsAll(formattedSans);
+    }
+
+    private List<String> extractSanFromCsr(JcaPKCS10CertificationRequest csr) {
+        List<String> sans = new ArrayList<>();
+        Attribute[] certAttributes = csr.getAttributes();
+        for (Attribute attribute : certAttributes) {
+            if 
(attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest))
 {
+                Extensions extensions = 
Extensions.getInstance(attribute.getAttrValues().getObjectAt(0));
+                GeneralNames gns = GeneralNames.fromExtensions(extensions, 
Extension.subjectAlternativeName);
+                GeneralName[] names = gns.getNames();
+                for (GeneralName name : names) {
+                    logger.info("Type: " + name.getTagNo() + " | Name: " + 
name.getName());
+                    String title = "";
+                    if (name.getTagNo() == GeneralName.dNSName) {
+                        title = "DNS";
+                    } else if (name.getTagNo() == GeneralName.iPAddress) {
+                        title = "IP Address";
+                        // name.toASN1Primitive();
+                    } else if (name.getTagNo() == GeneralName.otherName) {
+                        title = "Other Name";
+                    }
+                    sans.add(title + ": " + name.getName());
+                }
+            }
+        }
+
+        return sans;
+    }
 }

Reply via email to