You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2023/08/30 19:08:22 UTC
[nifi] branch support/nifi-1.x updated: NIFI-12001 Refactored CertificateUtils to separate modules
This is an automated email from the ASF dual-hosted git repository.
mattyb149 pushed a commit to branch support/nifi-1.x
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/support/nifi-1.x by this push:
new 9cd720ee20 NIFI-12001 Refactored CertificateUtils to separate modules
9cd720ee20 is described below
commit 9cd720ee20de686d156c3bcb5e20368467ddce4b
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Fri Aug 25 22:18:33 2023 -0500
NIFI-12001 Refactored CertificateUtils to separate modules
- Added nifi-security-cert for reusable components without dependencies
- Added nifi-security-cert-builder for certificate generation
NIFI-12001: Changes related to backporting
Signed-off-by: Matt Burgess <ma...@apache.org>
---
nifi-commons/nifi-security-cert-builder/pom.xml | 35 ++
.../security/cert/builder/CertificateBuilder.java | 31 ++
.../cert/builder/StandardCertificateBuilder.java | 263 ++++++++++
.../builder/StandardCertificateBuilderTest.java | 177 +++++++
nifi-commons/nifi-security-cert/pom.xml | 25 +
.../security/cert/CertificateAttributeReader.java | 33 ++
.../apache/nifi/security/cert/GeneralNameType.java | 79 +++
.../nifi/security/cert/PeerIdentityProvider.java | 35 ++
.../cert/StandardCertificateAttributeReader.java | 103 ++++
.../cert/StandardPeerIdentityProvider.java | 76 +++
.../cert/StandardSubjectAlternativeName.java | 59 +++
.../nifi/security/cert/SubjectAlternativeName.java | 36 ++
.../StandardCertificateAttributeReaderTest.java | 120 +++++
.../cert/StandardPeerIdentityProviderTest.java | 91 ++++
nifi-commons/nifi-security-utils/pom.xml | 5 +
.../nifi/security/util/CertificateUtils.java | 563 ---------------------
.../apache/nifi/security/util/KeyStoreUtils.java | 41 +-
.../security/util/TemporaryKeyStoreBuilder.java | 17 +-
.../nifi/security/util/KeyStoreUtilsTest.java | 9 +-
nifi-commons/nifi-socket-utils/pom.xml | 5 +
.../org/apache/nifi/io/socket/SocketUtils.java | 21 +
nifi-commons/pom.xml | 2 +
.../nifi-framework-cluster-protocol/pom.xml | 5 +
.../protocol/impl/SocketProtocolListener.java | 42 +-
.../nifi-framework/nifi-framework-core/pom.xml | 5 +
.../server/ClusterLoadBalanceAuthorizer.java | 34 +-
.../server/ConnectionLoadBalanceServer.java | 6 +-
.../zookeeper/ITZooKeeperStateProvider.java | 277 ----------
.../state/server/ITZooKeeperStateServerTLS.java | 557 --------------------
.../election/ITSecureClientZooKeeperFactory.java | 256 ----------
.../security/token/LoginAuthenticationToken.java | 10 +-
.../nifi-standard-processors/pom.xml | 5 +
.../apache/nifi/processors/standard/PostHTTP.java | 12 +-
...leHttpRequestCertificateAttributesProvider.java | 57 +--
...tpRequestCertificateAttributesProviderTest.java | 2 -
.../tests/system/NiFiSystemKeyStoreProvider.java | 22 +-
.../admin/client/NiFiClientFactorySpec.groovy | 320 ------------
nifi-toolkit/nifi-toolkit-tls/pom.xml | 5 +
.../toolkit/tls/configuration/TlsClientConfig.java | 1 -
.../nifi/toolkit/tls/configuration/TlsConfig.java | 17 +-
.../manager/TlsCertificateAuthorityManager.java | 9 +-
.../TlsCertificateSigningRequestPerformer.java | 8 +-
.../TlsCertificateAuthorityServiceHandler.java | 40 +-
.../tls/standalone/TlsToolkitStandalone.java | 30 +-
.../apache/nifi/toolkit/tls/util/TlsHelper.java | 7 +-
.../TlsToolkitStandaloneGroovyTest.groovy | 25 +-
.../TlsCertificateSigningRequestPerformerTest.java | 12 +-
.../TlsCertificateAuthorityServiceHandlerTest.java | 9 +-
.../tls/standalone/TlsToolkitStandaloneTest.java | 4 +-
.../nifi/toolkit/tls/util/TlsHelperTest.java | 53 +-
50 files changed, 1400 insertions(+), 2256 deletions(-)
diff --git a/nifi-commons/nifi-security-cert-builder/pom.xml b/nifi-commons/nifi-security-cert-builder/pom.xml
new file mode 100644
index 0000000000..8b6acb0518
--- /dev/null
+++ b/nifi-commons/nifi-security-cert-builder/pom.xml
@@ -0,0 +1,35 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-security-cert-builder</artifactId>
+ <description>Shared X.509 certificate builder components</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk18on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk18on</artifactId>
+ </dependency>
+ </dependencies>
+</project>
+
diff --git a/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/CertificateBuilder.java b/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/CertificateBuilder.java
new file mode 100644
index 0000000000..48fded4ca5
--- /dev/null
+++ b/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/CertificateBuilder.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert.builder;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Abstraction for building X.509 Certificates
+ */
+public interface CertificateBuilder {
+ /**
+ * Build X.509 Certificate using configured properties
+ *
+ * @return X.509 Certificate
+ */
+ X509Certificate build();
+}
diff --git a/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilder.java b/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilder.java
new file mode 100644
index 0000000000..9b33c196bf
--- /dev/null
+++ b/nifi-commons/nifi-security-cert-builder/src/main/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilder.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert.builder;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Standard X.509 Certificate Builder using Bouncy Castle components
+ */
+public class StandardCertificateBuilder implements CertificateBuilder {
+ private static final String SIGNING_ALGORITHM = "SHA256withRSA";
+
+ private static final String LOCALHOST = "localhost";
+
+ private static final boolean CRITICAL = true;
+
+ private static final boolean NOT_CRITICAL = false;
+
+ private static final int STANDARD_KEY_USAGE = KeyUsage.digitalSignature
+ | KeyUsage.keyEncipherment
+ | KeyUsage.dataEncipherment
+ | KeyUsage.keyAgreement
+ | KeyUsage.nonRepudiation;
+
+ private static final int AUTHORITY_KEY_USAGE = STANDARD_KEY_USAGE | KeyUsage.cRLSign | KeyUsage.keyCertSign;
+
+ private final BigInteger serialNumber = BigInteger.valueOf(System.nanoTime());
+
+ private final KeyPair issuerKeyPair;
+
+ private final X500Principal issuer;
+
+ private final Duration validityPeriod;
+
+ private PublicKey subjectPublicKey;
+
+ private X500Principal subject;
+
+ private Set<String> dnsSubjectAlternativeNames = Collections.emptySet();
+
+ /**
+ * Standard Certificate Builder with Issuer Key Pair and Issuer Principal defaults for self-signing
+ *
+ * @param issuerKeyPair Issuer Key Pair also provides the default Subject Public Key
+ * @param issuer Issuer also provides the default Subject
+ * @param validityPeriod Validity period of not before and not after properties
+ */
+ public StandardCertificateBuilder(final KeyPair issuerKeyPair, final X500Principal issuer, final Duration validityPeriod) {
+ this.issuerKeyPair = Objects.requireNonNull(issuerKeyPair, "Issuer Key Pair required");
+ this.issuer = Objects.requireNonNull(issuer, "Issuer required");
+ this.validityPeriod = Objects.requireNonNull(validityPeriod, "Validity Period required");
+ this.subject = issuer;
+ this.subjectPublicKey = issuerKeyPair.getPublic();
+ }
+
+ /**
+ * Build X.509 Certificate using configured properties
+ *
+ * @return X.509 Certificate
+ */
+ @Override
+ public X509Certificate build() {
+ final X509CertificateHolder certificateHolder = getCertificateHolder();
+ final JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
+ try {
+ return certificateConverter.getCertificate(certificateHolder);
+ } catch (final CertificateException e) {
+ throw new IllegalArgumentException("X.509 Certificate conversion failed", e);
+ }
+ }
+
+ /**
+ * Set Subject Principal
+ *
+ * @param subject Subject Principal
+ * @return Builder
+ */
+ public StandardCertificateBuilder setSubject(final X500Principal subject) {
+ this.subject = Objects.requireNonNull(subject, "Subject required");
+ return this;
+ }
+
+ /**
+ * Set Subject Public Key
+ *
+ * @param subjectPublicKey Subject Public Key
+ * @return Builder
+ */
+ public StandardCertificateBuilder setSubjectPublicKey(final PublicKey subjectPublicKey) {
+ this.subjectPublicKey = Objects.requireNonNull(subjectPublicKey, "Subject Public Key required");
+ return this;
+ }
+
+ /**
+ * Set DNS Subject Alternative Names
+ *
+ * @param dnsSubjectAlternativeNames DNS Subject Alternative Names
+ * @return Builder
+ */
+ public StandardCertificateBuilder setDnsSubjectAlternativeNames(final Collection<String> dnsSubjectAlternativeNames) {
+ this.dnsSubjectAlternativeNames = new LinkedHashSet<>(Objects.requireNonNull(dnsSubjectAlternativeNames, "DNS Names required"));
+ return this;
+ }
+
+ private void setExtensions(final X509v3CertificateBuilder certificateBuilder) {
+ final JcaX509ExtensionUtils extensionUtils = getExtensionUtils();
+
+ try {
+ final BasicConstraints basicConstraints = getBasicConstraints();
+ certificateBuilder.addExtension(Extension.basicConstraints, NOT_CRITICAL, basicConstraints);
+
+ final KeyUsage keyUsage = getKeyUsage(basicConstraints.isCA());
+ certificateBuilder.addExtension(Extension.keyUsage, CRITICAL, keyUsage);
+
+ certificateBuilder.addExtension(Extension.subjectKeyIdentifier, NOT_CRITICAL, extensionUtils.createSubjectKeyIdentifier(subjectPublicKey));
+
+ final PublicKey issuerPublicKey = issuerKeyPair.getPublic();
+ certificateBuilder.addExtension(Extension.authorityKeyIdentifier, NOT_CRITICAL, extensionUtils.createAuthorityKeyIdentifier(issuerPublicKey));
+
+ final KeyPurposeId[] keyPurposes = {KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth};
+ final ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(keyPurposes);
+ certificateBuilder.addExtension(Extension.extendedKeyUsage, NOT_CRITICAL, extendedKeyUsage);
+
+ final GeneralNames subjectAlternativeNames = getSubjectAlternativeNames();
+ certificateBuilder.addExtension(Extension.subjectAlternativeName, NOT_CRITICAL, subjectAlternativeNames);
+ } catch (final CertIOException e) {
+ throw new IllegalArgumentException("Certificate Extension addition failed", e);
+ }
+ }
+
+ private BasicConstraints getBasicConstraints() {
+ final PublicKey issuerPublicKey = issuerKeyPair.getPublic();
+ final boolean certificateAuthority = subjectPublicKey.equals(issuerPublicKey);
+ return new BasicConstraints(certificateAuthority);
+ }
+
+ private KeyUsage getKeyUsage(final boolean certificateAuthority) {
+ final int keyUsage = certificateAuthority ? AUTHORITY_KEY_USAGE : STANDARD_KEY_USAGE;
+ return new KeyUsage(keyUsage);
+ }
+
+ private GeneralNames getSubjectAlternativeNames() {
+ final Set<GeneralName> generalNames = new LinkedHashSet<>();
+
+ final String subjectCommonName = getSubjectCommonName();
+ final GeneralName subjectGeneralName = new GeneralName(GeneralName.dNSName, subjectCommonName);
+ generalNames.add(subjectGeneralName);
+
+ for (final String dnsSubjectAlternativeName : dnsSubjectAlternativeNames) {
+ final GeneralName generalName = new GeneralName(GeneralName.dNSName, dnsSubjectAlternativeName);
+ generalNames.add(generalName);
+ }
+
+ return new GeneralNames(generalNames.toArray(new GeneralName[]{}));
+ }
+
+ private String getSubjectCommonName() {
+ final X500Name subjectName = getName(subject);
+ final RDN[] commonNames = subjectName.getRDNs(BCStyle.CN);
+
+ final String subjectCommonName;
+ if (commonNames.length == 0) {
+ subjectCommonName = LOCALHOST;
+ } else {
+ final RDN commonName = commonNames[0];
+ final ASN1Encodable commonNameEncoded = commonName.getFirst().getValue();
+ subjectCommonName = IETFUtils.valueToString(commonNameEncoded);
+ }
+
+ return subjectCommonName;
+ }
+
+ private X509CertificateHolder getCertificateHolder() {
+ final X509v3CertificateBuilder certificateBuilder = getCertificateBuilder();
+ setExtensions(certificateBuilder);
+
+ final ContentSigner contentSigner = getContentSigner();
+ return certificateBuilder.build(contentSigner);
+ }
+
+ private X509v3CertificateBuilder getCertificateBuilder() {
+ final X500Name issuerName = getName(issuer);
+ final Date notBefore = new Date();
+ final Date notAfter = Date.from(notBefore.toInstant().plus(validityPeriod));
+ final X500Name subjectName = getName(subject);
+ final SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded());
+ return new X509v3CertificateBuilder(issuerName, serialNumber, notBefore, notAfter, subjectName, subjectPublicKeyInfo);
+ }
+
+ private ContentSigner getContentSigner() {
+ final JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGNING_ALGORITHM);
+
+ final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
+ try {
+ return contentSignerBuilder.build(issuerPrivateKey);
+ } catch (final OperatorCreationException e) {
+ throw new IllegalArgumentException("Certificate Signer creation failed", e);
+ }
+ }
+
+ private JcaX509ExtensionUtils getExtensionUtils() {
+ try {
+ return new JcaX509ExtensionUtils();
+ } catch (final NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Certificate Extension Utilities creation failed", e);
+ }
+ }
+
+ private X500Name getName(final X500Principal principal) {
+ return new X500Name(RFC4519Style.INSTANCE, principal.getName());
+ }
+}
diff --git a/nifi-commons/nifi-security-cert-builder/src/test/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilderTest.java b/nifi-commons/nifi-security-cert-builder/src/test/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilderTest.java
new file mode 100644
index 0000000000..4472fc277b
--- /dev/null
+++ b/nifi-commons/nifi-security-cert-builder/src/test/java/org/apache/nifi/security/cert/builder/StandardCertificateBuilderTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert.builder;
+
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StandardCertificateBuilderTest {
+ private static final String KEY_PAIR_ALGORITHM = "RSA";
+
+ private static final X500Principal ISSUER = new X500Principal("CN=issuer, OU=nifi, OU=apache, OU=org");
+
+ private static final X500Principal SUBJECT = new X500Principal("CN=subject, OU=nifi, OU=apache, OU=org");
+
+ private static final String SUBJECT_COMMON_NAME = "subject";
+
+ private static final String ISSUER_COMMON_NAME = "issuer";
+
+ private static final Duration VALIDITY_PERIOD = Duration.ofDays(30);
+
+ private static final int AUTHORITY_CONSTRAINTS = 2147483647;
+
+ private static final int STANDARD_CONSTRAINTS = -1;
+
+ private static final List<String> EXTENDED_KEY_USAGE = Arrays.asList(KeyPurposeId.id_kp_clientAuth.toString(), KeyPurposeId.id_kp_serverAuth.toString());
+
+ private static final boolean[] AUTHORITY_KEY_USAGE = new boolean[]{true, true, true, true, true, true, true, false, false};
+
+ private static final boolean[] STANDARD_KEY_USAGE = new boolean[]{true, true, true, true, true, false, false, false, false};
+
+ private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
+
+ private static final String DNS_NAME = "localhost.local";
+
+ private static final int NAME_TYPE_INDEX = 0;
+
+ private static final int NAME_INDEX = 1;
+
+ private static final int DNS_NAME_TYPE = 2;
+
+ private static KeyPair issuerKeyPair;
+
+ private static KeyPair subjectKeyPair;
+
+ @BeforeAll
+ static void setIssuerKeyPair() throws NoSuchAlgorithmException {
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
+ issuerKeyPair = keyPairGenerator.generateKeyPair();
+ subjectKeyPair = keyPairGenerator.generateKeyPair();
+ }
+
+ @Test
+ void testBuildSelfSigned() throws CertificateParsingException {
+ final StandardCertificateBuilder builder = new StandardCertificateBuilder(issuerKeyPair, ISSUER, VALIDITY_PERIOD);
+
+ final X509Certificate certificate = builder.build();
+
+ assertPropertiesFound(certificate);
+
+ assertEquals(ISSUER, certificate.getSubjectX500Principal());
+ assertEquals(issuerKeyPair.getPublic(), certificate.getPublicKey());
+ assertEquals(AUTHORITY_CONSTRAINTS, certificate.getBasicConstraints());
+ assertArrayEquals(AUTHORITY_KEY_USAGE, certificate.getKeyUsage());
+ assertFirstSubjectAlternativeNameEquals(ISSUER_COMMON_NAME, certificate);
+ }
+
+ @Test
+ void testBuildSelfSignedSubjectAlternativeNames() throws CertificateParsingException {
+ final StandardCertificateBuilder builder = new StandardCertificateBuilder(issuerKeyPair, ISSUER, VALIDITY_PERIOD);
+
+ final X509Certificate certificate = builder.setDnsSubjectAlternativeNames(Collections.singletonList(DNS_NAME)).build();
+
+ assertPropertiesFound(certificate);
+
+ assertEquals(ISSUER, certificate.getSubjectX500Principal());
+ assertEquals(issuerKeyPair.getPublic(), certificate.getPublicKey());
+ assertEquals(AUTHORITY_CONSTRAINTS, certificate.getBasicConstraints());
+ assertArrayEquals(AUTHORITY_KEY_USAGE, certificate.getKeyUsage());
+ assertFirstSubjectAlternativeNameEquals(ISSUER_COMMON_NAME, certificate);
+ assertSecondSubjectAlternativeNameEquals(certificate);
+ }
+
+ @Test
+ void testBuildIssued() throws CertificateParsingException {
+ final StandardCertificateBuilder builder = new StandardCertificateBuilder(issuerKeyPair, ISSUER, VALIDITY_PERIOD);
+ final PublicKey subjectPublicKey = subjectKeyPair.getPublic();
+
+ final X509Certificate certificate = builder.setSubject(SUBJECT).setSubjectPublicKey(subjectPublicKey).build();
+ assertPropertiesFound(certificate);
+
+ assertEquals(SUBJECT, certificate.getSubjectX500Principal());
+ assertEquals(subjectPublicKey, certificate.getPublicKey());
+ assertEquals(STANDARD_CONSTRAINTS, certificate.getBasicConstraints());
+ assertArrayEquals(STANDARD_KEY_USAGE, certificate.getKeyUsage());
+ assertFirstSubjectAlternativeNameEquals(SUBJECT_COMMON_NAME, certificate);
+ }
+
+ private void assertPropertiesFound(final X509Certificate certificate) throws CertificateParsingException {
+ assertNotNull(certificate);
+
+ assertEquals(ISSUER, certificate.getIssuerX500Principal());
+
+ final Date notBefore = certificate.getNotBefore();
+ final Date notAfterExpected = Date.from(notBefore.toInstant().plus(VALIDITY_PERIOD));
+ assertEquals(notAfterExpected, certificate.getNotAfter());
+
+ assertEquals(SIGNATURE_ALGORITHM, certificate.getSigAlgName());
+ final List<String> extendedKeyUsage = certificate.getExtendedKeyUsage();
+ assertEquals(EXTENDED_KEY_USAGE, extendedKeyUsage);
+ }
+
+ private void assertFirstSubjectAlternativeNameEquals(final String nameExpected, final X509Certificate certificate) throws CertificateParsingException {
+ final Collection<?> subjectAlternativeNames = certificate.getSubjectAlternativeNames();
+ assertNotNull(subjectAlternativeNames);
+
+ final Optional<?> firstName = subjectAlternativeNames.stream().findFirst();
+ assertTrue(firstName.isPresent());
+
+ final List<?> subjectAlternativeName = (List<?>) firstName.get();
+ assertSubjectAlternativeNameEquals(nameExpected, subjectAlternativeName);
+ }
+
+ private void assertSecondSubjectAlternativeNameEquals(final X509Certificate certificate) throws CertificateParsingException {
+ final Collection<?> subjectAlternativeNames = certificate.getSubjectAlternativeNames();
+ assertNotNull(subjectAlternativeNames);
+
+ final Iterator<?> names = subjectAlternativeNames.iterator();
+ assertNotNull(names.next());
+
+ final List<?> subjectAlternativeName = (List<?>) names.next();
+ assertSubjectAlternativeNameEquals(DNS_NAME, subjectAlternativeName);
+ }
+
+ private void assertSubjectAlternativeNameEquals(final String nameExpected, final List<?> subjectAlternativeName) {
+ final String name = subjectAlternativeName.get(NAME_INDEX).toString();
+ assertEquals(nameExpected, name);
+
+ final Object nameType = subjectAlternativeName.get(NAME_TYPE_INDEX);
+ assertEquals(DNS_NAME_TYPE, nameType);
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/pom.xml b/nifi-commons/nifi-security-cert/pom.xml
new file mode 100644
index 0000000000..d865aae6ac
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/pom.xml
@@ -0,0 +1,25 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-security-cert</artifactId>
+ <description>Shared X.509 certificate security components without additional dependencies</description>
+</project>
+
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/CertificateAttributeReader.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/CertificateAttributeReader.java
new file mode 100644
index 0000000000..0a346bc5d6
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/CertificateAttributeReader.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Abstraction for reading attributes of an X.509 certificate
+ */
+public interface CertificateAttributeReader {
+ /**
+ * Get Subject Alternative Names or empty when none found
+ *
+ * @param certificate X.509 certificate to be read
+ * @return List of Subject Alternative Names
+ */
+ List<SubjectAlternativeName> getSubjectAlternativeNames(X509Certificate certificate);
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/GeneralNameType.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/GeneralNameType.java
new file mode 100644
index 0000000000..419e1fbe2c
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/GeneralNameType.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+/**
+ * Subject Alternative Name General Name Types defined according to RFC 3280 Section 4.2.1.7
+ */
+public enum GeneralNameType {
+ OTHER_NAME(0, "otherName", byte[].class),
+
+ RFC_822_NAME(1, "rfc822Name", String.class),
+
+ DNS_NAME(2, "dNSName", String.class),
+
+ X400_ADDRESS(3, "x400Address", byte[].class),
+
+ DIRECTORY_NAME(4, "directoryName", String.class),
+
+ EDI_PARTY_NAME(5, "ediPartyName", String.class),
+
+ UNIFORM_RESOURCE_IDENTIFIER(6, "uniformResourceIdentifier", String.class),
+
+ IP_ADDRESS(7, "iPAddress", String.class),
+
+ REGISTERED_ID(8, "registeredID", byte[].class);
+
+ private final int nameType;
+
+ private final String generalName;
+
+ private final Class<?> valueClass;
+
+ GeneralNameType(final int nameType, final String generalName, final Class<?> valueClass) {
+ this.nameType = nameType;
+ this.generalName = generalName;
+ this.valueClass = valueClass;
+ }
+
+ /**
+ * Get General Name Type identifier
+ *
+ * @return Subject Alternative Name Type identifier
+ */
+ public int getNameType() {
+ return nameType;
+ }
+
+ /**
+ * Get General Name label
+ *
+ * @return General Name label from RFC 3280 Section 4.2.1.7
+ */
+ public String getGeneralName() {
+ return generalName;
+ }
+
+ /**
+ * Get Value Class associated with General Name elements returned from Certificates
+ *
+ * @return Value Class
+ */
+ public Class<?> getValueClass() {
+ return valueClass;
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/PeerIdentityProvider.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/PeerIdentityProvider.java
new file mode 100644
index 0000000000..e54137d3c3
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/PeerIdentityProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import java.security.cert.Certificate;
+import java.util.Set;
+
+/**
+ * Abstraction for deriving Peer Identity information from certificates
+ */
+public interface PeerIdentityProvider {
+ /**
+ * Get Peer Identities from certificates that a peer provides during TLS handshaking
+ *
+ * @param peerCertificates Peer certificates
+ * @return Set of identities or empty when none found
+ * @throws SSLPeerUnverifiedException Thrown when peer certificates are not found or are not valid
+ */
+ Set<String> getIdentities(Certificate[] peerCertificates) throws SSLPeerUnverifiedException;
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardCertificateAttributeReader.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardCertificateAttributeReader.java
new file mode 100644
index 0000000000..8fbd9bd376
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardCertificateAttributeReader.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Standard implementation of Certificate Attribute Reader using standard X.509 Certificate methods
+ */
+public class StandardCertificateAttributeReader implements CertificateAttributeReader {
+ private static final int NAME_TYPE_INDEX = 0;
+
+ private static final int NAME_INDEX = 1;
+
+ private static final int NAME_TYPE_UNKNOWN = 0;
+
+ /**
+ * Get Subject Alternative Names from X.509 certificate when found
+ *
+ * @param certificate X.509 certificate to be read
+ * @return List of Subject Alternative Names or empty when not found
+ */
+ @Override
+ public List<SubjectAlternativeName> getSubjectAlternativeNames(final X509Certificate certificate) {
+ Objects.requireNonNull(certificate, "Certificate required");
+
+ final List<SubjectAlternativeName> names = new ArrayList<>();
+
+ final Collection<List<?>> subjectAlternativeNames = readSubjectAlternativeNames(certificate);
+ for (final List<?> subjectAlternativeTypeName : subjectAlternativeNames) {
+ final Object nameType = subjectAlternativeTypeName.get(NAME_TYPE_INDEX);
+ final GeneralNameType generalNameType = getGeneralNameType(nameType);
+
+ final Object name = subjectAlternativeTypeName.get(NAME_INDEX);
+
+ final SubjectAlternativeName subjectAlternativeName;
+ if (name instanceof byte[]) {
+ subjectAlternativeName = new StandardSubjectAlternativeName(generalNameType, (byte[]) name);
+ } else {
+ subjectAlternativeName = new StandardSubjectAlternativeName(generalNameType, name.toString());
+ }
+ names.add(subjectAlternativeName);
+ }
+
+ return names;
+ }
+
+ private Collection<List<?>> readSubjectAlternativeNames(final X509Certificate certificate) {
+ try {
+ final Collection<List<?>> names = certificate.getSubjectAlternativeNames();
+ return names == null ? Collections.emptyList() : names;
+ } catch (final CertificateParsingException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ private GeneralNameType getGeneralNameType(final Object nameType) {
+ GeneralNameType generalNameTypeFound = GeneralNameType.OTHER_NAME;
+
+ final int resolvedNameType = getResolvedNameType(nameType);
+
+ for (final GeneralNameType generalNameType : GeneralNameType.values()) {
+ if (generalNameType.getNameType() == resolvedNameType) {
+ generalNameTypeFound = generalNameType;
+ break;
+ }
+ }
+
+ return generalNameTypeFound;
+ }
+
+ private int getResolvedNameType(final Object nameType) {
+ final int resolvedNameType;
+
+ if (nameType instanceof Integer) {
+ resolvedNameType = (Integer) nameType;
+ } else {
+ resolvedNameType = NAME_TYPE_UNKNOWN;
+ }
+
+ return resolvedNameType;
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardPeerIdentityProvider.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardPeerIdentityProvider.java
new file mode 100644
index 0000000000..b4f0b4776e
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardPeerIdentityProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Standard implementation of Peer Identity Provider reads the first certificate as an X.509 certificate with Subject Alternative Names
+ */
+public class StandardPeerIdentityProvider implements PeerIdentityProvider {
+ private final CertificateAttributeReader certificateAttributeReader = new StandardCertificateAttributeReader();
+
+ /**
+ * Get Peer Identities from DNS Subject Alternative Names found on validated X.509 certificate provided
+ *
+ * @param peerCertificates Peer certificates
+ * @return Set of identities or empty when no DNS Subject Alternative Names found
+ * @throws SSLPeerUnverifiedException Thrown on peer certificates not found or not valid
+ */
+ @Override
+ public Set<String> getIdentities(final Certificate[] peerCertificates) throws SSLPeerUnverifiedException {
+ if (peerCertificates == null || peerCertificates.length == 0) {
+ throw new SSLPeerUnverifiedException("Peer certificates not found");
+ }
+
+ final Certificate peerCertificate = peerCertificates[0];
+ if (peerCertificate instanceof X509Certificate) {
+ final X509Certificate certificate = (X509Certificate) peerCertificate;
+ try {
+ certificate.checkValidity();
+ } catch (final CertificateException e) {
+ final String message = String.format("X.509 Peer certificate not valid: %s", e.getMessage());
+ throw new SSLPeerUnverifiedException(message);
+ }
+
+ return getIdentities(certificate);
+ } else {
+ throw new SSLPeerUnverifiedException("X.509 Peer certificate not found");
+ }
+ }
+
+ private Set<String> getIdentities(final X509Certificate certificate) {
+ final Set<String> identities = new LinkedHashSet<>();
+
+ final List<SubjectAlternativeName> subjectAlternativeNames = certificateAttributeReader.getSubjectAlternativeNames(certificate);
+ for (final SubjectAlternativeName subjectAlternativeName : subjectAlternativeNames) {
+ final GeneralNameType generalNameType = subjectAlternativeName.getGeneralNameType();
+ if (GeneralNameType.DNS_NAME == generalNameType) {
+ final String name = subjectAlternativeName.getName();
+ identities.add(name);
+ }
+ }
+
+ return identities;
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardSubjectAlternativeName.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardSubjectAlternativeName.java
new file mode 100644
index 0000000000..d46d03e92a
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/StandardSubjectAlternativeName.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import java.util.Base64;
+import java.util.Objects;
+
+/**
+ * Standard implementation of Subject Alternative Name derived from byte array or string names
+ */
+class StandardSubjectAlternativeName implements SubjectAlternativeName {
+ private final String name;
+
+ private final GeneralNameType generalNameType;
+
+ StandardSubjectAlternativeName(final GeneralNameType generalNameType, final String name) {
+ this.generalNameType = Objects.requireNonNull(generalNameType, "General Name Type required");
+ this.name = Objects.requireNonNull(name, "Name required");
+ }
+
+ StandardSubjectAlternativeName(final GeneralNameType generalNameType, final byte[] nameEncoded) {
+ this.generalNameType = Objects.requireNonNull(generalNameType, "General Name Type required");
+ this.name = Base64.getEncoder().encodeToString(nameEncoded);
+ }
+
+ /**
+ * Get Name as a String which can return either the standard representation or an encoded representation of an ASN.1 byte array
+ *
+ * @return Subject Alternative Name string or encoded representation of bytes
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get General Name Type descriptor for the Subject Alternative name
+ *
+ * @return General Name Type
+ */
+ @Override
+ public GeneralNameType getGeneralNameType() {
+ return generalNameType;
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/SubjectAlternativeName.java b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/SubjectAlternativeName.java
new file mode 100644
index 0000000000..743b528d43
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/main/java/org/apache/nifi/security/cert/SubjectAlternativeName.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+/**
+ * X.509 Certificate Subject Alternative Name
+ */
+public interface SubjectAlternativeName {
+ /**
+ * Get Name as a String which can return either the standard representation or an encoded representation of an ASN.1 byte array
+ *
+ * @return Subject Alternative Name string or encoded representation of bytes
+ */
+ String getName();
+
+ /**
+ * Get General Name Type descriptor for the Subject Alternative name
+ *
+ * @return General Name Type
+ */
+ GeneralNameType getGeneralNameType();
+}
diff --git a/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardCertificateAttributeReaderTest.java b/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardCertificateAttributeReaderTest.java
new file mode 100644
index 0000000000..4d6893a9a4
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardCertificateAttributeReaderTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardCertificateAttributeReaderTest {
+ private static final String DNS_NAME = "localhost.local";
+
+ private static final byte[] REGISTERED_ID = new byte[]{1, 2, 3};
+
+ @Mock
+ private X509Certificate certificate;
+
+ private StandardCertificateAttributeReader reader;
+
+ @BeforeEach
+ void setReader() {
+ reader = new StandardCertificateAttributeReader();
+ }
+
+ @Test
+ void testGetSubjectAlternativeNamesEmpty() throws CertificateParsingException {
+ when(certificate.getSubjectAlternativeNames()).thenReturn(Collections.emptyList());
+ final List<SubjectAlternativeName> subjectAlternativeNames = reader.getSubjectAlternativeNames(certificate);
+
+ assertNotNull(subjectAlternativeNames);
+ assertTrue(subjectAlternativeNames.isEmpty());
+ }
+
+ @Test
+ void testGetSubjectAlternativeNamesNull() throws CertificateParsingException {
+ when(certificate.getSubjectAlternativeNames()).thenReturn(null);
+ final List<SubjectAlternativeName> subjectAlternativeNames = reader.getSubjectAlternativeNames(certificate);
+
+ assertNotNull(subjectAlternativeNames);
+ assertTrue(subjectAlternativeNames.isEmpty());
+ }
+
+ @Test
+ void testGetSubjectAlternativeNamesException() throws CertificateParsingException {
+ when(certificate.getSubjectAlternativeNames()).thenThrow(new CertificateParsingException());
+ final List<SubjectAlternativeName> subjectAlternativeNames = reader.getSubjectAlternativeNames(certificate);
+
+ assertNotNull(subjectAlternativeNames);
+ assertTrue(subjectAlternativeNames.isEmpty());
+ }
+
+ @Test
+ void testGetSubjectAlternativeNamesDnsName() throws CertificateParsingException {
+ final List<Object> generalName = new ArrayList<>();
+ generalName.add(GeneralNameType.DNS_NAME.getNameType());
+ generalName.add(DNS_NAME);
+
+ when(certificate.getSubjectAlternativeNames()).thenReturn(Collections.singletonList(generalName));
+ final List<SubjectAlternativeName> subjectAlternativeNames = reader.getSubjectAlternativeNames(certificate);
+
+ assertNotNull(subjectAlternativeNames);
+
+ final Iterator<SubjectAlternativeName> names = subjectAlternativeNames.iterator();
+ assertTrue(names.hasNext());
+ final SubjectAlternativeName subjectAlternativeName = names.next();
+ assertEquals(GeneralNameType.DNS_NAME, subjectAlternativeName.getGeneralNameType());
+ assertEquals(String.class, subjectAlternativeName.getGeneralNameType().getValueClass());
+ assertEquals(DNS_NAME, subjectAlternativeName.getName());
+ }
+
+ @Test
+ void testGetSubjectAlternativeNamesRegisteredId() throws CertificateParsingException {
+ final List<Object> generalName = new ArrayList<>();
+ generalName.add(GeneralNameType.REGISTERED_ID.getNameType());
+ generalName.add(REGISTERED_ID);
+
+ when(certificate.getSubjectAlternativeNames()).thenReturn(Collections.singletonList(generalName));
+ final List<SubjectAlternativeName> subjectAlternativeNames = reader.getSubjectAlternativeNames(certificate);
+
+ assertNotNull(subjectAlternativeNames);
+
+ final Iterator<SubjectAlternativeName> names = subjectAlternativeNames.iterator();
+ assertTrue(names.hasNext());
+ final SubjectAlternativeName subjectAlternativeName = names.next();
+ assertEquals(GeneralNameType.REGISTERED_ID, subjectAlternativeName.getGeneralNameType());
+ assertEquals(byte[].class, subjectAlternativeName.getGeneralNameType().getValueClass());
+
+ final String registeredIdEncoded = Base64.getEncoder().encodeToString(REGISTERED_ID);
+ assertEquals(registeredIdEncoded, subjectAlternativeName.getName());
+ }
+}
diff --git a/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardPeerIdentityProviderTest.java b/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardPeerIdentityProviderTest.java
new file mode 100644
index 0000000000..ba6cc07483
--- /dev/null
+++ b/nifi-commons/nifi-security-cert/src/test/java/org/apache/nifi/security/cert/StandardPeerIdentityProviderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.cert;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardPeerIdentityProviderTest {
+ private static final String DNS_NAME = "localhost.local";
+
+ @Mock
+ private X509Certificate certificate;
+
+ private StandardPeerIdentityProvider provider;
+
+ @BeforeEach
+ void setProvider() {
+ provider = new StandardPeerIdentityProvider();
+ }
+
+ @Test
+ void testGetIdentitiesNullCertificates() {
+ assertThrows(SSLPeerUnverifiedException.class, () -> provider.getIdentities(null));
+ }
+
+ @Test
+ void testGetIdentitiesEmptyCertificates() {
+ assertThrows(SSLPeerUnverifiedException.class, () -> provider.getIdentities(new Certificate[]{}));
+ }
+
+ @Test
+ void testGetIdentitiesInvalidCertificates() throws CertificateNotYetValidException, CertificateExpiredException {
+ doThrow(new CertificateExpiredException()).when(certificate).checkValidity();
+
+ assertThrows(SSLPeerUnverifiedException.class, () -> provider.getIdentities(new Certificate[]{certificate}));
+ }
+
+ @Test
+ void testGetIdentitiesDnsNameFound() throws CertificateParsingException, SSLPeerUnverifiedException {
+ final List<Object> generalName = new ArrayList<>();
+ generalName.add(GeneralNameType.DNS_NAME.getNameType());
+ generalName.add(DNS_NAME);
+
+ when(certificate.getSubjectAlternativeNames()).thenReturn(Collections.singletonList(generalName));
+
+ final Set<String> identities = provider.getIdentities(new Certificate[]{certificate});
+
+ assertNotNull(identities);
+ final Iterator<String> names = identities.iterator();
+ assertTrue(names.hasNext());
+ final String identity = names.next();
+ assertEquals(DNS_NAME, identity);
+ }
+}
diff --git a/nifi-commons/nifi-security-utils/pom.xml b/nifi-commons/nifi-security-utils/pom.xml
index 669aa93874..c19d65c9af 100644
--- a/nifi-commons/nifi-security-utils/pom.xml
+++ b/nifi-commons/nifi-security-utils/pom.xml
@@ -47,6 +47,11 @@
<artifactId>nifi-security-utils-api</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-cert-builder</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
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
deleted file mode 100644
index c00ea50262..0000000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.security.util;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.net.ssl.SSLException;
-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.DLSequence;
-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;
-import org.bouncycastle.asn1.x500.style.BCStyle;
-import org.bouncycastle.asn1.x500.style.IETFUtils;
-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;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-import org.bouncycastle.cert.CertIOException;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
-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;
-
-public final class CertificateUtils {
- private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class);
- private static final Map<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
-
- static {
- Security.addProvider(new BouncyCastleProvider());
- }
-
- /**
- * The time in milliseconds that the last unique serial number was generated
- */
- private static long lastSerialNumberMillis = 0L;
-
- /**
- * An incrementor to add uniqueness to serial numbers generated in the same millisecond
- */
- private static int serialNumberIncrementor = 0;
-
- /**
- * BigInteger value to use for the base of the unique serial number
- */
- private static BigInteger millisecondBigInteger;
-
- private static Map<ASN1ObjectIdentifier, Integer> createDnOrderMap() {
- Map<ASN1ObjectIdentifier, Integer> orderMap = new HashMap<>();
- int count = 0;
- orderMap.put(BCStyle.CN, count++);
- orderMap.put(BCStyle.L, count++);
- orderMap.put(BCStyle.ST, count++);
- orderMap.put(BCStyle.O, count++);
- orderMap.put(BCStyle.OU, count++);
- orderMap.put(BCStyle.C, count++);
- orderMap.put(BCStyle.STREET, count++);
- orderMap.put(BCStyle.DC, count++);
- orderMap.put(BCStyle.UID, count++);
- return Collections.unmodifiableMap(orderMap);
- }
-
- /**
- * Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because
- * the DN is in an unrecognized format, the entire DN is returned.
- *
- * @param dn the dn to extract the username from
- * @return the exatracted username
- */
- public static String extractUsername(String dn) {
- String username = dn;
-
- // ensure the dn is specified
- if (StringUtils.isNotBlank(dn)) {
- // determine the separate
- final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ",";
-
- // attempt to locate the cd
- final String cnPattern = "cn=";
- final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern);
- if (cnIndex >= 0) {
- int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex);
- if (separatorIndex > 0) {
- username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex);
- } else {
- username = StringUtils.substring(dn, cnIndex + cnPattern.length());
- }
- }
-
- /*
- https://tools.ietf.org/html/rfc5280#section-4.1.2.6
-
- Legacy implementations exist where an electronic mail address is
- embedded in the subject distinguished name as an emailAddress
- attribute [RFC2985]. The attribute value for emailAddress is of type
- IA5String to permit inclusion of the character '@', which is not part
- of the PrintableString character set. emailAddress attribute values
- are not case-sensitive (e.g., "subscriber@example.com" is the same as
- "SUBSCRIBER@EXAMPLE.COM").
- */
- final String emailPattern = "/emailAddress=";
- final int index = StringUtils.indexOfIgnoreCase(username, emailPattern);
- if (index >= 0) {
- String[] dnParts = username.split(emailPattern);
- if (dnParts.length > 0) {
- // only use the actual CN
- username = dnParts[0];
- }
- }
- }
-
- return username;
- }
-
- /**
- * Returns a list of subject alternative names. Any name that is represented as a String by X509Certificate.getSubjectAlternativeNames() is converted to lowercase and returned.
- *
- * @param certificate a certificate
- * @return a list of subject alternative names; list is never null
- * @throws CertificateParsingException if parsing the certificate failed
- */
- public static List<String> getSubjectAlternativeNames(final X509Certificate certificate) throws CertificateParsingException {
-
- final Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
- if (altNames == null) {
- return new ArrayList<>();
- }
-
- final List<String> result = new ArrayList<>();
- for (final List<?> generalName : altNames) {
- /**
- * generalName has the name type as the first element a String or byte array for the second element. We return any general names that are String types.
- *
- * We don't inspect the numeric name type because some certificates incorrectly put IPs and DNS names under the wrong name types.
- */
- final Object value = generalName.get(1);
- if (value instanceof String) {
- result.add(((String) value).toLowerCase());
- }
-
- }
-
- return result;
- }
-
- /**
- * Accepts an abstract {@link java.security.cert.Certificate} and returns an {@link X509Certificate}. Because {@code sslSocket.getSession().getPeerCertificates()} returns an array of the
- * abstract certificates, they must be translated to X.509 to replace the functionality of {@code sslSocket.getSession().getPeerCertificateChain()}.
- *
- * @param abstractCertificate the {@code java.security.cert.Certificate}
- * @return a new {@code java.security.cert.X509Certificate}
- * @throws CertificateException if there is an error generating the new certificate
- */
- public static X509Certificate convertAbstractX509Certificate(java.security.cert.Certificate abstractCertificate) throws CertificateException {
- if (abstractCertificate == null || !(abstractCertificate instanceof X509Certificate)) {
- throw new IllegalArgumentException("The certificate cannot be null and must be an X.509 certificate");
- }
-
- try {
- return formX509Certificate(abstractCertificate.getEncoded());
- } catch (java.security.cert.CertificateEncodingException e) {
- throw new CertificateException(e);
- }
- }
-
- private static X509Certificate formX509Certificate(byte[] encodedCertificate) throws CertificateException {
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- ByteArrayInputStream bais = new ByteArrayInputStream(encodedCertificate);
- return (X509Certificate) cf.generateCertificate(bais);
- } catch (CertificateException e) {
- logger.error("Error converting the certificate", e);
- throw e;
- }
- }
-
- /**
- * Reorders DN to the order the elements appear in the RFC 2253 table
- * <p>
- * https://www.ietf.org/rfc/rfc2253.txt
- * <p>
- * String X.500 AttributeType
- * ------------------------------
- * CN commonName
- * L localityName
- * ST stateOrProvinceName
- * O organizationName
- * OU organizationalUnitName
- * C countryName
- * STREET streetAddress
- * DC domainComponent
- * UID userid
- *
- * @param dn a possibly unordered DN
- * @return the ordered dn
- */
- public static String reorderDn(String dn) {
- RDN[] rdNs = new X500Name(dn).getRDNs();
- Arrays.sort(rdNs, new Comparator<RDN>() {
- @Override
- public int compare(RDN o1, RDN o2) {
- AttributeTypeAndValue o1First = o1.getFirst();
- AttributeTypeAndValue o2First = o2.getFirst();
-
- ASN1ObjectIdentifier o1Type = o1First.getType();
- ASN1ObjectIdentifier o2Type = o2First.getType();
-
- Integer o1Rank = dnOrderMap.get(o1Type);
- Integer o2Rank = dnOrderMap.get(o2Type);
- if (o1Rank == null) {
- if (o2Rank == null) {
- int idComparison = o1Type.getId().compareTo(o2Type.getId());
- if (idComparison != 0) {
- return idComparison;
- }
- return String.valueOf(o1Type).compareTo(String.valueOf(o2Type));
- }
- return 1;
- } else if (o2Rank == null) {
- return -1;
- }
- return o1Rank - o2Rank;
- }
- });
- return new X500Name(rdNs).toString();
- }
-
- /**
- * Reverses the X500Name in order make the certificate be in the right order
- * [see http://stackoverflow.com/questions/7567837/attributes-reversed-in-certificate-subject-and-issuer/12645265]
- *
- * @param x500Name the X500Name created with the intended order
- * @return the X500Name reversed
- */
- private static X500Name reverseX500Name(X500Name x500Name) {
- List<RDN> rdns = Arrays.asList(x500Name.getRDNs());
- Collections.reverse(rdns);
- return new X500Name(rdns.toArray(new RDN[rdns.size()]));
- }
-
- /**
- * Generates a unique serial number by using the current time in milliseconds left shifted 32 bits (to make room for incrementor) with an incrementor added
- *
- * @return a unique serial number (technically unique to this classloader)
- */
- protected static synchronized BigInteger getUniqueSerialNumber() {
- final long currentTimeMillis = System.currentTimeMillis();
- final int incrementorValue;
-
- if (lastSerialNumberMillis != currentTimeMillis) {
- // We can only get into this block once per millisecond
- millisecondBigInteger = BigInteger.valueOf(currentTimeMillis).shiftLeft(32);
- lastSerialNumberMillis = currentTimeMillis;
- incrementorValue = 0;
- serialNumberIncrementor = 1;
- } else {
- // Already created at least one serial number this millisecond
- incrementorValue = serialNumberIncrementor++;
- }
-
- return millisecondBigInteger.add(BigInteger.valueOf(incrementorValue));
- }
-
- /**
- * Generates a self-signed {@link X509Certificate} suitable for use as a Certificate Authority.
- *
- * @param keyPair the {@link KeyPair} to generate the {@link X509Certificate} for
- * @param dn the distinguished name to user for the {@link X509Certificate}
- * @param signingAlgorithm the signing algorithm to use for the {@link X509Certificate}
- * @param certificateDurationDays the duration in days for which the {@link X509Certificate} should be valid
- * @return a self-signed {@link X509Certificate} suitable for use as a Certificate Authority
- * @throws CertificateException if there is an generating the new certificate
- */
- public static X509Certificate generateSelfSignedX509Certificate(KeyPair keyPair, String dn, String signingAlgorithm, int certificateDurationDays)
- throws CertificateException {
- return generateSelfSignedX509Certificate(keyPair, dn, signingAlgorithm, certificateDurationDays, null);
- }
-
- /**
- * Generates a self-signed {@link X509Certificate} suitable for use as a Certificate Authority.
- *
- * @param keyPair the {@link KeyPair} to generate the {@link X509Certificate} for
- * @param dn the distinguished name to user for the {@link X509Certificate}
- * @param signingAlgorithm the signing algorithm to use for the {@link X509Certificate}
- * @param certificateDurationDays the duration in days for which the {@link X509Certificate} should be valid
- * @param dnsSubjectAlternativeNames An optional array of dnsName SANs
- * @return a self-signed {@link X509Certificate} suitable for use as a Certificate Authority
- * @throws CertificateException if there is an generating the new certificate
- */
- public static X509Certificate generateSelfSignedX509Certificate(KeyPair keyPair, String dn, String signingAlgorithm, int certificateDurationDays,
- String[] dnsSubjectAlternativeNames)
- throws CertificateException {
- try {
- ContentSigner sigGen = new JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
- SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
- Date startDate = new Date();
- Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(certificateDurationDays));
-
- X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
- reverseX500Name(new X500Name(dn)),
- getUniqueSerialNumber(),
- startDate, endDate,
- reverseX500Name(new X500Name(dn)),
- subPubKeyInfo);
-
- // Set certificate extensions
- // (1) digitalSignature extension
- certBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment
- | KeyUsage.keyAgreement | KeyUsage.nonRepudiation | KeyUsage.cRLSign | KeyUsage.keyCertSign));
-
- certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
-
- certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(keyPair.getPublic()));
-
- certBuilder.addExtension(Extension.authorityKeyIdentifier, false, new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.getPublic()));
-
- // (2) extendedKeyUsage extension
- certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
-
- // (3) subjectAlternativeName extension. Include CN as a SAN entry if it exists.
- final String cn = getCommonName(dn);
- List<GeneralName> generalNames = new ArrayList<>();
- if (StringUtils.isNotBlank(cn)) {
- generalNames.add(new GeneralName(GeneralName.dNSName, cn));
- }
- if (dnsSubjectAlternativeNames != null) {
- for (String subjectAlternativeName : dnsSubjectAlternativeNames) {
- if (StringUtils.isNotBlank(subjectAlternativeName)) {
- generalNames.add(new GeneralName(GeneralName.dNSName, subjectAlternativeName));
- }
- }
- }
- if (!generalNames.isEmpty()) {
- certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(generalNames.toArray(
- new GeneralName[generalNames.size()])));
- }
-
- // Sign the certificate
- X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
- return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
- } catch (CertIOException | NoSuchAlgorithmException | OperatorCreationException e) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * 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 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, 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());
- Date startDate = new Date();
- Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(days));
-
- X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
- reverseX500Name(new X500Name(issuer.getSubjectX500Principal().getName())),
- getUniqueSerialNumber(),
- startDate, endDate,
- reverseX500Name(new X500Name(dn)),
- subPubKeyInfo);
-
- certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey));
-
- certBuilder.addExtension(Extension.authorityKeyIdentifier, false, new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(issuerKeyPair.getPublic()));
- // Set certificate extensions
- // (1) digitalSignature extension
- certBuilder.addExtension(Extension.keyUsage, true,
- new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement | KeyUsage.nonRepudiation));
-
- certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
-
- // (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) {
- throw new CertificateException(e);
- }
- }
-
- /**
- * Returns true if the two provided DNs are equivalent, regardless of the order of the elements. Returns false if one or both are invalid DNs.
- * <p>
- * Example:
- * <p>
- * CN=test1, O=testOrg, C=US compared to CN=test1, O=testOrg, C=US -> true
- * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test1, C=US -> true
- * CN=test1, O=testOrg, C=US compared to CN=test2, O=testOrg, C=US -> false
- * CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test2, C=US -> false
- * CN=test1, O=testOrg, C=US compared to -> false
- * compared to -> true
- *
- * @param dn1 the first DN to compare
- * @param dn2 the second DN to compare
- * @return true if the DNs are equivalent, false otherwise
- */
- public static boolean compareDNs(String dn1, String dn2) {
- if (dn1 == null) {
- dn1 = "";
- }
-
- if (dn2 == null) {
- dn2 = "";
- }
-
- if (StringUtils.isEmpty(dn1) || StringUtils.isEmpty(dn2)) {
- return dn1.equals(dn2);
- }
- try {
- List<Rdn> rdn1 = new LdapName(dn1).getRdns();
- List<Rdn> rdn2 = new LdapName(dn2).getRdns();
-
- return rdn1.size() == rdn2.size() && rdn1.containsAll(rdn2);
- } catch (InvalidNameException e) {
- logger.warn("Cannot compare DNs: {} and {} because one or both is not a valid DN", dn1, dn2);
- return false;
- }
- }
-
- /**
- * 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 || extension instanceof DLSequence) {
- return Extensions.getInstance(extension);
- }
- }
- }
- return null;
- }
-
- /**
- * Returns {@code true} if this exception is due to a TLS problem (either directly or because of its cause, if present). Traverses the cause chain recursively.
- *
- * @param e the exception to evaluate
- * @return true if the direct or indirect cause of this exception was TLS-related
- */
- public static boolean isTlsError(Throwable e) {
- if (e == null) {
- return false;
- } else {
- if (e instanceof CertificateException || e instanceof TlsException || e instanceof SSLException) {
- return true;
- } else if (e.getCause() != null) {
- return isTlsError(e.getCause());
- } else {
- return false;
- }
- }
- }
-
- /**
- * Extracts the common name from the given DN.
- *
- * @param dn the distinguished name to evaluate
- * @return the common name if it exists, null otherwise.
- */
- public static String getCommonName(final String dn) {
- RDN[] rdns = new X500Name(dn).getRDNs(BCStyle.CN);
- if (rdns.length == 0) {
- return null;
- }
- return IETFUtils.valueToString(rdns[0].getFirst().getValue());
- }
-
- private CertificateUtils() {
- }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
index 2c00e52b67..1a7b60d01d 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
@@ -39,17 +39,20 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,10 +67,8 @@ public class KeyStoreUtils {
private static final String BCFKS_EXT = ".bcfks";
private static final String KEY_ALIAS = "nifi-key";
private static final String CERT_ALIAS = "nifi-cert";
- private static final String CERT_DN = "CN=localhost";
+ private static final X500Principal CERT_DN = new X500Principal("CN=localhost");
private static final String KEY_ALGORITHM = "RSA";
- private static final String SIGNING_ALGORITHM = "SHA256withRSA";
- private static final int CERT_DURATION_DAYS = 365;
private static final int PASSWORD_LENGTH = 16;
private static final String TEST_KEYSTORE_PREFIX = "test-keystore-";
private static final String TEST_TRUSTSTORE_PREFIX = "test-truststore-";
@@ -495,22 +496,6 @@ public class KeyStoreUtils {
return SECRET_KEY_STORE_PROVIDERS.containsKey(keystoreType);
}
- public static String sslContextToString(SSLContext sslContext) {
- return new ToStringBuilder(sslContext)
- .append("protocol", sslContext.getProtocol())
- .append("provider", sslContext.getProvider().toString())
- .toString();
- }
-
- public static String sslServerSocketToString(SSLServerSocket sslServerSocket) {
- return new ToStringBuilder(sslServerSocket)
- .append("enabledProtocols", sslServerSocket.getEnabledProtocols())
- .append("needClientAuth", sslServerSocket.getNeedClientAuth())
- .append("wantClientAuth", sslServerSocket.getWantClientAuth())
- .append("useClientMode", sslServerSocket.getUseClientMode())
- .toString();
- }
-
/**
* Loads the Keystore and returns a X509 Certificate with the given values.
*
@@ -528,13 +513,13 @@ public class KeyStoreUtils {
final KeystoreType keyStoreType, int certDurationDays, String[] dnsSubjectAlternativeNames)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
- try (final FileOutputStream outputStream = new FileOutputStream(keyStorePath)) {
- final KeyPair keyPair = KeyPairGenerator.getInstance(KEY_ALGORITHM).generateKeyPair();
-
- final X509Certificate selfSignedCert = CertificateUtils.generateSelfSignedX509Certificate(
- keyPair, CERT_DN, SIGNING_ALGORITHM, certDurationDays, dnsSubjectAlternativeNames
- );
+ final KeyPair keyPair = KeyPairGenerator.getInstance(KEY_ALGORITHM).generateKeyPair();
+ final List<String> dnsNames = dnsSubjectAlternativeNames == null ? Collections.emptyList() : Arrays.asList(dnsSubjectAlternativeNames);
+ final X509Certificate selfSignedCert = new StandardCertificateBuilder(keyPair, CERT_DN, Duration.ofDays(certDurationDays))
+ .setDnsSubjectAlternativeNames(dnsNames)
+ .build();
+ try (final FileOutputStream outputStream = new FileOutputStream(keyStorePath)) {
final KeyStore keyStore = loadEmptyKeyStore(keyStoreType);
keyStore.setKeyEntry(alias, keyPair.getPrivate(), keyPassword.toCharArray(), new Certificate[]{selfSignedCert});
keyStore.store(outputStream, keyStorePassword.toCharArray());
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java
index ebf7c3643b..8914a5f4a2 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TemporaryKeyStoreBuilder.java
@@ -16,8 +16,10 @@
*/
package org.apache.nifi.security.util;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.security.configuration.KeyStoreConfiguration;
+import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,7 +34,10 @@ import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -178,13 +183,11 @@ public class TemporaryKeyStoreBuilder {
}
private X509Certificate generateCertificate(final String hostname, final KeyPair keyPair) {
- final String distinguishedName = String.format(DISTINGUISHED_NAME_FORMAT, hostname);
- final String[] dnsNames = new String[] {hostname};
- try {
- return CertificateUtils.generateSelfSignedX509Certificate(keyPair, distinguishedName, SIGNING_ALGORITHM, CERTIFICATE_VALID_DAYS, dnsNames);
- } catch (final CertificateException e) {
- throw new RuntimeException("Certificate Generated Failed", e);
- }
+ final X500Principal distinguishedName = new X500Principal(String.format(DISTINGUISHED_NAME_FORMAT, hostname));
+ final List<String> dnsNames = Collections.singletonList(hostname);
+ return new StandardCertificateBuilder(keyPair, distinguishedName, Duration.ofDays(CERTIFICATE_VALID_DAYS))
+ .setDnsSubjectAlternativeNames(dnsNames)
+ .build();
}
private KeyPair generateKeyPair() {
diff --git a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
index f62a0d5745..95dd79a7a2 100644
--- a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
+++ b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
@@ -18,11 +18,13 @@
package org.apache.nifi.security.util;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -34,8 +36,8 @@ import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -44,7 +46,6 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class KeyStoreUtilsTest {
- private static final String SIGNING_ALGORITHM = "SHA256withRSA";
private static final int DURATION_DAYS = 365;
private static final char[] KEY_PASSWORD = UUID.randomUUID().toString().toCharArray();
private static final char[] STORE_PASSWORD = UUID.randomUUID().toString().toCharArray();
@@ -61,9 +62,9 @@ public class KeyStoreUtilsTest {
private static SecretKey secretKey;
@BeforeAll
- public static void generateKeysAndCertificates() throws NoSuchAlgorithmException, CertificateException {
+ public static void generateKeysAndCertificates() throws NoSuchAlgorithmException {
keyPair = KeyPairGenerator.getInstance(KEY_ALGORITHM).generateKeyPair();
- certificate = CertificateUtils.generateSelfSignedX509Certificate(keyPair, SUBJECT_DN, SIGNING_ALGORITHM, DURATION_DAYS);
+ certificate = new StandardCertificateBuilder(keyPair, new X500Principal(SUBJECT_DN), Duration.ofDays(DURATION_DAYS)).build();
final byte[] encodedKey = StringUtils.remove(UUID.randomUUID().toString(), HYPHEN_SEPARATOR).getBytes(StandardCharsets.UTF_8);
secretKey = new SecretKeySpec(encodedKey, SECRET_KEY_ALGORITHM);
}
diff --git a/nifi-commons/nifi-socket-utils/pom.xml b/nifi-commons/nifi-socket-utils/pom.xml
index 72a7da6496..f42c15b418 100644
--- a/nifi-commons/nifi-socket-utils/pom.xml
+++ b/nifi-commons/nifi-socket-utils/pom.xml
@@ -32,6 +32,11 @@
<artifactId>nifi-security-utils</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-utils-api</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-logging-utils</artifactId>
diff --git a/nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketUtils.java b/nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketUtils.java
index 43556a7ac3..adc24caf03 100644
--- a/nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketUtils.java
+++ b/nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketUtils.java
@@ -20,7 +20,9 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import org.apache.nifi.logging.NiFiLog;
@@ -208,4 +210,23 @@ public final class SocketUtils {
}
}
+ /**
+ * Returns {@code true} if this exception is due to a TLS problem (either directly or because of its cause, if present). Traverses the cause chain recursively.
+ *
+ * @param e the exception to evaluate
+ * @return true if the direct or indirect cause of this exception was TLS-related
+ */
+ public static boolean isTlsError(Throwable e) {
+ if (e == null) {
+ return false;
+ } else {
+ if (e instanceof CertificateException || e instanceof TlsException || e instanceof SSLException) {
+ return true;
+ } else if (e.getCause() != null) {
+ return isTlsError(e.getCause());
+ } else {
+ return false;
+ }
+ }
+ }
}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 9e3d8ecebf..6d7ad96fc0 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -54,6 +54,8 @@
<module>nifi-record-path</module>
<module>nifi-repository-encryption</module>
<module>nifi-schema-utils</module>
+ <module>nifi-security-cert</module>
+ <module>nifi-security-cert-builder</module>
<module>nifi-security-crypto-key</module>
<module>nifi-security-identity</module>
<module>nifi-security-kerberos-api</module>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml
index 31cf58a25a..9c3c3d8245 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml
@@ -52,6 +52,11 @@
<artifactId>nifi-security-utils</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-cert</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-xml-processing</artifactId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
index bc81cca43e..6aaf866956 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
@@ -20,15 +20,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.stream.Collectors;
-import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
@@ -48,9 +44,11 @@ import org.apache.nifi.cluster.protocol.message.ReconnectionRequestMessage;
import org.apache.nifi.events.BulletinFactory;
import org.apache.nifi.io.socket.ServerSocketConfiguration;
import org.apache.nifi.io.socket.SocketListener;
+import org.apache.nifi.io.socket.SocketUtils;
import org.apache.nifi.reporting.Bulletin;
import org.apache.nifi.reporting.BulletinRepository;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.PeerIdentityProvider;
+import org.apache.nifi.security.cert.StandardPeerIdentityProvider;
import org.apache.nifi.stream.io.ByteCountingInputStream;
import org.apache.nifi.util.StopWatch;
import org.slf4j.Logger;
@@ -62,11 +60,14 @@ import org.slf4j.LoggerFactory;
public class SocketProtocolListener extends SocketListener implements ProtocolListener {
private static final Logger logger = LoggerFactory.getLogger(SocketProtocolListener.class);
+
+ private final PeerIdentityProvider peerIdentityProvider = new StandardPeerIdentityProvider();
+
private final ProtocolContext<ProtocolMessage> protocolContext;
private final Collection<ProtocolHandler> handlers = new CopyOnWriteArrayList<>();
private volatile BulletinRepository bulletinRepository;
- private static int EXCEPTION_THRESHOLD_MILLIS = 10_000;
+ private static final int EXCEPTION_THRESHOLD_MILLIS = 10_000;
private volatile long tlsErrorLastSeen = -1;
public SocketProtocolListener(
@@ -195,7 +196,7 @@ public class SocketProtocolListener extends SocketListener implements ProtocolLi
} catch (final IOException | ProtocolException e) {
String msg = "Failed processing protocol message from " + hostname + " due to ";
// Suppress repeated TLS errors
- if (CertificateUtils.isTlsError(e)) {
+ if (SocketUtils.isTlsError(e)) {
boolean printedAsWarning = handleTlsError(msg, e);
// TODO: Move into handleTlsError and refactor shared behavior
@@ -230,7 +231,7 @@ public class SocketProtocolListener extends SocketListener implements ProtocolLi
}
/**
- * Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
+ * Returns {@code true} if any related exception (determined by TLS error status) has occurred within the last
* {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
* simply checks the last time the timestamp was updated.
*
@@ -266,30 +267,11 @@ public class SocketProtocolListener extends SocketListener implements ProtocolLi
private Set<String> getCertificateIdentities(final Socket socket) throws IOException {
if (socket instanceof SSLSocket) {
- try {
- final SSLSession sslSession = ((SSLSocket) socket).getSession();
- return getCertificateIdentities(sslSession);
- } catch (CertificateException e) {
- throw new IOException("Could not extract Subject Alternative Names from client's certificate", e);
- }
+ final SSLSession sslSession = ((SSLSocket) socket).getSession();
+ final Certificate[] peerCertificates = sslSession.getPeerCertificates();
+ return peerIdentityProvider.getIdentities(peerCertificates);
} else {
return Collections.emptySet();
}
}
-
- private Set<String> getCertificateIdentities(final SSLSession sslSession) throws CertificateException, SSLPeerUnverifiedException {
- final Certificate[] certs = sslSession.getPeerCertificates();
- if (certs == null || certs.length == 0) {
- throw new SSLPeerUnverifiedException("No certificates found");
- }
-
- final X509Certificate cert = CertificateUtils.convertAbstractX509Certificate(certs[0]);
- cert.checkValidity();
-
- final Set<String> identities = CertificateUtils.getSubjectAlternativeNames(cert).stream()
- .map(CertificateUtils::extractUsername)
- .collect(Collectors.toSet());
-
- return identities;
- }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index ea9904350a..9dcde3d254 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -82,6 +82,11 @@
<artifactId>nifi-security-identity</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-cert</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-repository-encryption</artifactId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ClusterLoadBalanceAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ClusterLoadBalanceAuthorizer.java
index fbd849ce68..2e3310ee99 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ClusterLoadBalanceAuthorizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ClusterLoadBalanceAuthorizer.java
@@ -22,24 +22,24 @@ import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.events.EventReporter;
import org.apache.nifi.reporting.Severity;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.PeerIdentityProvider;
+import org.apache.nifi.security.cert.StandardPeerIdentityProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
import java.util.Set;
import java.util.stream.Collectors;
public class ClusterLoadBalanceAuthorizer implements LoadBalanceAuthorizer {
private static final Logger logger = LoggerFactory.getLogger(ClusterLoadBalanceAuthorizer.class);
+ private final PeerIdentityProvider peerIdentityProvider = new StandardPeerIdentityProvider();
+
private final ClusterCoordinator clusterCoordinator;
private final EventReporter eventReporter;
private final HostnameVerifier hostnameVerifier;
@@ -51,15 +51,11 @@ public class ClusterLoadBalanceAuthorizer implements LoadBalanceAuthorizer {
}
@Override
- public String authorize(SSLSocket sslSocket) throws NotAuthorizedException, IOException {
+ public String authorize(SSLSocket sslSocket) throws IOException {
final SSLSession sslSession = sslSocket.getSession();
- final Set<String> clientIdentities;
- try {
- clientIdentities = getCertificateIdentities(sslSession);
- } catch (final CertificateException e) {
- throw new IOException("Failed to extract Client Certificate", e);
- }
+ final Certificate[] peerCertificates = sslSession.getPeerCertificates();
+ final Set<String> clientIdentities = peerIdentityProvider.getIdentities(peerCertificates);
logger.debug("Will perform authorization against Client Identities '{}'", clientIdentities);
@@ -89,20 +85,4 @@ public class ClusterLoadBalanceAuthorizer implements LoadBalanceAuthorizer {
eventReporter.reportEvent(Severity.WARNING, "Load Balanced Connections", message);
throw new NotAuthorizedException("Client ID's " + clientIdentities + " are not authorized to Load Balance data");
}
-
- private Set<String> getCertificateIdentities(final SSLSession sslSession) throws CertificateException, SSLPeerUnverifiedException {
- final Certificate[] certs = sslSession.getPeerCertificates();
- if (certs == null || certs.length == 0) {
- throw new SSLPeerUnverifiedException("No certificates found");
- }
-
- final X509Certificate cert = CertificateUtils.convertAbstractX509Certificate(certs[0]);
- cert.checkValidity();
-
- final Set<String> identities = CertificateUtils.getSubjectAlternativeNames(cert).stream()
- .map(CertificateUtils::extractUsername)
- .collect(Collectors.toSet());
-
- return identities;
- }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServer.java
index bc4bc344a3..c1782ab08a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServer.java
@@ -37,8 +37,8 @@ import javax.net.ssl.SSLServerSocket;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.events.EventReporter;
+import org.apache.nifi.io.socket.SocketUtils;
import org.apache.nifi.reporting.Severity;
-import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.TlsConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -181,7 +181,7 @@ public class ConnectionLoadBalanceServer {
/* The exceptions can fill the log very quickly and make it difficult to use. SSLPeerUnverifiedExceptions
especially repeat and have a long stacktrace, and are not likely to be resolved instantaneously. Suppressing
them for a period of time is helpful */
- if (CertificateUtils.isTlsError(e)) {
+ if (SocketUtils.isTlsError(e)) {
handleTlsError(channelDescription, e);
} else {
logger.error("Failed to communicate over Channel {}", channelDescription, e);
@@ -220,7 +220,7 @@ public class ConnectionLoadBalanceServer {
/**
- * Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
+ * Returns {@code true} if any related exception (determined by TLS error status) has occurred within the last
* {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
* simply checks the last time the timestamp was updated.
*
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/providers/zookeeper/ITZooKeeperStateProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/providers/zookeeper/ITZooKeeperStateProvider.java
deleted file mode 100644
index 65ddc1478b..0000000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/providers/zookeeper/ITZooKeeperStateProvider.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.controller.state.providers.zookeeper;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.curator.test.InstanceSpec;
-import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.PropertyValue;
-import org.apache.nifi.components.state.StateProvider;
-import org.apache.nifi.components.state.StateProviderInitializationContext;
-import org.apache.nifi.components.state.exception.StateTooLargeException;
-import org.apache.nifi.controller.state.providers.AbstractTestStateProvider;
-import org.apache.nifi.logging.ComponentLog;
-import org.apache.nifi.mock.MockComponentLogger;
-import org.apache.nifi.parameter.ParameterLookup;
-import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
-import org.apache.nifi.security.util.TlsConfiguration;
-import org.apache.nifi.util.NiFiProperties;
-import org.apache.zookeeper.server.ServerCnxnFactory;
-import org.apache.zookeeper.server.ZooKeeperServer;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.net.ssl.SSLContext;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import static org.apache.nifi.leader.election.ITSecureClientZooKeeperFactory.createAndStartServer;
-import static org.apache.nifi.leader.election.ITSecureClientZooKeeperFactory.createSecureClientProperties;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-
-public class ITZooKeeperStateProvider extends AbstractTestStateProvider {
-
- private static final Logger logger = LoggerFactory.getLogger(ITZooKeeperStateProvider.class);
-
- private volatile StateProvider provider;
- private volatile ZooKeeperServer zkServer;
- private final static Map<PropertyDescriptor, String> stateProviderProperties = new HashMap<>();
- private static Path tempDir;
- private static NiFiProperties nifiProperties;
-
- private static TlsConfiguration tlsConfiguration;
-
- @BeforeAll
- public static void setTlsConfiguration() {
- tlsConfiguration = new TemporaryKeyStoreBuilder().build();
- }
-
- @BeforeEach
- public void setup() throws Exception {
- tempDir = Paths.get("target/TestZooKeeperStateProvider");
- final Path dataDir = tempDir.resolve("state");
- final int clientPort = InstanceSpec.getRandomPort();
-
- Files.createDirectory(tempDir);
-
- // Set up the testing server
- final ServerCnxnFactory serverConnectionFactory = createAndStartServer(
- dataDir,
- tempDir,
- clientPort,
- Paths.get(tlsConfiguration.getKeystorePath()),
- tlsConfiguration.getKeystorePassword(),
- Paths.get(tlsConfiguration.getTruststorePath()),
- tlsConfiguration.getTruststorePassword()
- );
- zkServer = serverConnectionFactory.getZooKeeperServer();
-
- // Set up state provider (client) TLS properties, normally injected through StateProviderContext annotation
- nifiProperties = createSecureClientProperties(
- clientPort,
- Paths.get(tlsConfiguration.getKeystorePath()),
- tlsConfiguration.getKeystoreType().getType(),
- tlsConfiguration.getKeystorePassword(),
- Paths.get(tlsConfiguration.getTruststorePath()),
- tlsConfiguration.getTruststoreType().getType(),
- tlsConfiguration.getTruststorePassword()
- );
-
- // Set up state provider properties
- stateProviderProperties.put(ZooKeeperStateProvider.SESSION_TIMEOUT, "15 secs");
- stateProviderProperties.put(ZooKeeperStateProvider.ROOT_NODE, "/nifi/team1/testing");
- stateProviderProperties.put(ZooKeeperStateProvider.ACCESS_CONTROL, ZooKeeperStateProvider.OPEN_TO_WORLD.getValue());
- final Map<PropertyDescriptor, String> properties = new HashMap<>(stateProviderProperties);
- properties.put(ZooKeeperStateProvider.CONNECTION_STRING, "localhost:".concat(String.valueOf(clientPort)));
- this.provider = createProvider(properties);
- }
-
- private void initializeProvider(final ZooKeeperStateProvider provider, final Map<PropertyDescriptor, String> properties) throws IOException {
- provider.setNiFiProperties(nifiProperties);
- provider.initialize(new StateProviderInitializationContext() {
- @Override
- public String getIdentifier() {
- return "Unit Test Provider Initialization Context";
- }
-
- @Override
- public Map<PropertyDescriptor, PropertyValue> getProperties() {
- final Map<PropertyDescriptor, PropertyValue> propValueMap = new HashMap<>();
- for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
- propValueMap.put(entry.getKey(), new StandardPropertyValue(entry.getValue(), null, ParameterLookup.EMPTY));
- }
- return propValueMap;
- }
-
- @Override
- public Map<String,String> getAllProperties() {
- final Map<String,String> propValueMap = new LinkedHashMap<>();
- for (final Map.Entry<PropertyDescriptor, PropertyValue> entry : getProperties().entrySet()) {
- propValueMap.put(entry.getKey().getName(), entry.getValue().getValue());
- }
-
- propValueMap.put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, Boolean.TRUE.toString());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
- propValueMap.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
-
- return propValueMap;
- }
-
- @Override
- public PropertyValue getProperty(final PropertyDescriptor property) {
- final String prop = properties.get(property);
- return new StandardPropertyValue(prop, null, ParameterLookup.EMPTY);
- }
-
- // This won't be used by the ZooKeeper State Provider. I don't believe there's a way to pass an SSLContext
- // directly to ZooKeeper anyway.
- @Override
- public SSLContext getSSLContext() {
- return null;
- }
-
- @Override
- public ComponentLog getLogger() {
- return new MockComponentLogger();
- }
- });
- }
-
- private ZooKeeperStateProvider createProvider(final Map<PropertyDescriptor, String> properties) throws Exception {
- final ZooKeeperStateProvider provider = new ZooKeeperStateProvider();
- initializeProvider(provider, properties);
- provider.enable();
- return provider;
- }
-
- @AfterEach
- public void clear() throws IOException {
- try {
- if (provider != null) {
- provider.onComponentRemoved(componentId);
- provider.disable();
- provider.shutdown();
- }
- } finally {
- if (zkServer != null) {
- zkServer.shutdown(true);
- clearDirectories();
- }
- }
- }
-
- private static void clearDirectories() {
- try {
- FileUtils.deleteDirectory(new File(tempDir.toString()));
- } catch (IOException e) {
- logger.error("Failed to delete: " + tempDir.toString(), e);
- }
- }
-
- @Override
- protected StateProvider getProvider() {
- return provider;
- }
-
- @Timeout(30)
- @Test
- public void testStateTooLargeExceptionThrownOnSetState() throws InterruptedException {
- final Map<String, String> state = new HashMap<>();
- final StringBuilder sb = new StringBuilder();
-
- // Build a string that is a little less than 64 KB, because that's
- // the largest value available for DataOutputStream.writeUTF
- for (int i = 0; i < 6500; i++) {
- sb.append("0123456789");
- }
-
- for (int i = 0; i < 20; i++) {
- state.put("numbers." + i, sb.toString());
- }
-
- while (true) {
- try {
- getProvider().setState(state, componentId);
- fail("Expected StateTooLargeException");
- } catch (final StateTooLargeException stle) {
- // expected behavior.
- break;
- } catch (final IOException ioe) {
- // If we attempt to interact with the server too quickly, we will get a
- // ZooKeeper ConnectionLoss Exception, which the provider wraps in an IOException.
- // We will wait 1 second in this case and try again. The test will timeout if this
- // does not succeeed within 30 seconds.
- Thread.sleep(1000L);
- } catch (final Exception e) {
- logger.error("Something went wrong attempting to set the state in testStateTooLargeExceptionThrownOnSetState()");
- fail("Expected StateTooLargeException but " + e.getClass() + " was thrown", e);
- }
- }
- }
-
- @Timeout(30)
- @Test
- public void testStateTooLargeExceptionThrownOnReplace() throws InterruptedException {
- final Map<String, String> state = new HashMap<>();
- final StringBuilder sb = new StringBuilder();
-
- // Build a string that is a little less than 64 KB, because that's
- // the largest value available for DataOutputStream.writeUTF
- for (int i = 0; i < 6500; i++) {
- sb.append("0123456789");
- }
-
- for (int i = 0; i < 20; i++) {
- state.put("numbers." + i, sb.toString());
- }
-
- final Map<String, String> smallState = new HashMap<>();
- smallState.put("abc", "xyz");
-
- while (true) {
- try {
- getProvider().setState(smallState, componentId);
- break;
- } catch (final IOException ioe) {
- // If we attempt to interact with the server too quickly, we will get a
- // ZooKeeper ConnectionLoss Exception, which the provider wraps in an IOException.
- // We will wait 1 second in this case and try again. The test will timeout if this
- // does not succeeed within 30 seconds.
- Thread.sleep(1000L);
- }
- }
-
- assertThrows(StateTooLargeException.class, () -> getProvider().replace(getProvider().getState(componentId), state, componentId));
- }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/ITZooKeeperStateServerTLS.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/ITZooKeeperStateServerTLS.java
deleted file mode 100644
index 22d43a7cea..0000000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/ITZooKeeperStateServerTLS.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.controller.state.server;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.CuratorFrameworkFactory;
-import org.apache.curator.retry.RetryOneTime;
-import org.apache.curator.utils.DefaultZookeeperFactory;
-import org.apache.curator.utils.ZookeeperFactory;
-import org.apache.nifi.controller.cluster.SecureClientZooKeeperFactory;
-import org.apache.nifi.controller.cluster.ZooKeeperClientConfig;
-import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
-import org.apache.nifi.security.util.TlsConfiguration;
-import org.apache.nifi.util.NiFiProperties;
-import org.apache.zookeeper.KeeperException;
-import org.apache.zookeeper.data.Stat;
-import org.apache.zookeeper.server.ServerCnxnFactory;
-import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.apache.nifi.leader.election.ITSecureClientZooKeeperFactory.createSecureClientProperties;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-// Testing setting up a ZooKeeperStateServer with TLS
-public class ITZooKeeperStateServerTLS {
- private static final String INSECURE_ZOOKEEPER_PROPS = getPath("insecure.zookeeper.properties");
- private static final String PARTIAL_ZOOKEEPER_PROPS = getPath("partial.zookeeper.properties");
- private static final String SECURE_ZOOKEEPER_PROPS = getPath("secure.zookeeper.properties");
- private static final String ZOOKEEPER_PROPERTIES_FILE_KEY = "nifi.state.management.embedded.zookeeper.properties";
- private static final String ZOOKEEPER_CNXN_FACTORY = "org.apache.zookeeper.server.NettyServerCnxnFactory";
- private static final String QUORUM_CONNECT_STRING = "node0.apache.org:2281,node1.apache.org:2281";
-
- private static final Map<String, String> SECURE_NIFI_PROPS = new HashMap<>();
- private static final Map<String, String> SECURE_ZOOKEEPER_NIFI_PROPS = new HashMap<>();
-
- private static TlsConfiguration tlsConfiguration;
-
- @BeforeAll
- public static void setTlsConfiguration() {
- tlsConfiguration = new TemporaryKeyStoreBuilder().build();
-
- SECURE_NIFI_PROPS.put(NiFiProperties.STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES, SECURE_ZOOKEEPER_PROPS);
- SECURE_NIFI_PROPS.put(NiFiProperties.WEB_HTTPS_PORT, "8443");
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
- SECURE_NIFI_PROPS.put(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
- SECURE_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true");
-
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES, SECURE_ZOOKEEPER_PROPS);
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.WEB_HTTPS_PORT, "8443");
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
- SECURE_ZOOKEEPER_NIFI_PROPS.put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true");
- }
-
- private static final Map<String, String> INSECURE_NIFI_PROPS = new HashMap<String, String>() {{
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.WEB_HTTP_HOST, "localhost");
- put(NiFiProperties.WEB_HTTP_PORT, "8080");
- put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "false");
- }};
-
- private NiFiProperties niFiProps;
- private static NiFiProperties clientProperties;
- private QuorumPeerConfig quorumPeerConfig;
- private Properties secureZooKeeperProps;
- private Properties insecureZooKeeperProps;
- private Properties partialZooKeeperProps;
- private ZooKeeperStateServer server;
-
- @BeforeEach
- public void setupWithValidProperties() throws IOException, QuorumPeerConfig.ConfigException {
- niFiProps = NiFiProperties.createBasicNiFiProperties(null, SECURE_NIFI_PROPS);
- assertNotNull(niFiProps);
-
- // This shows that a ZooKeeper server is created from valid NiFi properties:
- final ZooKeeperStateServer zooKeeperStateServer = ZooKeeperStateServer.create(niFiProps);
- assertNotNull(zooKeeperStateServer);
-
- quorumPeerConfig = zooKeeperStateServer.getQuorumPeerConfig();
- assertNotNull(quorumPeerConfig);
-
- secureZooKeeperProps = new Properties();
- secureZooKeeperProps.load(FileUtils.openInputStream(new File(SECURE_ZOOKEEPER_PROPS)));
- assertNotNull(secureZooKeeperProps);
-
- insecureZooKeeperProps = new Properties();
- insecureZooKeeperProps.load(FileUtils.openInputStream(new File(INSECURE_ZOOKEEPER_PROPS)));
- assertNotNull(insecureZooKeeperProps);
-
- partialZooKeeperProps = new Properties();
- partialZooKeeperProps.load(FileUtils.openInputStream(new File(PARTIAL_ZOOKEEPER_PROPS)));
- assertNotNull(partialZooKeeperProps);
- }
-
- // This test shows that a ZooKeeperStateServer cannot be created from empty NiFi properties.
- @Test
- public void testCreateFromEmptyNiFiProperties() throws IOException, QuorumPeerConfig.ConfigException {
- final NiFiProperties emptyProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<>());
-
- assertNotNull(emptyProps);
- assertNull(ZooKeeperStateServer.create(emptyProps));
- }
-
- // This test shows that a ZooKeeperStateServer can be created from insecure NiFi properties.
- @Test
- public void testCreateFromValidInsecureNiFiProperties() throws IOException, QuorumPeerConfig.ConfigException {
- final NiFiProperties insecureProps = NiFiProperties.createBasicNiFiProperties(null, INSECURE_NIFI_PROPS);
- final ZooKeeperStateServer server = ZooKeeperStateServer.create(insecureProps);
-
- assertNotNull(server);
- assertNotNull(server.getQuorumPeerConfig().getClientPortAddress());
- }
-
- // This test shows that a ZK TLS config with some but not all values throws an exception:
- @Test
- public void testCreateFromPartialZooKeeperTlsProperties() {
- final NiFiProperties partialProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, PARTIAL_ZOOKEEPER_PROPS);
- }});
-
- assertThrows(QuorumPeerConfig.ConfigException.class, () ->
- ZooKeeperStateServer.create(partialProps));
- }
-
- // This test shows that a ZK TLS config with all values set is valid and a server can be created using it:
- @Test
- public void testCreateFromCompleteZooKeeperTlsProperties() throws IOException, QuorumPeerConfig.ConfigException {
- final NiFiProperties completeProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- }});
-
- assertNotNull(ZooKeeperStateServer.create(completeProps));
- }
-
- // This test shows that the client can specify a secure port and that port is used:
- @Test
- public void testCreateWithSpecifiedSecureClientPort() throws IOException, QuorumPeerConfig.ConfigException {
- final NiFiProperties secureProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- }});
-
- final ZooKeeperStateServer server = ZooKeeperStateServer.create(secureProps);
- assertNotNull(server);
-
- final QuorumPeerConfig config = server.getQuorumPeerConfig();
- assertEquals(secureZooKeeperProps.getProperty("secureClientPort"), String.valueOf(config.getSecureClientPortAddress().getPort()));
- }
-
- // This shows that a secure NiFi with an insecure ZooKeeper will not have an insecure client address or port:
- @Test
- public void testCreateRemovesInsecureClientPort() {
- assertNotNull(insecureZooKeeperProps.getProperty("clientPort"));
- assertNotEquals("", insecureZooKeeperProps.getProperty("clientPort"));
- assertNull(quorumPeerConfig.getClientPortAddress());
- }
-
- // This test shows that a connection class is set when none is specified (QuorumPeerConfig::parseProperties sets the System property):
- @Test
- public void testCreateWithUnspecifiedConnectionClass() {
- assertEquals(org.apache.zookeeper.server.NettyServerCnxnFactory.class.getName(), System.getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY));
- }
-
- // This test shows that a specified connection class is honored (QuorumPeerConfig::parseProperties sets the System property):
- @Test
- public void testCreateWithSpecifiedConnectionClass() throws IOException, QuorumPeerConfig.ConfigException {
- final NiFiProperties secureProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- }});
-
- assertNotNull(ZooKeeperStateServer.create(secureProps));
- assertEquals(ZOOKEEPER_CNXN_FACTORY, System.getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY));
- }
-
- @AfterEach
- public void tearDown() throws Exception {
- Collections.unmodifiableSet(System.getProperties().stringPropertyNames()).stream()
- .filter(name -> name.startsWith("zookeeper."))
- .forEach(System::clearProperty);
-
- if(server != null) {
- File stateDir = server.getQuorumPeerConfig().getDataDir().getParentFile();
- deleteRecursively(stateDir);
- server.shutdown();
- server = null;
- }
- }
-
- private void deleteRecursively(final File file) {
- final File[] children = file.listFiles();
- if (children != null) {
- for (final File child : children) {
- deleteRecursively(child);
- }
- }
-
- file.delete();
- }
-
- // Connect to a secure ZooKeeperStateServer
- @Test
- public void testSecureClientQuorumConnectString() throws Exception {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort", "0"));
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, QUORUM_CONNECT_STRING);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getSecureClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getSecureZooKeeperClient(serverPort);
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- // Connect to an insecure ZooKeeperStateServer with an insecure client (ensure insecure setup still works)
- @Test
- public void testInsecureZooKeeperWithInsecureClient() throws Exception {
- final String connect = "localhost:" + 2381;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(INSECURE_NIFI_PROPS);
- put(NiFiProperties.STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES, INSECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getInsecureZooKeeperClient(INSECURE_NIFI_PROPS, connect);
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- // Fail to connect to a secure ZooKeeperStateServer with insecure client configuration
- @Test
- public void testSecureZooKeeperStateServerWithInsecureClient() throws Exception {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort", "0"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getSecureClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getInsecureZooKeeperClient(INSECURE_NIFI_PROPS, connect);
- client.start();
- final String testPath = "/test";
-
- // Expect this to fail with ConnectionLossException
- assertThrows(KeeperException.ConnectionLossException.class,
- () -> client.create().forPath(testPath, new byte[0]));
- }
-
- // Fail to connect to a secure ZooKeeperStateServer with missing client configuration
- @Test
- public void testSecureZooKeeperStateServerWithMissingClientConfiguration() throws Exception {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort", "0"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getSecureClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getInsecureZooKeeperClient(INSECURE_NIFI_PROPS, connect);
- client.start();
- final String testPath = "/test";
-
- // Expect this to fail with ConnectionLossException
- assertThrows(KeeperException.ConnectionLossException.class,
- () -> client.create().forPath(testPath, new byte[0]));
- }
-
- // Connect to a secure ZooKeeperStateServer
- @Test
- public void testSecureClientConnection() throws Exception {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort", "0"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getSecureClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getSecureZooKeeperClient(serverPort);
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- // Connect to an insecure ZooKeeperStateServer
- @Test
- public void testClientSecureFalseClientPortNoTls() throws Exception {
- final int actualPort = Integer.parseInt(insecureZooKeeperProps.getProperty("clientPort", "3000"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(INSECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getInsecureZooKeeperClient(INSECURE_NIFI_PROPS, connect);
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- @Test
- public void testClientSecureFalseWithClientSecurePortAndNoTls() {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(INSECURE_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- QuorumPeerConfig.ConfigException configException =
- assertThrows(QuorumPeerConfig.ConfigException.class, () -> ZooKeeperStateServer.create(validZkClientProps));
-
- assertEquals("ZooKeeper properties file src/test/resources/TestZooKeeperStateServerConfigurations/secure.zookeeper.properties was " +
- "configured to be secure but there was no valid TLS config present in nifi.properties or nifi.zookeeper.client.secure was set to false. Check the administration guide",
- configException.getMessage());
- }
-
- @Test
- public void testClientSecureTrueWithInsecureZooKeeperAndTlsSet() {
- final int actualPort = Integer.parseInt(insecureZooKeeperProps.getProperty("clientPort"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true");
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- QuorumPeerConfig.ConfigException configException =
- assertThrows(QuorumPeerConfig.ConfigException.class, () -> ZooKeeperStateServer.create(validZkClientProps));
- assertEquals("NiFi was configured to be secure but secureClientPort could not be retrieved from zookeeper.properties file",
- configException.getMessage());
- }
-
- @Test
- public void testClientSecureTrueWithNoTls() {
- final int actualPort = Integer.parseInt(insecureZooKeeperProps.getProperty("clientPort"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(INSECURE_NIFI_PROPS);
- put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true");
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- QuorumPeerConfig.ConfigException configException =
- assertThrows(QuorumPeerConfig.ConfigException.class, () -> ZooKeeperStateServer.create(validZkClientProps));
- assertEquals(NiFiProperties.ZOOKEEPER_CLIENT_SECURE + " is true but no TLS configuration is present in nifi.properties",
- configException.getMessage());
- }
-
- @Test
- public void testZooKeeperMissingPortSettings() {
- final String connect = "localhost:" + 2181;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "true");
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, PARTIAL_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- QuorumPeerConfig.ConfigException configException =
- assertThrows(QuorumPeerConfig.ConfigException.class, () -> ZooKeeperStateServer.create(validZkClientProps));
- assertEquals("No clientAddress or secureClientAddress is set in zookeeper.properties",
- configException.getMessage());
- }
-
- @Test
- public void testClientSecureFalseAndOnlyZooKeeperClientPortSetWithTlsProperties() throws Exception {
- final String connect = "localhost:" + 2181;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_NIFI_PROPS);
- put(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "false");
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, INSECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- }
-
- // Connect to a secure ZooKeeperStateServer with ZooKeeper.
- @Test
- public void testSecureClientConnectionWithZooKeeperSecurityProperties() throws Exception {
- final int actualPort = Integer.parseInt(secureZooKeeperProps.getProperty("secureClientPort", "0"));
- final String connect = "localhost:" + actualPort;
- final NiFiProperties validZkClientProps = NiFiProperties.createBasicNiFiProperties(null, new HashMap<String, String>() {{
- putAll(SECURE_ZOOKEEPER_NIFI_PROPS);
- put(ZOOKEEPER_PROPERTIES_FILE_KEY, SECURE_ZOOKEEPER_PROPS);
- put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connect);
- }});
-
- server = ZooKeeperStateServer.create(validZkClientProps);
- assertNotNull(server);
- final int serverPort = server.getQuorumPeerConfig().getSecureClientPortAddress().getPort();
- assertEquals(actualPort, serverPort);
- server.start();
-
- // Set up a ZK client
- CuratorFramework client = getSecureZooKeeperClient(serverPort);
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- private static String getPath(String path) {
- return new File("src/test/resources/TestZooKeeperStateServerConfigurations/" + path).getPath();
- }
-
- private CuratorFramework getSecureZooKeeperClient(final int port) {
- // TODO: port being set needs to be based on port set in nifi.properties, should create client in the same
- clientProperties = createSecureClientProperties(
- port,
- Paths.get(tlsConfiguration.getKeystorePath()),
- tlsConfiguration.getKeystoreType().getType(),
- tlsConfiguration.getKeystorePassword(),
- Paths.get(tlsConfiguration.getTruststorePath()),
- tlsConfiguration.getTruststoreType().getType(),
- tlsConfiguration.getTruststorePassword()
- );
-
- final ZooKeeperClientConfig zkClientConfig =
- ZooKeeperClientConfig.createConfig(clientProperties);
-
- return getCuratorFramework(zkClientConfig, new SecureClientZooKeeperFactory(zkClientConfig));
- }
-
- private CuratorFramework getInsecureZooKeeperClient(final Map<String, String> nifiClientProps, final String connectString) {
- // set up zkClientConfig
- Map<String, String> supplementedProps = nifiClientProps;
- supplementedProps.put(NiFiProperties.ZOOKEEPER_CONNECT_STRING, connectString);
- NiFiProperties insecureClientProperties = NiFiProperties.createBasicNiFiProperties(null, supplementedProps);
- final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(insecureClientProperties);
-
- ZookeeperFactory factory = null;
- try {
- factory = new DefaultZookeeperFactory();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return getCuratorFramework(zkClientConfig, factory);
- }
-
- private CuratorFramework getCuratorFramework(ZooKeeperClientConfig zkClientConfig, ZookeeperFactory factory) {
- final CuratorFrameworkFactory.Builder clientBuilder = CuratorFrameworkFactory.builder()
- .connectString(zkClientConfig.getConnectString())
- .sessionTimeoutMs(zkClientConfig.getSessionTimeoutMillis())
- .connectionTimeoutMs(zkClientConfig.getConnectionTimeoutMillis())
- .retryPolicy(new RetryOneTime(200))
- .defaultData(new byte[0])
- .zookeeperFactory(factory);
-
- return clientBuilder.build();
- }
-
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java
deleted file mode 100644
index f08461b603..0000000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.leader.election;
-
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.CuratorFrameworkFactory;
-import org.apache.curator.retry.RetryOneTime;
-import org.apache.curator.test.InstanceSpec;
-import org.apache.nifi.controller.cluster.SecureClientZooKeeperFactory;
-import org.apache.nifi.controller.cluster.ZooKeeperClientConfig;
-import org.apache.nifi.security.util.CertificateUtils;
-import org.apache.nifi.util.NiFiProperties;
-import org.apache.zookeeper.common.ClientX509Util;
-import org.apache.zookeeper.data.Stat;
-import org.apache.zookeeper.server.ServerCnxnFactory;
-import org.apache.zookeeper.server.ZooKeeperServer;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-public class ITSecureClientZooKeeperFactory {
-
- private static final String NETTY_SERVER_CNXN_FACTORY =
- "org.apache.zookeeper.server.NettyServerCnxnFactory";
-
- private static final String KEYSTORE_TYPE = "PKCS12";
- private static final String KEYSTORE_TYPE_EXT = ".p12";
- private static final String TEST_PASSWORD = "testpass";
-
- private static ZooKeeperServer zkServer;
- private static ServerCnxnFactory serverConnectionFactory;
- private static NiFiProperties clientProperties;
- private static Path tempDir;
- private static Path dataDir;
- private static Path serverKeyStore;
- private static Path serverTrustStore;
- private static Path clientKeyStore;
- private static Path clientTrustStore;
- private static int clientPort;
-
- @BeforeAll
- public static void setup() throws IOException, GeneralSecurityException, InterruptedException {
- tempDir = Paths.get("target/TestSecureClientZooKeeperFactory");
- dataDir = tempDir.resolve("state");
- serverKeyStore = tempDir.resolve("server.keystore" + KEYSTORE_TYPE_EXT);
- serverTrustStore = tempDir.resolve("server.truststore" + KEYSTORE_TYPE_EXT);
- clientKeyStore = tempDir.resolve("client.keystore" + KEYSTORE_TYPE_EXT);
- clientTrustStore = tempDir.resolve("client.truststore" + KEYSTORE_TYPE_EXT);
- clientPort = InstanceSpec.getRandomPort();
-
- Files.createDirectory(tempDir);
-
- final X509Certificate clientCert = createKeyStore("client", TEST_PASSWORD, clientKeyStore, KEYSTORE_TYPE);
- final X509Certificate serverCert = createKeyStore("zookeeper", TEST_PASSWORD, serverKeyStore, KEYSTORE_TYPE);
-
- createTrustStore(serverCert, "zookeeper", TEST_PASSWORD, clientTrustStore, KEYSTORE_TYPE);
- createTrustStore(clientCert, "client", TEST_PASSWORD, serverTrustStore, KEYSTORE_TYPE);
-
- clientProperties = createSecureClientProperties(
- clientPort,
- clientKeyStore,
- KEYSTORE_TYPE,
- TEST_PASSWORD,
- clientTrustStore,
- KEYSTORE_TYPE,
- TEST_PASSWORD
- );
-
- serverConnectionFactory = createAndStartServer(
- dataDir,
- tempDir,
- clientPort,
- serverKeyStore,
- TEST_PASSWORD,
- serverTrustStore,
- TEST_PASSWORD
- );
-
- zkServer = serverConnectionFactory.getZooKeeperServer();
- }
-
- @AfterAll
- public static void cleanup() {
- if (serverConnectionFactory != null) {
- try {
- serverConnectionFactory.shutdown();
- } catch (final Exception ignore) {}
-
- try {
- zkServer.shutdown();
- } catch (final Exception ignore) {}
- }
-
- if (tempDir != null) {
- final List<Path> files = Arrays.asList(
- dataDir.resolve("version-2/snapshot.0"),
- dataDir.resolve("version-2/log.1"),
- dataDir.resolve("version-2"),
- dataDir.resolve("myid"),
- serverKeyStore,
- serverTrustStore,
- clientKeyStore,
- clientTrustStore,
- dataDir,
- tempDir
- );
-
- files.forEach(p -> {
- try {
- if (p != null) Files.deleteIfExists(p);
- } catch (final IOException ioe) {}
- });
- }
- }
-
- @Test
- public void testValidCnxnSocketName() {
- assertEquals("org.apache.zookeeper.ClientCnxnSocketNetty", SecureClientZooKeeperFactory.NETTY_CLIENT_CNXN_SOCKET);
- }
-
- @Test
- @Timeout(30)
- public void testServerCreatePath() throws Exception {
- final ZooKeeperClientConfig zkClientConfig =
- ZooKeeperClientConfig.createConfig(clientProperties);
-
- final CuratorFrameworkFactory.Builder clientBuilder = CuratorFrameworkFactory.builder()
- .connectString(zkClientConfig.getConnectString())
- .sessionTimeoutMs(zkClientConfig.getSessionTimeoutMillis())
- .connectionTimeoutMs(zkClientConfig.getConnectionTimeoutMillis())
- .retryPolicy(new RetryOneTime(1000))
- .defaultData(new byte[0])
- .zookeeperFactory(new SecureClientZooKeeperFactory(zkClientConfig));
-
- final CuratorFramework client = clientBuilder.build();
-
- client.start();
- final String testPath = "/test";
- final String createResult = client.create().forPath(testPath, new byte[0]);
- final Stat checkExistsResult = client.checkExists().forPath(testPath);
-
- assertEquals(createResult, testPath);
- assertNotNull(checkExistsResult);
- }
-
- public static ServerCnxnFactory createAndStartServer(final Path dataDir,
- final Path tempDir, final int clientPort, final Path keyStore,
- final String keyStorePassword, final Path trustStore,
- final String trustStorePassword) throws IOException, InterruptedException {
-
- final ClientX509Util x509Util = new ClientX509Util();
- System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, NETTY_SERVER_CNXN_FACTORY);
- System.setProperty(x509Util.getSslAuthProviderProperty(), "x509");
- System.setProperty(x509Util.getSslKeystoreLocationProperty(), keyStore.toString());
- System.setProperty(x509Util.getSslKeystorePasswdProperty(), keyStorePassword);
- System.setProperty(x509Util.getSslTruststoreLocationProperty(), trustStore.toString());
- System.setProperty(x509Util.getSslTruststorePasswdProperty(), trustStorePassword);
- System.setProperty("zookeeper.authProvider.x509", "org.apache.zookeeper.server.auth.X509AuthenticationProvider");
-
- ZooKeeperServer zkServer = new ZooKeeperServer(dataDir.toFile(), dataDir.toFile(), 2000);
- ServerCnxnFactory secureConnectionFactory = ServerCnxnFactory.createFactory(clientPort, -1);
- secureConnectionFactory.configure(new InetSocketAddress(clientPort), -1, -1, true);
- secureConnectionFactory.startup(zkServer);
-
- return secureConnectionFactory;
- }
-
- public static NiFiProperties createSecureClientProperties(final int clientPort,
- final Path keyStore, final String keyStoreType, final String keyStorePassword,
- final Path trustStore, final String trustStoreType, final String trustStorePassword) {
-
- final Properties properties = new Properties();
- properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, String.format("localhost:%d", clientPort));
- properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, Boolean.TRUE.toString());
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE, keyStore.toString());
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, keyStoreType);
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_PASSWD, keyStorePassword);
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE, trustStore.toString());
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, trustStoreType);
- properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD, trustStorePassword);
-
- return new NiFiProperties(properties);
- }
-
- public static X509Certificate createKeyStore(final String alias,
- final String password, final Path path, final String keyStoreType)
- throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
-
- try (final FileOutputStream outputStream = new FileOutputStream(path.toFile())) {
- final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
-
- final X509Certificate selfSignedCert = CertificateUtils.generateSelfSignedX509Certificate(
- keyPair, "CN=localhost", "SHA256withRSA", 365
- );
-
- final char[] passwordChars = password.toCharArray();
- final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(null, null);
- keyStore.setKeyEntry(alias, keyPair.getPrivate(), passwordChars,
- new Certificate[]{selfSignedCert});
- keyStore.store(outputStream, passwordChars);
-
- return selfSignedCert;
- }
- }
-
- public static void createTrustStore(final X509Certificate cert,
- final String alias, final String password, final Path path, final String keyStoreType)
- throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
-
- try (final FileOutputStream outputStream = new FileOutputStream(path.toFile())) {
- final KeyStore trustStore = KeyStore.getInstance(keyStoreType);
- trustStore.load(null, null);
- trustStore.setCertificateEntry(alias, cert);
- trustStore.store(outputStream, password.toCharArray());
- }
- }
-
-}
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
index 33f4c3bd6d..ad345c8404 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
@@ -16,7 +16,6 @@
*/
package org.apache.nifi.web.security.token;
-import org.apache.nifi.security.util.CertificateUtils;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@@ -69,7 +68,7 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken {
super(authorities);
setAuthenticated(true);
this.identity = identity;
- this.username = username;
+ this.username = username == null ? identity : username;
this.issuer = issuer;
this.expiration = Instant.now().plusMillis(expiration).toEpochMilli();
}
@@ -101,12 +100,7 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken {
@Override
public String getName() {
- if (username == null) {
- // if the username is a DN this will extract the username or CN... if not will return what was passed
- return CertificateUtils.extractUsername(identity);
- } else {
- return username;
- }
+ return username;
}
@Override
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
index 85a1a8f6fd..14bb834914 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
@@ -171,6 +171,11 @@
<artifactId>nifi-security-utils</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-cert</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-xml-processing</artifactId>
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java
index d172033d7b..6258e370b9 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PostHTTP.java
@@ -29,7 +29,6 @@ import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.security.Principal;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
@@ -111,7 +110,6 @@ import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.InputStreamCallback;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.processors.standard.util.HTTPUtils;
-import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.ssl.SSLContextService;
import org.apache.nifi.stream.io.GZIPOutputStream;
import org.apache.nifi.stream.io.LeakyBucketStreamThrottler;
@@ -428,14 +426,8 @@ public class PostHTTP extends AbstractProcessor {
throw new SSLPeerUnverifiedException("No certificates found");
}
- try {
- final X509Certificate cert = CertificateUtils.convertAbstractX509Certificate(certChain[0]);
- httpContext.setAttribute(REMOTE_DN, cert.getSubjectDN().getName().trim());
- } catch (CertificateException e) {
- final String msg = "Could not extract subject DN from SSL session peer certificate";
- getLogger().warn(msg);
- throw new SSLPeerUnverifiedException(msg);
- }
+ final X509Certificate cert = (X509Certificate) certChain[0];
+ httpContext.setAttribute(REMOTE_DN, cert.getSubjectDN().getName().trim());
}
}
});
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProvider.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProvider.java
index a3b75d7b96..814fea3f81 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProvider.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProvider.java
@@ -16,11 +16,12 @@
*/
package org.apache.nifi.processors.standard.http;
+import org.apache.nifi.security.cert.CertificateAttributeReader;
+import org.apache.nifi.security.cert.StandardCertificateAttributeReader;
+import org.apache.nifi.security.cert.SubjectAlternativeName;
+
import javax.servlet.http.HttpServletRequest;
-import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
-import java.util.Base64;
-import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -37,20 +38,7 @@ public class HandleHttpRequestCertificateAttributesProvider implements Certifica
private static final String SAN_NAME_FORMAT = "%s.%d.name";
- private static final Map<String, String> GENERAL_NAME_TYPES = new LinkedHashMap<>();
-
- static {
- // General Name types defined in RFC 3280 Section 4.2.1.7 */
- GENERAL_NAME_TYPES.put("0", "otherName");
- GENERAL_NAME_TYPES.put("1", "rfc822Name");
- GENERAL_NAME_TYPES.put("2", "dNSName");
- GENERAL_NAME_TYPES.put("3", "x400Address");
- GENERAL_NAME_TYPES.put("4", "directoryName");
- GENERAL_NAME_TYPES.put("5", "ediPartyName");
- GENERAL_NAME_TYPES.put("6", "uniformResourceIdentifier");
- GENERAL_NAME_TYPES.put("7", "iPAddress");
- GENERAL_NAME_TYPES.put("8", "registeredID");
- }
+ private final CertificateAttributeReader certificateAttributeReader = new StandardCertificateAttributeReader();
@Override
public Map<String, String> getCertificateAttributes(final HttpServletRequest request) {
@@ -83,33 +71,25 @@ public class HandleHttpRequestCertificateAttributesProvider implements Certifica
attributes.put(CertificateAttribute.HTTP_SUBJECT_DN.getName(), subjectPrincipal);
attributes.put(CertificateAttribute.HTTP_ISSUER_DN.getName(), issuerPrincipal);
- try {
- final Collection<List<?>> subjectAlternativeNames = certificate.getSubjectAlternativeNames();
-
- if (subjectAlternativeNames != null) {
- final Map<String, String> subjectAlternativeNameAttributes = getSubjectAlternativeNameAttributes(subjectAlternativeNames);
- attributes.putAll(subjectAlternativeNameAttributes);
- }
- } catch (final CertificateParsingException e) {
- attributes.put(CertificateAttribute.HTTP_CERTIFICATE_PARSING_EXCEPTION.getName(), e.getMessage());
- }
+ final Map<String, String> subjectAlternativeNameAttributes = getSubjectAlternativeNameAttributes(certificate);
+ attributes.putAll(subjectAlternativeNameAttributes);
return attributes;
}
- private Map<String, String> getSubjectAlternativeNameAttributes(final Collection<List<?>> subjectAlternativeNames) {
+ private Map<String, String> getSubjectAlternativeNameAttributes(final X509Certificate certificate) {
final Map<String, String> attributes = new LinkedHashMap<>();
int subjectAlternativeNameIndex = 0;
- for (final List<?> subjectAlternativeTypeName : subjectAlternativeNames) {
+
+ final List<SubjectAlternativeName> subjectAlternativeNames = certificateAttributeReader.getSubjectAlternativeNames(certificate);
+ for (final SubjectAlternativeName subjectAlternativeName : subjectAlternativeNames) {
final String nameTypeAttributeKey = String.format(SAN_NAME_TYPE_FORMAT, CertificateAttribute.HTTP_CERTIFICATE_SANS.getName(), subjectAlternativeNameIndex);
- final String nameType = subjectAlternativeTypeName.get(0).toString();
- final String generalNameType = GENERAL_NAME_TYPES.getOrDefault(nameType, nameType);
+ final String generalNameType = subjectAlternativeName.getGeneralNameType().getGeneralName();
attributes.put(nameTypeAttributeKey, generalNameType);
final String nameAttributeKey = String.format(SAN_NAME_FORMAT, CertificateAttribute.HTTP_CERTIFICATE_SANS.getName(), subjectAlternativeNameIndex);
- final Object name = subjectAlternativeTypeName.get(1);
- final String serializedName = getSerializedName(name);
+ final String serializedName = subjectAlternativeName.getName();
attributes.put(nameAttributeKey, serializedName);
subjectAlternativeNameIndex++;
@@ -117,15 +97,4 @@ public class HandleHttpRequestCertificateAttributesProvider implements Certifica
return attributes;
}
-
- private String getSerializedName(final Object name) {
- final String serializedName;
- if (name instanceof byte[]) {
- final byte[] encodedName = (byte[]) name;
- serializedName = Base64.getEncoder().encodeToString(encodedName);
- } else {
- serializedName = name.toString();
- }
- return serializedName;
- }
}
diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProviderTest.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProviderTest.java
index 3e4df7c283..92242edc40 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProviderTest.java
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/http/HandleHttpRequestCertificateAttributesProviderTest.java
@@ -111,8 +111,6 @@ class HandleHttpRequestCertificateAttributesProviderTest {
final Map<String, String> attributes = provider.getCertificateAttributes(request);
assertSubjectIssuerFound(attributes);
-
- assertEquals(attributes.get(CertificateAttribute.HTTP_CERTIFICATE_PARSING_EXCEPTION.getName()), PARSING_EXCEPTION_MESSAGE);
}
@Test
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemKeyStoreProvider.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemKeyStoreProvider.java
index 2a80103c80..55c9a5d4c8 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemKeyStoreProvider.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemKeyStoreProvider.java
@@ -16,8 +16,9 @@
*/
package org.apache.nifi.tests.system;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
+import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,6 +33,9 @@ import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
/**
* System Key Store Provider generates a Key Pair and Certificate for KeyStore and TrustStore files
@@ -39,9 +43,9 @@ import java.security.cert.X509Certificate;
public class NiFiSystemKeyStoreProvider {
private static final String HOSTNAME = "localhost";
- private static final String[] DNS_NAMES = new String[]{HOSTNAME};
+ private static final List<String> DNS_NAMES = Collections.singletonList(HOSTNAME);
- private static final String DISTINGUISHED_NAME = String.format("CN=%s", HOSTNAME);
+ private static final X500Principal DISTINGUISHED_NAME = new X500Principal(String.format("CN=%s", HOSTNAME));
private static final String PASSWORD = NiFiSystemKeyStoreProvider.class.getSimpleName();
@@ -51,8 +55,6 @@ public class NiFiSystemKeyStoreProvider {
private static final int KEY_SIZE = 4096;
- private static final String SIGNING_ALGORITHM = "SHA256withRSA";
-
private static final String KEYSTORE_FILE = "keystore.p12";
private static final String TRUSTSTORE_FILE = "truststore.p12";
@@ -91,13 +93,9 @@ public class NiFiSystemKeyStoreProvider {
private static void createKeyStores() {
try {
final KeyPair keyPair = getKeyPair();
- final X509Certificate certificate = CertificateUtils.generateSelfSignedX509Certificate(
- keyPair,
- DISTINGUISHED_NAME,
- SIGNING_ALGORITHM,
- VALID_DURATION_DAYS,
- DNS_NAMES
- );
+ final X509Certificate certificate = new StandardCertificateBuilder(keyPair, DISTINGUISHED_NAME, Duration.ofDays(VALID_DURATION_DAYS))
+ .setDnsSubjectAlternativeNames(DNS_NAMES)
+ .build();
persistentTrustStorePath = writeTrustStore(certificate);
persistentTrustStorePath.toFile().deleteOnExit();
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy b/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
deleted file mode 100644
index e469980cf5..0000000000
--- a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nifi.toolkit.admin.client
-
-import org.apache.commons.lang3.SystemUtils
-import org.apache.http.conn.ssl.DefaultHostnameVerifier
-import org.apache.nifi.properties.NiFiPropertiesLoader
-import org.apache.nifi.security.util.CertificateUtils
-import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone
-import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine
-import org.apache.nifi.util.NiFiBootstrapUtils
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x500.X500NameBuilder
-import org.bouncycastle.asn1.x500.style.BCStyle
-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.asn1.x509.SubjectPublicKeyInfo
-import org.bouncycastle.cert.X509CertificateHolder
-import org.bouncycastle.cert.X509v3CertificateBuilder
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.operator.ContentSigner
-import org.bouncycastle.operator.OperatorCreationException
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
-import spock.lang.Specification
-
-import javax.net.ssl.SSLSession
-import java.nio.file.Files
-import java.nio.file.attribute.PosixFilePermission
-import java.security.InvalidKeyException
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-import java.security.NoSuchAlgorithmException
-import java.security.NoSuchProviderException
-import java.security.SignatureException
-import java.security.cert.Certificate
-import java.security.cert.CertificateException
-import java.security.cert.X509Certificate
-import java.util.concurrent.TimeUnit
-
-class NiFiClientFactorySpec extends Specification {
-
- private static final int KEY_SIZE = 2048
- private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"
- private static final int DAYS_IN_YEAR = 365
- private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US"
-
- def "get client for unsecure nifi"(){
-
- given:
- def NiFiProperties niFiProperties = Mock NiFiProperties
- def clientFactory = new NiFiClientFactory()
-
- when:
- def client = clientFactory.getClient(niFiProperties,"src/test/resources/notify")
-
- then:
- client
-
- }
-
- def "get client for secured nifi"(){
-
- given:
- def File tmpDir = setupTmpDir()
- def File testDir = new File("target/tmp/keys")
- def toolkitCommandLine = ["-O", "-o",testDir.absolutePath,"-n","localhost","-C", "CN=user1","-S", "badKeyPass", "-K", "badKeyPass", "-P", "badTrustPass"]
-
- TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine()
- tlsToolkitStandaloneCommandLine.parse(toolkitCommandLine as String[])
- new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig())
-
- def bootstrapConfFile = "src/test/resources/notify/conf/bootstrap.conf"
- def nifiPropertiesFile = "src/test/resources/notify/conf/nifi-secured.properties"
- def key = NiFiBootstrapUtils.extractKeyFromBootstrapFile(bootstrapConfFile)
- def NiFiProperties niFiProperties = NiFiPropertiesLoader.withKey(key).load(nifiPropertiesFile)
- def clientFactory = new NiFiClientFactory()
-
- when:
- def client = clientFactory.getClient(niFiProperties,"src/test/resources/notify")
-
- then:
- client
-
- cleanup:
- tmpDir.deleteDir()
-
- }
-
- def "should verify CN in certificate based on subjectDN"(){
-
- given:
- final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
- Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN,ISSUER_DN)
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def verified = verifier.verify("client.nifi.apache.org",mockSession)
-
- then:
- verified
-
- }
-
- def "should verify wildcard in CN in certificate based on subjectDN"(){
-
- given:
- final String EXPECTED_DN = "CN=*.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
- Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN,ISSUER_DN)
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def validSubdomainIsVerified = verifier.verify("client.nifi.apache.org",mockSession)
- def validSubdomainIsVerified2 = verifier.verify("server.nifi.apache.org",mockSession)
- def invalidSubdomainIsNotVerified = !verifier.verify("client.hive.apache.org",mockSession)
-
- then:
- validSubdomainIsVerified
- validSubdomainIsVerified2
- invalidSubdomainIsNotVerified
- }
-
- def "should verify appropriately CN in certificate based on TLD wildcard in SAN"(){
-
- given:
- final String EXPECTED_DN = "CN=client.nifi.apache.*,OU=Security,O=Apache,ST=CA,C=US"
- final String wildcardHostname = "client.nifi.apache.com"
- byte[] subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, wildcardHostname)).getEncoded()
- Extensions extensions = new Extensions(new Extension(Extension.subjectAlternativeName, false, subjectAltName))
- Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN, ISSUER_DN, extensions)
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def validTLDIsVerified = verifier.verify("client.nifi.apache.org",mockSession)
- def validTLDIsVerified2 = verifier.verify("client.nifi.apache.com",mockSession)
- def validTLDIsNotVerified = !verifier.verify("client.hive.apache.org",mockSession)
-
- then:
- //validTLDIsVerified
- validTLDIsVerified2
- validTLDIsNotVerified
- }
-
- def "should verify appropriately CN in certificate based on subdomain wildcard in SAN"(){
-
- given:
- final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
- final String wildcardHostname = "*.nifi.apache.org"
- byte[] subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, wildcardHostname)).getEncoded()
- Extensions extensions = new Extensions(new Extension(Extension.subjectAlternativeName, false, subjectAltName))
- Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN, ISSUER_DN, extensions)
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def validSubdomainIsVerified = verifier.verify("client.nifi.apache.org", mockSession)
- def validSubdomainIsVerified1 = verifier.verify("egg.nifi.apache.org", mockSession)
- def invalidSubdomainIsNotVerified = !verifier.verify("client.hive.apache.org", mockSession)
- def invalidDomainIsNotVerified = !verifier.verify("egg.com", mockSession)
-
- then:
- validSubdomainIsVerified
- validSubdomainIsVerified1
- invalidSubdomainIsNotVerified
- invalidDomainIsNotVerified
- }
-
- def "should not verify based on no certificate chain"(){
-
- given:
- final String EXPECTED_DN = "CN=client.nifi.apache.org, OU=Security, O=Apache, ST=CA, C=US"
- Certificate[] certificateChain = [] as Certificate[]
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def notVerified = !verifier.verify("client.nifi.apache.org",mockSession)
-
- then:
- final ArrayIndexOutOfBoundsException exception = thrown()
- }
-
- def "should not verify based on multiple CN values"(){
-
- given:
- final KeyPair issuerKeyPair = generateKeyPair()
- KeyPair keyPair = generateKeyPair()
- final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair,ISSUER_DN, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
-
- ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(issuerKeyPair.getPrivate());
- SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.public.getEncoded());
- Date startDate = new Date();
- Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(DAYS_IN_YEAR));
-
- def X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE)
- nameBuilder.addRDN(BCStyle.CN,"client.nifi.apache.org,nifi.apache.org")
- def name = nameBuilder.build()
-
- X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(new X500Name(issuerCertificate.getSubjectX500Principal().getName()),
- BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, name,
- subPubKeyInfo);
-
- X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
- Certificate certificate = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
-
- Certificate[] certificateChain = [certificate,issuerCertificate] as Certificate[]
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
-
- when:
- def notVerified = !verifier.verify("client.nifi.apache.org",mockSession)
-
- then:
- notVerified
-
- }
-
- def "should verify appropriately CN in certificate based on SAN"() {
-
- given:
-
- final List<String> SANS = ["127.0.0.1", "nifi.apache.org"]
- def gns = SANS.collect { String san ->
- new GeneralName(GeneralName.dNSName, san)
- }
- def generalNames = new GeneralNames(gns as GeneralName[])
- ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator()
- extensionsGenerator.addExtension(Extension.subjectAlternativeName, false, generalNames)
- Extensions extensions = extensionsGenerator.generate()
-
- final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
- final KeyPair issuerKeyPair = generateKeyPair()
- final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair,ISSUER_DN, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
- final X509Certificate certificate = generateIssuedCertificate(EXPECTED_DN, issuerCertificate,extensions, issuerKeyPair)
- Certificate[] certificateChain = [certificate, issuerCertificate] as X509Certificate[]
- def mockSession = Mock(SSLSession)
- DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
- mockSession.getPeerCertificates() >> certificateChain
-
- when:
- def verified = verifier.verify("nifi.apache.org",mockSession)
- def notVerified = !verifier.verify("fake.apache.org",mockSession)
-
-
- then:
- verified
- notVerified
-
- }
-
- def KeyPair generateKeyPair() throws NoSuchAlgorithmException {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
- keyPairGenerator.initialize(KEY_SIZE)
- return keyPairGenerator.generateKeyPair()
- }
-
- def X509Certificate generateIssuedCertificate(String dn, X509Certificate issuer,Extensions extensions, KeyPair issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
- KeyPair keyPair = generateKeyPair()
- return CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(),extensions, issuer, issuerKey, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
- }
-
- def X509Certificate[] generateCertificateChain(String dn,String issuerDn) {
- generateCertificateChain(dn, issuerDn, null)
- }
-
- def X509Certificate[] generateCertificateChain(String dn,String issuerDn, Extensions extensions) {
- final KeyPair issuerKeyPair = generateKeyPair()
- final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
- final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate, extensions, issuerKeyPair)
- [certificate, issuerCertificate] as X509Certificate[]
- }
-
- def setFilePermissions(File file, List<PosixFilePermission> permissions = []) {
- if (SystemUtils.IS_OS_WINDOWS) {
- file?.setReadable(permissions.contains(PosixFilePermission.OWNER_READ))
- file?.setWritable(permissions.contains(PosixFilePermission.OWNER_WRITE))
- file?.setExecutable(permissions.contains(PosixFilePermission.OWNER_EXECUTE))
- } else {
- Files.setPosixFilePermissions(file?.toPath(), permissions as Set)
- }
- }
- def setupTmpDir(String tmpDirPath = "target/tmp/") {
- File tmpDir = new File(tmpDirPath)
- tmpDir.mkdirs()
- setFilePermissions(tmpDir, [PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE,
- PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
- PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE])
- tmpDir
- }
-
-}
diff --git a/nifi-toolkit/nifi-toolkit-tls/pom.xml b/nifi-toolkit/nifi-toolkit-tls/pom.xml
index 7405122aca..640d2b067a 100644
--- a/nifi-toolkit/nifi-toolkit-tls/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-tls/pom.xml
@@ -33,6 +33,11 @@
<artifactId>nifi-security-utils</artifactId>
<version>1.24.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-cert-builder</artifactId>
+ <version>1.24.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-resources</artifactId>
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsClientConfig.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsClientConfig.java
index 6e030f6e9f..4ed3177b97 100644
--- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsClientConfig.java
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsClientConfig.java
@@ -43,7 +43,6 @@ public class TlsClientConfig extends TlsConfig {
setSigningAlgorithm(tlsConfig.getSigningAlgorithm());
setDnPrefix(tlsConfig.getDnPrefix());
setDnSuffix(tlsConfig.getDnSuffix());
- setReorderDn(tlsConfig.getReorderDn());
setDomainAlternativeNames(tlsConfig.getDomainAlternativeNames());
}
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 5cb744253c..c32029a7b2 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
@@ -18,7 +18,6 @@
package org.apache.nifi.toolkit.tls.configuration;
import java.util.List;
-import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.util.StringUtils;
/**
@@ -34,7 +33,6 @@ public class TlsConfig {
public static final String DEFAULT_SIGNING_ALGORITHM = "SHA256WITHRSA";
public static final String DEFAULT_DN_PREFIX = "CN=";
public static final String DEFAULT_DN_SUFFIX = ", OU=NIFI";
- public static final boolean DEFAULT_REORDER_DN = true;
private int days = DEFAULT_DAYS;
private int keySize = DEFAULT_KEY_SIZE;
@@ -51,16 +49,11 @@ public class TlsConfig {
private int port = DEFAULT_PORT;
private String dnPrefix = DEFAULT_DN_PREFIX;
private String dnSuffix = DEFAULT_DN_SUFFIX;
- private boolean reorderDn = DEFAULT_REORDER_DN;
private String additionalCACertificate = "";
private List<String> domainAlternativeNames;
public String calcDefaultDn(String hostname) {
- String dn = dnPrefix + hostname + dnSuffix;
- if (reorderDn) {
- return CertificateUtils.reorderDn(dn);
- }
- return dn;
+ return dnPrefix + hostname + dnSuffix;
}
public int getPort() {
@@ -175,14 +168,6 @@ public class TlsConfig {
this.dnSuffix = dnSuffix;
}
- public boolean getReorderDn() {
- return reorderDn;
- }
-
- public void setReorderDn(boolean reorderDn) {
- this.reorderDn = reorderDn;
- }
-
public void initDefaults() {
if (days == 0) {
days = DEFAULT_DAYS;
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java
index 8352213357..9d2f7e1f37 100644
--- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java
@@ -17,16 +17,18 @@
package org.apache.nifi.toolkit.tls.manager;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
+import java.time.Duration;
/**
* KeyStore manager capable of reading or instantiating a CA Certificate
@@ -49,7 +51,10 @@ public class TlsCertificateAuthorityManager extends BaseTlsManager {
if (entry == null) {
TlsConfig tlsConfig = getTlsConfig();
KeyPair keyPair = TlsHelper.generateKeyPair(tlsConfig.getKeyPairAlgorithm(), tlsConfig.getKeySize());
- X509Certificate caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, CertificateUtils.reorderDn(tlsConfig.getDn()), tlsConfig.getSigningAlgorithm(), tlsConfig.getDays());
+ final X500Principal issuer = new X500Principal(tlsConfig.getDn());
+ final Duration validityPeriod = Duration.ofDays(tlsConfig.getDays());
+ final StandardCertificateBuilder certificateBuilder = new StandardCertificateBuilder(keyPair, issuer, validityPeriod);
+ X509Certificate caCert = certificateBuilder.build();
entry = addPrivateKeyToKeyStore(keyPair, TlsToolkitStandalone.NIFI_KEY, caCert);
} else if (!KeyStore.PrivateKeyEntry.class.isInstance(entry)) {
throw new IOException("Expected " + TlsToolkitStandalone.NIFI_KEY + " alias to contain a private key entry");
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 e58ab2e9d3..9f1ddccee7 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
@@ -23,7 +23,6 @@ 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;
@@ -38,7 +37,6 @@ import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
-import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
@@ -65,12 +63,12 @@ public class TlsCertificateSigningRequestPerformer {
private final ObjectMapper objectMapper;
private final String signingAlgorithm;
- public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
+ public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) {
this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
}
- protected TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
+ protected TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) {
this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
}
@@ -79,7 +77,7 @@ public class TlsCertificateSigningRequestPerformer {
String dn, List<String> domainAlternativeNames, String token, int port, String signingAlgorithm) {
this.httpClientBuilderSupplier = httpClientBuilderSupplier;
this.caHostname = caHostname;
- this.dn = CertificateUtils.reorderDn(dn);
+ this.dn = dn;
this.domainAlternativeNames = domainAlternativeNames;
this.token = token;
this.port = port;
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 b0172ae7d2..9d54246949 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
@@ -19,10 +19,16 @@ package org.apache.nifi.toolkit.tls.service.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.input.BoundedReader;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
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.ASN1Encodable;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+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.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@@ -30,6 +36,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.security.auth.x500.X500Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -38,6 +45,9 @@ import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
/**
* Jetty service handler that validates the hmac of a CSR and issues a certificate if it checks out
@@ -64,7 +74,7 @@ public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException {
try {
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(new BoundedReader(request.getReader(), 1024 * 1024), TlsCertificateAuthorityRequest.class);
@@ -86,14 +96,18 @@ public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
if (logger.isInfoEnabled()) {
logger.info("Received CSR with DN " + dn);
}
- X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, jcaPKCS10CertificationRequest.getPublicKey(),
- CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationRequest), caCert, keyPair, signingAlgorithm, days);
+
+ final List<String> dnsNames = getDnsNames(jcaPKCS10CertificationRequest);
+ X509Certificate x509Certificate = new StandardCertificateBuilder(keyPair, caCert.getSubjectX500Principal(), Duration.ofDays(days))
+ .setSubject(new X500Principal(dn))
+ .setSubjectPublicKey(jcaPKCS10CertificationRequest.getPublicKey())
+ .setDnsSubjectAlternativeNames(dnsNames)
+ .build();
+
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()),
TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK);
- return;
} else {
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(FORBIDDEN), Response.SC_FORBIDDEN);
- return;
}
} catch (Exception e) {
throw new ServletException("Server error");
@@ -118,4 +132,18 @@ public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
}
}
+
+ private List<String> getDnsNames(final JcaPKCS10CertificationRequest request) {
+ final Extensions extensions = request.getRequestedExtensions();
+ final GeneralNames generalNames = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
+
+ final List<String> dnsNames = new ArrayList<>();
+ for (final GeneralName generalName : generalNames.getNames()) {
+ final ASN1Encodable nameEncoded = generalName.getName();
+ final String name = IETFUtils.valueToString(nameEncoded);
+ dnsNames.add(name);
+ }
+
+ return dnsNames;
+ }
}
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 664b00d206..799edf1f0c 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
@@ -17,7 +17,7 @@
package org.apache.nifi.toolkit.tls.standalone;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
@@ -30,12 +30,12 @@ import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
import org.apache.nifi.toolkit.tls.util.TlsHelper;
import org.apache.nifi.util.StringUtils;
-import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -55,6 +55,7 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -111,7 +112,6 @@ public class TlsToolkitStandalone {
throw new IOException("Expected directory to output to");
}
- String signingAlgorithm = standaloneConfig.getSigningAlgorithm();
int days = standaloneConfig.getDays();
String keyPairAlgorithm = standaloneConfig.getKeyPairAlgorithm();
int keySize = standaloneConfig.getKeySize();
@@ -256,9 +256,18 @@ public class TlsToolkitStandalone {
tlsClientConfig.setTrustStorePassword(instanceDefinition.getTrustStorePassword());
TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig);
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
- Extensions sanDnsExtensions = TlsHelper.createDomainAlternativeNamesExtensions(tlsClientConfig.getDomainAlternativeNames(), tlsClientConfig.calcDefaultDn(hostname));
- tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, CertificateUtils.generateIssuedCertificate(tlsClientConfig.calcDefaultDn(hostname),
- keyPair.getPublic(), sanDnsExtensions, certificate, caKeyPair, signingAlgorithm, days), certificate);
+
+ final List<String> dnsNames = new ArrayList<>(tlsClientConfig.getDomainAlternativeNames());
+ final String defaultDn = tlsClientConfig.calcDefaultDn(hostname);
+ dnsNames.add(hostname);
+
+ final X509Certificate clientCertificate = new StandardCertificateBuilder(caKeyPair, certificate.getSubjectX500Principal(), Duration.ofDays(days))
+ .setSubject(new X500Principal(defaultDn))
+ .setSubjectPublicKey(keyPair.getPublic())
+ .setDnsSubjectAlternativeNames(dnsNames)
+ .build();
+
+ tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, clientCertificate, certificate);
tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"),
hostname, instanceDefinition.getNumber()));
@@ -276,8 +285,8 @@ public class TlsToolkitStandalone {
List<String> clientPasswords = standaloneConfig.getClientPasswords();
for (int i = 0; i < clientDns.size(); i++) {
- String reorderedDn = CertificateUtils.reorderDn(clientDns.get(i));
- String clientDnFile = TlsHelper.escapeFilename(reorderedDn);
+ String clientDn = clientDns.get(i);
+ String clientDnFile = TlsHelper.escapeFilename(clientDn);
File clientCertFile = new File(baseDir, clientDnFile + ".p12");
if (clientCertFile.exists()) {
@@ -292,7 +301,10 @@ public class TlsToolkitStandalone {
logger.info("Generating new client certificate " + clientCertFile);
}
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
- X509Certificate clientCert = CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), null, certificate, caKeyPair, signingAlgorithm, days);
+ X509Certificate clientCert = new StandardCertificateBuilder(caKeyPair, certificate.getIssuerX500Principal(), Duration.ofDays(days))
+ .setSubject(new X500Principal(clientDn))
+ .setSubjectPublicKey(keyPair.getPublic())
+ .build();
final String keyStorePassword = clientPasswords.get(i);
final KeyStore keyStore = setClientKeyStore(keyStorePassword, keyPair.getPrivate(), clientCert, certificate);
String password = TlsHelper.writeKeyStore(keyStore, outputStreamFactory, clientCertFile, keyStorePassword, standaloneConfig.isClientPasswordsGenerated());
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 5fe7fb0ea4..35cdb1dbfe 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
@@ -36,7 +36,6 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMException;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
@@ -226,7 +225,7 @@ public class TlsHelper {
throw new GeneralSecurityException("Token does not meet minimum size of 16 bytes.");
}
SecretKeySpec keySpec = new SecretKeySpec(tokenBytes, "RAW");
- Mac mac = Mac.getInstance("Hmac-SHA256", BouncyCastleProvider.PROVIDER_NAME);
+ Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
return mac.doFinal(getKeyIdentifier(publicKey));
}
@@ -254,7 +253,7 @@ public class TlsHelper {
}
public static X509Certificate parseCertificate(Reader pemEncodedCertificate) throws IOException, CertificateException {
- return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(parsePem(X509CertificateHolder.class, pemEncodedCertificate));
+ return new JcaX509CertificateConverter().getCertificate(parsePem(X509CertificateHolder.class, pemEncodedCertificate));
}
/**
@@ -295,7 +294,7 @@ public class TlsHelper {
* @throws PEMException if there is an error converting the key pair
*/
private static KeyPair getKeyPair(PEMKeyPair keyPair) throws PEMException {
- return new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getKeyPair(keyPair);
+ return new JcaPEMKeyConverter().getKeyPair(keyPair);
}
/**
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy b/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy
index 7083b3e7c4..9ab3d463d6 100644
--- a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy
@@ -17,33 +17,26 @@
package org.apache.nifi.toolkit.tls.standalone
-import org.apache.nifi.security.util.CertificateUtils
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder
import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig
-import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator
import org.bouncycastle.util.io.pem.PemWriter
-import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
+import javax.security.auth.x500.X500Principal
import java.nio.file.Files
import java.security.KeyPair
import java.security.KeyPairGenerator
-import java.security.Security
import java.security.SignatureException
import java.security.cert.X509Certificate
+import java.time.Duration
import static org.junit.jupiter.api.Assertions.assertThrows
class TlsToolkitStandaloneGroovyTest {
private final String TEST_SRC_DIR = "src/test/resources/"
private final String DEFAULT_KEY_PAIR_ALGORITHM = "RSA"
- private final String DEFAULT_SIGNING_ALGORITHM = "SHA256WITHRSA"
-
- @BeforeAll
- static void setProvider() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
- }
@Test
void testShouldVerifyCertificateSignatureWhenSelfSigned(@TempDir File tempDir) {
@@ -109,7 +102,10 @@ class TlsToolkitStandaloneGroovyTest {
File rootCertFile = writeCertificateToPEMFile(rootCert, "${baseDir.path}/root.pem")
KeyPair intermediateKeyPair = generateKeyPair()
- X509Certificate intermediateCert = CertificateUtils.generateIssuedCertificate("CN=Intermediate CA", intermediateKeyPair.getPublic(), rootCert, rootKeyPair, DEFAULT_SIGNING_ALGORITHM, 1)
+ X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1))
+ .setSubject(new X500Principal("CN=Intermediate CA"))
+ .setSubjectPublicKey(intermediateKeyPair.getPublic())
+ .build()
File intermediateCertFile = writeCertificateToPEMFile(intermediateCert, "${baseDir.path}/nifi-cert.pem")
@@ -139,7 +135,10 @@ class TlsToolkitStandaloneGroovyTest {
X509Certificate rootCert = generateX509Certificate("CN=Root CA", rootKeyPair)
KeyPair intermediateKeyPair = generateKeyPair()
- X509Certificate intermediateCert = CertificateUtils.generateIssuedCertificate("CN=Intermediate CA", intermediateKeyPair.getPublic(), rootCert, rootKeyPair, DEFAULT_SIGNING_ALGORITHM, 1)
+ X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1))
+ .setSubject(new X500Principal("CN=Intermediate CA"))
+ .setSubjectPublicKey(intermediateKeyPair.getPublic())
+ .build()
File intermediateCertFile = writeCertificateToPEMFile(intermediateCert, "${baseDir.path}/nifi-cert.pem")
@@ -188,7 +187,7 @@ class TlsToolkitStandaloneGroovyTest {
* @return the X509Certificate
*/
private X509Certificate generateX509Certificate(String dn = "CN=Test Certificate", KeyPair keyPair = generateKeyPair()) {
- CertificateUtils.generateSelfSignedX509Certificate(keyPair, CertificateUtils.reorderDn(dn), DEFAULT_SIGNING_ALGORITHM, 1)
+ new StandardCertificateBuilder(keyPair, new X500Principal(dn), Duration.ofDays(1)).build()
}
private KeyPair generateKeyPair() {
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 9e411ba181..18585e7445 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
@@ -25,7 +25,7 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
@@ -40,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
@@ -47,6 +48,7 @@ import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
@@ -133,10 +135,12 @@ public class TlsCertificateSigningRequestPerformerTest {
return closeableHttpResponse;
});
KeyPair caKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
- caCertificate = CertificateUtils.generateSelfSignedX509Certificate(caKeyPair, "CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
+ caCertificate = new StandardCertificateBuilder(caKeyPair, new X500Principal("CN=fakeCa"), Duration.ofDays(TlsConfig.DEFAULT_DAYS)).build();
testHmac = TlsHelper.calculateHMac(testToken, caCertificate.getPublicKey());
- signedCsr = CertificateUtils.generateIssuedCertificate(jcaPKCS10CertificationRequest.getSubject().toString(), jcaPKCS10CertificationRequest.getPublicKey(),
- caCertificate, caKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
+ signedCsr = new StandardCertificateBuilder(caKeyPair, caCertificate.getIssuerX500Principal(), Duration.ofDays(TlsConfig.DEFAULT_DAYS))
+ .setSubject(new X500Principal(jcaPKCS10CertificationRequest.getSubject().toString()))
+ .setSubjectPublicKey(jcaPKCS10CertificationRequest.getPublicKey())
+ .build();
testSignedCsr = TlsHelper.pemEncodeJcaObject(signedCsr);
tlsCertificateSigningRequestPerformer = new TlsCertificateSigningRequestPerformer(httpClientBuilderSupplier, tlsClientConfig);
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 86220c061c..a4a35dd1b3 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
@@ -38,12 +38,15 @@ import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.Arrays;
import java.util.List;
+import javax.security.auth.x500.X500Principal;
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.security.cert.builder.StandardCertificateBuilder;
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;
@@ -121,7 +124,7 @@ public class TlsCertificateAuthorityServiceHandlerTest {
response = new StringWriter();
return new PrintWriter(response);
});
- caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, "CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
+ caCert = new StandardCertificateBuilder(keyPair, new X500Principal("CN=fakeCa"), Duration.ofDays(TlsConfig.DEFAULT_DAYS)).build();
requestedDn = new TlsConfig().calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME);
certificateKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(requestedDn, null, certificateKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
@@ -193,7 +196,7 @@ public class TlsCertificateAuthorityServiceHandlerTest {
JcaPKCS10CertificationRequest jcaPKCS10CertificationDecoded = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
- Extensions extensions = CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationDecoded);
+ Extensions extensions = jcaPKCS10CertificationDecoded.getRequestedExtensions();
// Satisfy @After requirement
baseRequest.setHandled(true);
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java
index 7b58f23fe6..974ee849d3 100644
--- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java
@@ -19,7 +19,6 @@ package org.apache.nifi.toolkit.tls.standalone;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
-import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.toolkit.tls.SystemExitCapturer;
@@ -504,13 +503,12 @@ public class TlsToolkitStandaloneTest {
TlsConfig tlsConfig = new TlsConfig();
tlsConfig.setDnPrefix(dnPrefix);
tlsConfig.setDnSuffix(dnSuffix);
- assertEquals(tlsConfig.calcDefaultDn(hostname), CertificateUtils.convertAbstractX509Certificate(certificateChain[0]).getSubjectX500Principal().getName());
TlsCertificateAuthorityTest.assertPrivateAndPublicKeyMatch(privateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey());
return nifiProperties;
}
private void checkClientCert(String clientDn, X509Certificate rootCert) throws Exception {
- String clientDnFile = TlsHelper.escapeFilename(CertificateUtils.reorderDn(clientDn));
+ String clientDnFile = TlsHelper.escapeFilename(clientDn);
String password;
try (FileReader fileReader = new FileReader(new File(tempDir, clientDnFile + ".password"))) {
List<String> lines = IOUtils.readLines(fileReader);
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 d04cd4e2e7..191be74c91 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,7 +17,7 @@
package org.apache.nifi.toolkit.tls.util;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import org.apache.nifi.util.file.FileUtils;
import org.bouncycastle.asn1.pkcs.Attribute;
@@ -43,30 +43,28 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.security.auth.x500.X500Principal;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.Reader;
import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
-import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -100,8 +98,6 @@ public class TlsHelperTest {
@Mock(lenient = true)
OutputStreamFactory outputStreamFactory;
- private ByteArrayOutputStream tmpFileOutputStream;
-
private File file;
public static KeyPair loadKeyPair(final Reader reader) throws IOException {
@@ -137,13 +133,13 @@ public class TlsHelperTest {
days = 360;
keySize = 2048;
keyPairAlgorithm = "RSA";
- signingAlgorithm = "SHA256WITHRSA";
+ signingAlgorithm = "SHA256withRSA";
keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm);
keyPairGenerator.initialize(keySize);
file = File.createTempFile("keystore", "file");
file.deleteOnExit();
- tmpFileOutputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream tmpFileOutputStream = new ByteArrayOutputStream();
when(outputStreamFactory.create(file)).thenReturn(tmpFileOutputStream);
}
@@ -162,7 +158,7 @@ public class TlsHelperTest {
goodTokens.add("0123456789abcdef");
String dn = "CN=testDN,O=testOrg";
- X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), dn, signingAlgorithm, days);
+ X509Certificate x509Certificate = new StandardCertificateBuilder(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), new X500Principal(dn), Duration.ofDays(days)).build();
PublicKey pubKey = x509Certificate.getPublicKey();
for (String token : badTokens) {
@@ -186,7 +182,7 @@ public class TlsHelperTest {
public void testGenerateSelfSignedCert() throws GeneralSecurityException {
String dn = "CN=testDN,O=testOrg";
- X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), dn, signingAlgorithm, days);
+ X509Certificate x509Certificate = new StandardCertificateBuilder(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), new X500Principal(dn), Duration.ofDays(days)).build();
Date notAfter = x509Certificate.getNotAfter();
assertTrue(notAfter.after(inFuture(days - 1)));
@@ -203,33 +199,6 @@ public class TlsHelperTest {
x509Certificate.checkValidity();
}
- @Test
- public void testIssueCert() throws IOException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
- X509Certificate issuer = loadCertificate(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("rootCert.crt")));
- KeyPair issuerKeyPair = loadKeyPair(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("rootCert.key")));
-
- String dn = "CN=testIssued, O=testOrg";
-
- KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
- X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, signingAlgorithm, 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 notBefore = x509Certificate.getNotBefore();
- assertTrue(notBefore.after(inFuture(-1)));
- assertTrue(notBefore.before(inFuture(1)));
-
- assertEquals(signingAlgorithm, x509Certificate.getSigAlgName());
- assertEquals(keyPairAlgorithm, x509Certificate.getPublicKey().getAlgorithm());
-
- x509Certificate.verify(issuerKeyPair.getPublic());
- }
-
@Test
public void testWriteKeyStoreSuccess() throws IOException, GeneralSecurityException {
String testPassword = "testPassword";
@@ -339,16 +308,16 @@ public class TlsHelperTest {
@Test
public void testClientDnFilenameSlashes() {
- String clientDn = "OU=NiFi/Organisation,CN=testuser";
- String escapedClientDn = TlsHelper.escapeFilename(CertificateUtils.reorderDn(clientDn));
+ String clientDn = "CN=testuser,OU=NiFi/Organisation";
+ String escapedClientDn = TlsHelper.escapeFilename(clientDn);
assertEquals("CN=testuser_OU=NiFi_Organisation", escapedClientDn);
}
@Test
public void testClientDnFilenameSpecialChars() {
- String clientDn = "OU=NiFi#!Organisation,CN=testuser";
- String escapedClientDn = TlsHelper.escapeFilename(CertificateUtils.reorderDn(clientDn));
+ String clientDn = "CN=testuser,OU=NiFi#!Organisation";
+ String escapedClientDn = TlsHelper.escapeFilename(clientDn);
assertEquals("CN=testuser_OU=NiFi_Organisation", escapedClientDn);
}