You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by al...@apache.org on 2020/09/01 23:34:55 UTC

[nifi] branch main updated: NIFI-7767 - Fixed issue with tls-toolkit not adding SANs to generated certificates. Added tests. NIFI-7767 - Fixed up TlsCertificateAuthorityTest to include SAN in tests.

This is an automated email from the ASF dual-hosted git repository.

alopresto pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 1e6619b  NIFI-7767 - Fixed issue with tls-toolkit not adding SANs to generated certificates. Added tests. NIFI-7767 - Fixed up TlsCertificateAuthorityTest to include SAN in tests.
1e6619b is described below

commit 1e6619b91f2d9a7af2d7195464d4a0f5b595b93d
Author: Nathan Gough <th...@gmail.com>
AuthorDate: Fri Aug 28 16:33:51 2020 -0400

    NIFI-7767 - Fixed issue with tls-toolkit not adding SANs to generated certificates. Added tests.
    NIFI-7767 - Fixed up TlsCertificateAuthorityTest to include SAN in tests.
---
 .../nifi/security/util/CertificateUtils.java       |  3 +-
 .../nifi/security/util/CertificateUtilsTest.groovy | 51 +++++++++++++
 .../tls/service/TlsCertificateAuthorityTest.java   | 27 +++++++
 .../TlsCertificateAuthorityServiceHandlerTest.java | 88 ++++++++++++++++------
 4 files changed, 145 insertions(+), 24 deletions(-)

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 ee0c33e..a93c518 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
@@ -50,6 +50,7 @@ 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.DLSequence;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.pkcs.Attribute;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -598,7 +599,7 @@ public final class CertificateUtils {
                 ASN1Encodable extension = attValue.getObjectAt(0);
                 if (extension instanceof Extensions) {
                     return (Extensions) extension;
-                } else if (extension instanceof DERSequence) {
+                } else if (extension instanceof DERSequence || extension instanceof DLSequence) {
                     return Extensions.getInstance(extension);
                 }
             }
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 d20cfeb..a1044ca 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,12 +16,20 @@
  */
 package org.apache.nifi.security.util
 
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
+import org.bouncycastle.asn1.x500.X500Name
+import org.bouncycastle.asn1.x500.style.BCStyle
+import org.bouncycastle.asn1.x500.style.IETFUtils
 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.bouncycastle.operator.jcajce.JcaContentSignerBuilder
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
+import org.bouncycastle.util.IPAddress
 import org.junit.After
 import org.junit.Before
 import org.junit.BeforeClass
