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);