@@ -68,6 +76,7 @@ class CertificateUtilsTest extends GroovyTestCase {
 
     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 List<String> SUBJECT_ALT_NAMES = ["127.0.0.1", "nifi.nifi.apache.org"]
 
     @BeforeClass
     static void setUpOnce() {
@@ -655,4 +664,46 @@ class CertificateUtilsTest extends GroovyTestCase {
             assert tlsVersion == "TLSv1.3"
         }
     }
+
+    @Test
+    void testGetExtensionsFromCSR() {
+        // Arrange
+        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA")
+        KeyPair keyPair = generator.generateKeyPair()
+        Extensions sanExtensions = createDomainAlternativeNamesExtensions(SUBJECT_ALT_NAMES, SUBJECT_DN)
+
+        JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(SUBJECT_DN), keyPair.getPublic())
+        jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, sanExtensions)
+        JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder("SHA256WITHRSA")
+        JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())))
+
+        // Act
+        Extensions extensions = CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationRequest)
+
+        // Assert
+        assert(extensions.equivalent(sanExtensions))
+    }
+
+    // Using this directly from tls-toolkit results in a dependency loop, so it's added here for testing purposes.
+    private static Extensions createDomainAlternativeNamesExtensions(List<String> domainAlternativeNames, String requestedDn) throws IOException {
+        List<GeneralName> namesList = new ArrayList<>()
+
+        try {
+            final String cn = IETFUtils.valueToString(new X500Name(requestedDn).getRDNs(BCStyle.CN)[0].getFirst().getValue())
+            namesList.add(new GeneralName(GeneralName.dNSName, cn))
+        } catch (Exception e) {
+            throw new IOException("Failed to extract CN from request DN: " + requestedDn, e)
+        }
+
+        if (domainAlternativeNames != null) {
+            for (String alternativeName : domainAlternativeNames) {
+                namesList.add(new GeneralName(IPAddress.isValid(alternativeName) ? GeneralName.iPAddress : GeneralName.dNSName, alternativeName))
+            }
+        }
+
+        GeneralNames subjectAltNames = new GeneralNames(namesList.toArray([] as GeneralName[]))
+        ExtensionsGenerator extGen = new ExtensionsGenerator()
+        extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames)
+        return extGen.generate()
+    }
 }
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java
index 7137dfe..afefee3 100644
--- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java
@@ -51,6 +51,11 @@ import java.security.SignatureException;
 import java.security.UnrecoverableEntryException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -73,6 +78,7 @@ public class TlsCertificateAuthorityTest {
     private ByteArrayOutputStream clientTrustStoreOutputStream;
     private ByteArrayOutputStream serverConfigFileOutputStream;
     private ByteArrayOutputStream clientConfigFileOutputStream;
+    private String subjectAlternativeName;
 
     @Before
     public void setup() throws FileNotFoundException {
@@ -87,6 +93,7 @@ public class TlsCertificateAuthorityTest {
         clientTrustStoreOutputStream = new ByteArrayOutputStream();
         serverConfigFileOutputStream = new ByteArrayOutputStream();
         clientConfigFileOutputStream = new ByteArrayOutputStream();
+        subjectAlternativeName = "nifi.apache.org";
 
         String myTestTokenUseSomethingStronger = "myTestTokenUseSomethingStronger";
         int port = availablePort();
@@ -106,6 +113,7 @@ public class TlsCertificateAuthorityTest {
         clientConfig.setKeyStore(clientKeyStore);
         clientConfig.setTrustStore(clientTrustStore);
         clientConfig.setToken(myTestTokenUseSomethingStronger);
+        clientConfig.setDomainAlternativeNames(Arrays.asList(subjectAlternativeName));
         clientConfig.setPort(port);
         clientConfig.setKeySize(2048);
         clientConfig.initDefaults();
@@ -240,6 +248,9 @@ public class TlsCertificateAuthorityTest {
         certificateChain[0].verify(caCertificate.getPublicKey());
         assertPrivateAndPublicKeyMatch(clientPrivateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey());
 
+        // Does the certificate contain the SAN we defined in the client config?
+        assert(isSANPresent(certificateChain[0]));
+
         KeyStore clientTrustStore = KeyStoreUtils.getTrustStore(KeystoreType.JKS.toString());
         clientTrustStore.load(new ByteArrayInputStream(clientTrustStoreOutputStream.toByteArray()), clientConfig.getTrustStorePassword().toCharArray());
         assertEquals(caCertificate, clientTrustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT));
@@ -257,6 +268,22 @@ public class TlsCertificateAuthorityTest {
         verify.verify(signature.sign());
     }
 
+    private boolean isSANPresent(Certificate cert) {
+        Iterator<List<?>> iterator = null;
+        try {
+            iterator = ((X509Certificate) cert).getSubjectAlternativeNames().iterator();
+        } catch (CertificateParsingException e) {
+            e.printStackTrace();
+        }
+        boolean containsSAN = false;
+        while(iterator.hasNext()) {
+            if(iterator.next().contains(subjectAlternativeName)) {
+                containsSAN = true;
+            }
+        }
+        return containsSAN;
+    }
+
     /**
      * Will determine the available port used by ca server
      */
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 eb0dbc3..54e49ca 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
@@ -17,15 +17,48 @@
 
 package org.apache.nifi.toolkit.tls.service.server;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import org.apache.nifi.security.util.CertificateUtils;
 import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
 import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
 import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
 import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.junit.After;
@@ -35,29 +68,6 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.X509Certificate;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(MockitoJUnitRunner.class)
 public class TlsCertificateAuthorityServiceHandlerTest {
     X509Certificate caCert;
@@ -95,6 +105,9 @@ public class TlsCertificateAuthorityServiceHandlerTest {
     private String requestedDn;
     private KeyPair certificateKeyPair;
 
+    private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US";
+    private static final List<String> SUBJECT_ALT_NAMES = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org");
+
     @Before
     public void setup() throws Exception {
         testToken = "testTokenTestToken";
@@ -166,6 +179,35 @@ public class TlsCertificateAuthorityServiceHandlerTest {
         tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
     }
 
+    @Test
+    public void testSANAgainUsingCertificationRequestMethod() throws GeneralSecurityException, IOException, OperatorCreationException {
+        // Arrange
+        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+        KeyPair keyPair = generator.generateKeyPair();
+        Extensions exts = TlsHelper.createDomainAlternativeNamesExtensions(SUBJECT_ALT_NAMES, SUBJECT_DN);
+        String token = "someTokenData16B";
+
+        JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(SUBJECT_DN), keyPair.getPublic());
+        jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, exts);
+        JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder("SHA256WITHRSA");
+        JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest(
+                jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
+        TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(
+                TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey()), TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest));
+
+        JcaPKCS10CertificationRequest jcaPKCS10CertificationDecoded = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
+
+        // Act
+        Extensions extensions = CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationDecoded);
+        // Satisfy @After requirement
+        baseRequest.setHandled(true);
+
+        // Assert
+        assertNotNull("The extensions parsed from the CSR were found to be null when they should have been present.", extensions);
+        assertNotNull("The Subject Alternate Name parsed from the CSR was found to be null when it should have been present.", extensions.getExtension(Extension.subjectAlternativeName));
+        assertTrue(extensions.equivalent(exts));
+    }
+
     @After
     public void verifyHandled() {
         verify(baseRequest).setHandled(true);