diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 4dca67b92d..9e2ea1daeb 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -368,147 +368,6 @@ language governing permissions and limitations under the License. -->
-
-
- 512
- 128
-
-
- ${project.version}
- true
- 10 sec
- 500 ms
- 30 sec
- 10 millis
-
- ./conf/flow.xml.gz
- true
- ./conf/archive/
- 30 days
- 500 MB
- ./conf/login-identity-providers.xml
- ./conf/authorizers.xml
- ./conf/templates
- ./database_repository
-
- ./conf/state-management.xml
- false
- ./conf/zookeeper.properties
- local-provider
- zk-provider
-
- org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
- ./flowfile_repository
- 256
- 2 mins
- false
- org.apache.nifi.controller.FileSystemSwapManager
- 20000
- 5 sec
- 1
- 5 sec
- 4
-
- org.apache.nifi.controller.repository.FileSystemRepository
- 10 MB
- 100
- ./content_repository
- 12 hours
- 50%
- true
- false
- /nifi-content-viewer/
-
-
-
- 30 sec
- ./lib
- ./work/nar/
- ./work/docs/components
-
- PBEWITHMD5AND256BITAES-CBC-OPENSSL
- BC
- ;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
-
- 9990
-
-
- org.apache.nifi.provenance.PersistentProvenanceRepository
- ./provenance_repository
- 24 hours
- 1 GB
- 30 secs
- 100 MB
- 2
- 1
- true
- EventType, FlowFileUUID, Filename, ProcessorID, Relationship
-
- 500 MB
- false
- 16
- 65536
-
-
- 100000
-
-
- org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
- 1440
- 1 min
-
-
- ./lib
-
- 8080
-
-
- ./work/jetty
- 200
-
-
-
-
-
-
-
-
-
-
- file-provider
-
-
-
-
-
-
- 5 sec
- false
-
-
- false
-
-
- 10
- 25
- 5 sec
- 5 sec
-
-
- 15 secs
-
-
-
- 3 secs
- 3 secs
- /nifi
-
-
-
-
-
- 12 hours
- rpm
diff --git a/nifi-commons/nifi-security-utils/pom.xml b/nifi-commons/nifi-security-utils/pom.xml
index 1381e82d9d..fddeeca17a 100644
--- a/nifi-commons/nifi-security-utils/pom.xml
+++ b/nifi-commons/nifi-security-utils/pom.xml
@@ -33,12 +33,10 @@
org.bouncycastlebcprov-jdk15on
- testorg.bouncycastlebcpkix-jdk15on
- test
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 3579493dcf..20497180d1 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -16,12 +16,41 @@
*/
package org.apache.nifi.security.util;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+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.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.math.BigInteger;
import java.net.Socket;
import java.net.URL;
+import java.security.KeyPair;
import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -29,15 +58,9 @@ import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Date;
import java.util.List;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocket;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.concurrent.TimeUnit;
public final class CertificateUtils {
@@ -326,6 +349,100 @@ public final class CertificateUtils {
}
}
+ /**
+ * 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 {
+ 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(
+ new X500Name(dn),
+ BigInteger.valueOf(System.currentTimeMillis()),
+ startDate, endDate,
+ 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}));
+
+ // 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 issueing the certificate
+ */
+ public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, 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(
+ new X500Name(issuer.getSubjectDN().getName()),
+ BigInteger.valueOf(System.currentTimeMillis()),
+ startDate, endDate,
+ 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}));
+
+ 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.
*
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
index 22fc62847b..7f93f51c94 100644
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
+++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
@@ -16,19 +16,8 @@
*/
package org.apache.nifi.security.util
-import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x509.ExtendedKeyUsage
-import org.bouncycastle.asn1.x509.KeyPurposeId
-import org.bouncycastle.asn1.x509.KeyUsage
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
-import org.bouncycastle.asn1.x509.X509Extension
-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 org.junit.After
import org.junit.Before
import org.junit.BeforeClass
@@ -46,13 +35,14 @@ import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.NoSuchProviderException
-import java.security.PrivateKey
-import java.security.PublicKey
import java.security.Security
import java.security.SignatureException
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
+
+import static org.junit.Assert.assertEquals
@RunWith(JUnit4.class)
class CertificateUtilsTest extends GroovyTestCase {
@@ -116,53 +106,7 @@ class CertificateUtilsTest extends GroovyTestCase {
private
static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair();
- return generateCertificate(dn, keyPair);
- }
-
- /**
- * Generates a signed certificate with a specific keypair.
- *
- * @param dn the DN
- * @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate
- * @return the certificate
- * @throws IOException
- * @throws NoSuchAlgorithmException
- * @throws CertificateException
- * @throws NoSuchProviderException
- * @throws SignatureException
- * @throws InvalidKeyException
- * @throws OperatorCreationException
- */
- private
- static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
- PrivateKey privateKey = keyPair.getPrivate();
- ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey);
- SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
- Date startDate = new Date(YESTERDAY);
- Date endDate = new Date(ONE_YEAR_FROM_NOW);
-
- X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
- new X500Name(dn),
- BigInteger.valueOf(System.currentTimeMillis()),
- startDate, endDate,
- new X500Name(dn),
- subPubKeyInfo);
-
- // Set certificate extensions
- // (1) digitalSignature extension
- certBuilder.addExtension(X509Extension.keyUsage, true,
- new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement));
-
- // (2) extendedKeyUsage extension
- Vector ekUsages = new Vector<>();
- ekUsages.add(KeyPurposeId.id_kp_clientAuth);
- ekUsages.add(KeyPurposeId.id_kp_serverAuth);
- certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages));
-
- // Sign the certificate
- X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
- return new JcaX509CertificateConverter().setProvider(PROVIDER)
- .getCertificate(certificateHolder);
+ return CertificateUtils.generateSelfSignedX509Certificate(keyPair, dn, SIGNATURE_ALGORITHM, 365);
}
/**
@@ -181,52 +125,16 @@ class CertificateUtilsTest extends GroovyTestCase {
* @throws OperatorCreationException
*/
private
- static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
+ static X509Certificate generateIssuedCertificate(String dn, X509Certificate issuer, KeyPair issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair();
- return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey);
- }
-
- /**
- * Generates a certificate with a specific public key signed by the issuer key.
- *
- * @param dn the subject DN
- * @param publicKey the subject public key
- * @param issuerDn the issuer DN
- * @param issuerKey the issuer private key
- * @return the certificate
- * @throws IOException
- * @throws NoSuchAlgorithmException
- * @throws CertificateException
- * @throws NoSuchProviderException
- * @throws SignatureException
- * @throws InvalidKeyException
- * @throws OperatorCreationException
- */
- private
- static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
- ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey);
- SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
- Date startDate = new Date(YESTERDAY);
- Date endDate = new Date(ONE_YEAR_FROM_NOW);
-
- X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
- new X500Name(issuerDn),
- BigInteger.valueOf(System.currentTimeMillis()),
- startDate, endDate,
- new X500Name(dn),
- subPubKeyInfo);
-
- X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
- return new JcaX509CertificateConverter().setProvider(PROVIDER)
- .getCertificate(certificateHolder);
+ return CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKey, SIGNATURE_ALGORITHM, 365);
}
private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) {
final KeyPair issuerKeyPair = generateKeyPair();
- final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
- final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
- final X509Certificate certificate = generateIssuedCertificate(dn, issuerDn, issuerPrivateKey);
+ final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, SIGNATURE_ALGORITHM, 365);
+ final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate, issuerKeyPair);
[certificate, issuerCertificate] as X509Certificate[]
}
@@ -238,6 +146,10 @@ class CertificateUtilsTest extends GroovyTestCase {
return x509Certificate as Certificate
}
+ private static Date inFuture(int days) {
+ return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days));
+ }
+
@Test
void testShouldConvertLegacyX509Certificate() {
// Arrange
@@ -513,4 +425,58 @@ class CertificateUtilsTest extends GroovyTestCase {
assert !dn1MatchesDn2Reversed
assert !dn1MatchesEmpty
}
+
+
+
+ @Test
+ public void testShouldGenerateSelfSignedCert() throws Exception {
+ String dn = "CN=testDN,O=testOrg";
+
+ int days = 365;
+ X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(generateKeyPair(), dn, SIGNATURE_ALGORITHM, days);
+
+ Date notAfter = x509Certificate.getNotAfter();
+ assertTrue(notAfter.after(inFuture(days - 1)));
+ assertTrue(notAfter.before(inFuture(days + 1)));
+
+ Date notBefore = x509Certificate.getNotBefore();
+ assertTrue(notBefore.after(inFuture(-1)));
+ assertTrue(notBefore.before(inFuture(1)));
+
+ assertEquals(dn, x509Certificate.getIssuerDN().getName());
+ assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase());
+ assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm());
+
+ x509Certificate.checkValidity();
+ }
+
+
+
+ @Test
+ public void testIssueCert() throws Exception {
+ int days = 365;
+ KeyPair issuerKeyPair = generateKeyPair();
+ X509Certificate issuer = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, "CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days);
+
+ String dn = "CN=testIssued,O=testOrg";
+
+ KeyPair keyPair = generateKeyPair();
+ X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, SIGNATURE_ALGORITHM, days);
+ assertEquals(dn, x509Certificate.getSubjectDN().toString());
+ assertEquals(issuer.getSubjectDN().toString(), x509Certificate.getIssuerDN().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(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase());
+ assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm());
+
+ x509Certificate.verify(issuerKeyPair.getPublic());
+ }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index a738069714..fff546b87e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -23,6 +23,147 @@
nifi-resourcespomholds common resources used to build installers
+
+
+ 512
+ 128
+
+
+ ${project.version}
+ true
+ 10 sec
+ 500 ms
+ 30 sec
+ 10 millis
+
+ ./conf/flow.xml.gz
+ true
+ ./conf/archive/
+ 30 days
+ 500 MB
+ ./conf/login-identity-providers.xml
+ ./conf/authorizers.xml
+ ./conf/templates
+ ./database_repository
+
+ ./conf/state-management.xml
+ false
+ ./conf/zookeeper.properties
+ local-provider
+ zk-provider
+
+ org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
+ ./flowfile_repository
+ 256
+ 2 mins
+ false
+ org.apache.nifi.controller.FileSystemSwapManager
+ 20000
+ 5 sec
+ 1
+ 5 sec
+ 4
+
+ org.apache.nifi.controller.repository.FileSystemRepository
+ 10 MB
+ 100
+ ./content_repository
+ 12 hours
+ 50%
+ true
+ false
+ /nifi-content-viewer/
+
+
+
+ 30 sec
+ ./lib
+ ./work/nar/
+ ./work/docs/components
+
+ PBEWITHMD5AND256BITAES-CBC-OPENSSL
+ BC
+ ;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+ 9990
+
+
+ org.apache.nifi.provenance.PersistentProvenanceRepository
+ ./provenance_repository
+ 24 hours
+ 1 GB
+ 30 secs
+ 100 MB
+ 2
+ 1
+ true
+ EventType, FlowFileUUID, Filename, ProcessorID, Relationship
+
+ 500 MB
+ false
+ 16
+ 65536
+
+
+ 100000
+
+
+ org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
+ 1440
+ 1 min
+
+
+ ./lib
+
+ 8080
+
+
+ ./work/jetty
+ 200
+
+
+
+
+
+
+
+
+
+
+ file-provider
+
+
+
+
+
+
+ 5 sec
+ false
+
+
+ false
+
+
+ 10
+ 25
+ 5 sec
+ 5 sec
+
+
+ 15 secs
+
+
+
+ 3 secs
+ 3 secs
+ /nifi
+
+
+
+
+
+ 12 hours
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/assembly/dependencies.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/assembly/dependencies.xml
index 2cc37cb2f4..9e7750a57e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/assembly/dependencies.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/assembly/dependencies.xml
@@ -23,6 +23,7 @@
src/main/resources/
+ truesrc/main/resources/bin
diff --git a/nifi-toolkit/nifi-toolkit-assembly/LICENSE b/nifi-toolkit/nifi-toolkit-assembly/LICENSE
new file mode 100644
index 0000000000..412bea37c6
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/LICENSE
@@ -0,0 +1,256 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
+
+APACHE NIFI TOOLKIT SUBCOMPONENTS:
+
+The Apache NiFi Toolkit project contains subcomponents with separate copyright
+notices and license terms. Your use of the source code for the these
+subcomponents is subject to the terms and conditions of the following
+licenses.
+
+ The binary distribution of this product bundles 'Bouncy Castle JDK 1.5 Provider'
+ under an MIT style license.
+
+ Copyright (c) 2000 - 2016 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ The binary distribution of this product bundles 'Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs'
+ under an MIT style license.
+
+ Copyright (c) 2000 - 2016 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
diff --git a/nifi-toolkit/nifi-toolkit-assembly/NOTICE b/nifi-toolkit/nifi-toolkit-assembly/NOTICE
new file mode 100644
index 0000000000..7cd5ffc072
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/NOTICE
@@ -0,0 +1,94 @@
+Apache NiFi Toolkit
+Copyright 2014-2016 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+===========================================
+Apache Software License v2
+===========================================
+
+The following binary components are provided under the Apache Software License v2
+
+ (ASLv2) Apache NiFi
+ The following NOTICE information applies:
+ Apache NiFi
+ Copyright 2014-2016 The Apache Software Foundation
+
+ This product includes software developed at
+ The Apache Software Foundation (http://www.apache.org/).
+
+ This product includes the following work from the Apache Hadoop project:
+
+ BoundedByteArrayOutputStream.java adapted to SoftLimitBoundedByteArrayOutputStream.java
+
+ (ASLv2) Apache Commons CLI
+ The following NOTICE information applies:
+ Apache Commons IO
+ Copyright 2001-2015 The Apache Software Foundation
+
+ (ASLv2) Apache Commons Codec
+ The following NOTICE information applies:
+ Apache Commons Codec
+ Copyright 2002-2014 The Apache Software Foundation
+
+ src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java
+ contains test data from http://aspell.net/test/orig/batch0.tab.
+ Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org)
+
+ ===============================================================================
+
+ The content of package org.apache.commons.codec.language.bm has been translated
+ from the original php source code available at http://stevemorse.org/phoneticinfo.htm
+ with permission from the original authors.
+ Original source copyright:
+ Copyright (c) 2008 Alexander Beider & Stephen P. Morse.
+
+ (ASLv2) Apache Commons IO
+ The following NOTICE information applies:
+ Apache Commons IO
+ Copyright 2002-2012 The Apache Software Foundation
+
+ (ASLv2) Apache Commons Lang
+ The following NOTICE information applies:
+ Apache Commons Lang
+ Copyright 2001-2015 The Apache Software Foundation
+
+ (ASLv2) Apache HttpComponents
+ The following NOTICE information applies:
+ Apache HttpClient
+ Copyright 1999-2015 The Apache Software Foundation
+
+ Apache HttpCore
+ Copyright 2005-2015 The Apache Software Foundation
+
+ This project contains annotations derived from JCIP-ANNOTATIONS
+ Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net
+
+ (ASLv2) Jackson JSON processor
+ The following NOTICE information applies:
+ # Jackson JSON processor
+
+ Jackson is a high-performance, Free/Open Source JSON processing library.
+ It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has
+ been in development since 2007.
+ It is currently developed by a community of developers, as well as supported
+ commercially by FasterXML.com.
+
+ ## Licensing
+
+ Jackson core and extension components may licensed under different licenses.
+ To find the details that apply to this artifact see the accompanying LICENSE file.
+ For more information, including possible other licensing options, contact
+ FasterXML.com (http://fasterxml.com).
+
+ ## Credits
+
+ A list of contributors may be found from CREDITS file, which is included
+ in some artifacts (usually source distributions); but is always available
+ from the source code management (SCM) system project uses.
+
+ (ASLv2) Jetty
+ The following NOTICE information applies:
+ Jetty Web Container
+ Copyright 1995-2015 Mort Bay Consulting Pty Ltd.
\ No newline at end of file
diff --git a/nifi-toolkit/nifi-toolkit-assembly/pom.xml b/nifi-toolkit/nifi-toolkit-assembly/pom.xml
new file mode 100644
index 0000000000..edd5aa1d71
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+ 4.0.0
+
+ org.apache.nifi
+ nifi-toolkit
+ 1.0.0-SNAPSHOT
+
+ nifi-toolkit-assembly
+ pom
+ This is the assembly Apache NiFi Toolkit
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+
+
+ src/main/resources/conf/config-client.json
+ src/main/resources/conf/config-server.json
+
+
+
+
+ maven-assembly-plugin
+
+ nifi-toolkit-${project.version}
+
+
+
+ make shared resource
+
+ single
+
+ package
+
+
+ 0755
+ 0755
+ 0644
+
+
+ src/main/assembly/dependencies.xml
+
+ posix
+
+
+
+
+
+
+
+
+ org.apache.nifi
+ nifi-toolkit-tls
+
+
+ org.slf4j
+ slf4j-api
+ compile
+ 1.7.12
+
+
+ org.eclipse.jetty
+ jetty-server
+ compile
+
+
+ javax.servlet
+ javax.servlet-api
+ compile
+
+
+ commons-io
+ commons-io
+ 2.5
+
+
+
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml b/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml
new file mode 100644
index 0000000000..626287a122
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/assembly/dependencies.xml
@@ -0,0 +1,75 @@
+
+
+
+ bin
+
+ dir
+ zip
+ tar.gz
+
+ true
+ nifi-toolkit-${project.version}
+
+
+
+
+ lib
+ false
+ 0770
+ 0660
+
+
+
+
+ ${project.basedir}/src/main/resources/bin
+ bin/
+ 0700
+
+
+ ${project.basedir}/src/main/resources/conf
+ conf/
+ 0600
+
+
+ ${project.basedir}/src/main/resources/classpath
+ classpath/
+ 0600
+
+
+ ${project.build.directory}/nifi-resources/conf
+ lib/
+ 0600
+
+
+
+
+
+ ./
+ LICENSE
+ 0644
+ true
+
+
+
+ ./
+ NOTICE
+ 0644
+ true
+
+
+
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.bat b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.bat
new file mode 100644
index 0000000000..b875e1ab47
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.bat
@@ -0,0 +1,40 @@
+@echo off
+rem
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements. See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+rem
+
+rem Use JAVA_HOME if it's set; otherwise, just use java
+
+if "%JAVA_HOME%" == "" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+set JAVA_EXE=%JAVA_HOME%\bin\java.exe
+goto startConfig
+
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly.
+echo Instead the PATH will be used to find the java executable.
+echo.
+set JAVA_EXE=java
+goto startConfig
+
+:startConfig
+set LIB_DIR=%~dp0..\classpath;%~dp0..\lib
+
+SET JAVA_PARAMS=-cp %LIB_DIR%\* -Xms12m -Xmx24m %JAVA_ARGS% org.apache.nifi.toolkit.tls.service.server.TlsCertificateAuthorityService
+
+cmd.exe /C "%JAVA_EXE%" %JAVA_PARAMS% %*
+
+popd
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.sh b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.sh
new file mode 100644
index 0000000000..c34c4201f0
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/tls-toolkit.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# 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.
+#
+#
+
+# Script structure inspired from Apache Karaf and other Apache projects with similar startup approaches
+
+SCRIPT_DIR=$(dirname "$0")
+SCRIPT_NAME=$(basename "$0")
+NIFI_TOOLKIT_HOME=$(cd "${SCRIPT_DIR}" && cd .. && pwd)
+PROGNAME=$(basename "$0")
+
+
+warn() {
+ echo "${PROGNAME}: $*"
+}
+
+die() {
+ warn "$*"
+ exit 1
+}
+
+detectOS() {
+ # OS specific support (must be 'true' or 'false').
+ cygwin=false;
+ aix=false;
+ os400=false;
+ darwin=false;
+ case "$(uname)" in
+ CYGWIN*)
+ cygwin=true
+ ;;
+ AIX*)
+ aix=true
+ ;;
+ OS400*)
+ os400=true
+ ;;
+ Darwin)
+ darwin=true
+ ;;
+ esac
+ # For AIX, set an environment variable
+ if ${aix}; then
+ export LDR_CNTRL=MAXDATA=0xB0000000@DSA
+ echo ${LDR_CNTRL}
+ fi
+}
+
+locateJava() {
+ # Setup the Java Virtual Machine
+ if $cygwin ; then
+ [ -n "${JAVA}" ] && JAVA=$(cygpath --unix "${JAVA}")
+ [ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}")
+ fi
+
+ if [ "x${JAVA}" = "x" ] && [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+ if [ "x${JAVA}" = "x" ]; then
+ if [ "x${JAVA_HOME}" != "x" ]; then
+ if [ ! -d "${JAVA_HOME}" ]; then
+ die "JAVA_HOME is not valid: ${JAVA_HOME}"
+ fi
+ JAVA="${JAVA_HOME}/bin/java"
+ else
+ warn "JAVA_HOME not set; results may vary"
+ JAVA=$(type java)
+ JAVA=$(expr "${JAVA}" : '.* \(/.*\)$')
+ if [ "x${JAVA}" = "x" ]; then
+ die "java command not found"
+ fi
+ fi
+ fi
+}
+
+init() {
+ # Determine if there is special OS handling we must perform
+ detectOS
+
+ # Locate the Java VM to execute
+ locateJava "$1"
+}
+
+run() {
+ LIBS="${NIFI_TOOLKIT_HOME}/lib/*"
+
+ sudo_cmd_prefix=""
+ if $cygwin; then
+ NIFI_TOOLKIT_HOME=$(cygpath --path --windows "${NIFI_TOOLKIT_HOME}")
+ CLASSPATH="$NIFI_TOOLKIT_HOME/classpath";$(cygpath --path --windows "${LIBS}")
+ else
+ CLASSPATH="$NIFI_TOOLKIT_HOME/classpath:${LIBS}"
+ fi
+
+ export JAVA_HOME="$JAVA_HOME"
+ export NIFI_TOOLKIT_HOME="$NIFI_TOOLKIT_HOME"
+
+ umask 0077
+ "${JAVA}" -cp "${CLASSPATH}" -Xms12m -Xmx24m org.apache.nifi.toolkit.tls.TlsToolkitMain "$@"
+ return $?
+}
+
+
+init "$1"
+run "$@"
\ No newline at end of file
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/log4j.properties b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/log4j.properties
new file mode 100644
index 0000000000..fc2aaf12c4
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/log4j.properties
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+log4j.rootLogger=INFO,console
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n
\ No newline at end of file
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-client.json b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-client.json
new file mode 100644
index 0000000000..e572bc1407
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-client.json
@@ -0,0 +1,14 @@
+{
+ "keyStore" : "clientKeyStore",
+ "keyStoreType" : "jks",
+ "token" : "myTestTokenUseSomethingStronger",
+ "dn" : "CN=otherHostname,OU=NIFI",
+ "port" : 8443,
+ "caHostname" : "localhost",
+ "trustStore" : "clientTrustStore",
+ "trustStoreType" : "jks",
+ "days" : 1095,
+ "keySize" : 2048,
+ "keyPairAlgorithm" : "RSA",
+ "signingAlgorithm" : "SHA256WITHRSA"
+}
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-server.json b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-server.json
new file mode 100644
index 0000000000..fae89ed5ca
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/conf/config-server.json
@@ -0,0 +1,11 @@
+{
+ "keyStore" : "serverKeyStore",
+ "keyStoreType" : "jks",
+ "token" : "myTestTokenUseSomethingStronger",
+ "caHostname" : "localhost",
+ "port" : 8443,
+ "days" : 1095,
+ "keySize" : 2048,
+ "keyPairAlgorithm" : "RSA",
+ "signingAlgorithm" : "SHA256WITHRSA"
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/pom.xml b/nifi-toolkit/nifi-toolkit-tls/pom.xml
new file mode 100644
index 0000000000..2c05755561
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/pom.xml
@@ -0,0 +1,115 @@
+
+
+
+ 4.0.0
+
+ org.apache.nifi
+ nifi-toolkit
+ 1.0.0-SNAPSHOT
+
+ nifi-toolkit-tls
+ Tooling to make tls configuration easier
+
+
+ org.apache.nifi
+ nifi-properties
+
+
+ org.apache.nifi
+ nifi-security-utils
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.12
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ commons-cli
+ commons-cli
+ 1.3.1
+
+
+ commons-io
+ commons-io
+ 2.5
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.2
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack
+ process-resources
+
+ unpack
+
+
+
+
+ org.apache.nifi
+ nifi-resources
+ zip
+ resources
+ true
+ ${project.build.directory}/classes
+ **/nifi.properties
+
+
+
+
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+
+
+ src/test/resources/rootCert.crt
+ src/test/resources/rootCert.key
+
+
+
+
+
+
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/TlsToolkitMain.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/TlsToolkitMain.java
new file mode 100644
index 0000000000..7d445a5ee6
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/TlsToolkitMain.java
@@ -0,0 +1,97 @@
+/*
+ * 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.tls;
+
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.service.client.TlsCertificateAuthorityClientCommandLine;
+import org.apache.nifi.toolkit.tls.service.server.TlsCertificateAuthorityServiceCommandLine;
+import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Command line entry point
+ */
+public class TlsToolkitMain {
+ public static final String DESCRIPTION = "DESCRIPTION";
+ private final Map> mainMap;
+
+ public TlsToolkitMain() {
+ mainMap = new LinkedHashMap<>();
+ mainMap.put("standalone", TlsToolkitStandaloneCommandLine.class);
+ mainMap.put("server", TlsCertificateAuthorityServiceCommandLine.class);
+ mainMap.put("client", TlsCertificateAuthorityClientCommandLine.class);
+ }
+
+ public static void main(String[] args) {
+ new TlsToolkitMain().doMain(args);
+ }
+
+ private void printUsageAndExit(String message, ExitCode exitCode) {
+ System.out.println(message);
+ System.out.println();
+ System.out.println("Usage: tls-toolkit service [-h] [args]");
+ System.out.println();
+ System.out.println("Services:");
+ mainMap.forEach((s, aClass) -> System.out.println(" " + s + ": " + getDescription(aClass)));
+ System.out.println();
+ System.exit(exitCode.ordinal());
+ }
+
+ private String getDescription(Class> clazz) {
+ try {
+ Field declaredField = clazz.getDeclaredField(DESCRIPTION);
+ return String.valueOf(declaredField.get(null));
+ } catch (Exception e) {
+ return "Unable to get description. (" + e.getMessage() + ")";
+ }
+ }
+
+ public void doMain(String[] args) {
+ if (args.length < 1) {
+ printUsageAndExit("Expected at least a service argument.", ExitCode.INVALID_ARGS);
+ }
+
+ String service = args[0].toLowerCase();
+ Class> mainClass = mainMap.get(service);
+ if (mainClass == null) {
+ printUsageAndExit("Unknown service: " + service, ExitCode.INVALID_ARGS);
+ }
+
+ Method main;
+ try {
+ main = mainClass.getDeclaredMethod("main", String[].class);
+ } catch (NoSuchMethodException e) {
+ printUsageAndExit("Service " + service + " is missing main method.", ExitCode.SERVICE_ERROR);
+ return;
+ }
+
+ try {
+ main.invoke(null, (Object) args);
+ } catch (IllegalAccessException e) {
+ printUsageAndExit("Service " + service + " has invalid main method.", ExitCode.SERVICE_ERROR);
+ } catch (InvocationTargetException e) {
+ printUsageAndExit("Service " + service + " error: " + e.getCause().getMessage(), ExitCode.SERVICE_ERROR);
+ }
+ return;
+ }
+}
\ No newline at end of file
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/BaseCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/BaseCommandLine.java
new file mode 100644
index 0000000000..763f71535d
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/BaseCommandLine.java
@@ -0,0 +1,178 @@
+/*
+ * 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.tls.commandLine;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.nifi.toolkit.tls.TlsToolkitMain;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+
+public abstract class BaseCommandLine {
+ public static final String HELP_ARG = "help";
+ public static final String JAVA_HOME = "JAVA_HOME";
+ public static final String NIFI_TOOLKIT_HOME = "NIFI_TOOLKIT_HOME";
+ public static final String FOOTER = new StringBuilder(System.lineSeparator()).append("Java home: ")
+ .append(System.getenv(JAVA_HOME)).append(System.lineSeparator()).append("NiFi Toolkit home: ").append(System.getenv(NIFI_TOOLKIT_HOME)).toString();
+
+ public static final String KEY_SIZE_ARG = "keySize";
+ public static final String KEY_ALGORITHM_ARG = "keyAlgorithm";
+ public static final String CERTIFICATE_AUTHORITY_HOSTNAME_ARG = "certificateAuthorityHostname";
+ public static final String DAYS_ARG = "days";
+ public static final String KEY_STORE_TYPE_ARG = "keyStoreType";
+ public static final String SIGNING_ALGORITHM_ARG = "signingAlgorithm";
+ public static final String DN_ARG = "dn";
+ public static final String DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG = "differentKeyAndKeystorePasswords";
+
+ public static final String KEYSTORE = "keystore.";
+ public static final String TRUSTSTORE = "truststore.";
+
+ private final Options options;
+ private final String header;
+ private int keySize;
+ private String keyAlgorithm;
+ private String certificateAuthorityHostname;
+ private String keyStoreType;
+ private int days;
+ private String signingAlgorithm;
+ private boolean differentPasswordForKeyAndKeystore;
+
+ public BaseCommandLine(String header) {
+ this.header = System.lineSeparator() + header + System.lineSeparator() + System.lineSeparator();
+ this.options = new Options();
+ if (shouldAddDaysArg()) {
+ addOptionWithArg("d", DAYS_ARG, "Number of days issued certificate should be valid for.", TlsConfig.DEFAULT_DAYS);
+ }
+ addOptionWithArg("T", KEY_STORE_TYPE_ARG, "The type of keyStores to generate.", getKeyStoreTypeDefault());
+ options.addOption("h", HELP_ARG, false, "Print help and exit.");
+ addOptionWithArg("c", CERTIFICATE_AUTHORITY_HOSTNAME_ARG, "Hostname of NiFi Certificate Authority", TlsConfig.DEFAULT_HOSTNAME);
+ addOptionWithArg("a", KEY_ALGORITHM_ARG, "Algorithm to use for generated keys.", TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+ addOptionWithArg("k", KEY_SIZE_ARG, "Number of bits for generated keys.", TlsConfig.DEFAULT_KEY_SIZE);
+ if (shouldAddSigningAlgorithmArg()) {
+ addOptionWithArg("s", SIGNING_ALGORITHM_ARG, "Algorithm to use for signing certificates.", TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ }
+ addOptionNoArg("g", DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG, "Use different generated password for the key and the keyStore.");
+ }
+
+ protected String getKeyStoreTypeDefault() {
+ return TlsConfig.DEFAULT_KEY_STORE_TYPE;
+ }
+
+ protected boolean shouldAddSigningAlgorithmArg() {
+ return true;
+ }
+
+ protected boolean shouldAddDaysArg() {
+ return true;
+ }
+
+ protected void addOptionWithArg(String arg, String longArg, String description) {
+ addOptionWithArg(arg, longArg, description, null);
+ }
+
+ protected void addOptionNoArg(String arg, String longArg, String description) {
+ options.addOption(arg, longArg, false, description);
+ }
+
+ protected void addOptionWithArg(String arg, String longArg, String description, Object defaultVal) {
+ String fullDescription = description;
+ if (defaultVal != null) {
+ fullDescription += " (default: " + defaultVal + ")";
+ }
+ options.addOption(arg, longArg, true, fullDescription);
+ }
+
+ public void printUsage(String errorMessage) {
+ if (errorMessage != null) {
+ System.out.println(errorMessage);
+ System.out.println();
+ }
+ HelpFormatter helpFormatter = new HelpFormatter();
+ helpFormatter.setWidth(160);
+ helpFormatter.printHelp(TlsToolkitMain.class.getCanonicalName(), header, options, FOOTER, true);
+ }
+
+ protected T printUsageAndThrow(String errorMessage, ExitCode exitCode) throws CommandLineParseException {
+ printUsage(errorMessage);
+ throw new CommandLineParseException(errorMessage, exitCode.ordinal());
+ }
+
+ protected int getIntValue(CommandLine commandLine, String arg, int defaultVal) throws CommandLineParseException {
+ try {
+ return Integer.parseInt(commandLine.getOptionValue(arg, Integer.toString(defaultVal)));
+ } catch (NumberFormatException e) {
+ return printUsageAndThrow("Expected integer for " + arg + " argument. (" + e.getMessage() + ")", ExitCode.ERROR_PARSING_INT_ARG);
+ }
+ }
+
+ public int getKeySize() {
+ return keySize;
+ }
+
+ public String getKeyAlgorithm() {
+ return keyAlgorithm;
+ }
+
+ public String getCertificateAuthorityHostname() {
+ return certificateAuthorityHostname;
+ }
+
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ public int getDays() {
+ return days;
+ }
+
+ public String getSigningAlgorithm() {
+ return signingAlgorithm;
+ }
+
+ public boolean differentPasswordForKeyAndKeystore() {
+ return differentPasswordForKeyAndKeystore;
+ }
+
+ protected CommandLine doParse(String[] args) throws CommandLineParseException {
+ CommandLineParser parser = new DefaultParser();
+ CommandLine commandLine;
+ try {
+ commandLine = parser.parse(options, args);
+ if (commandLine.hasOption(HELP_ARG)) {
+ return printUsageAndThrow(null, ExitCode.HELP);
+ }
+ certificateAuthorityHostname = commandLine.getOptionValue(CERTIFICATE_AUTHORITY_HOSTNAME_ARG, TlsConfig.DEFAULT_HOSTNAME);
+ days = getIntValue(commandLine, DAYS_ARG, TlsConfig.DEFAULT_DAYS);
+ keySize = getIntValue(commandLine, KEY_SIZE_ARG, TlsConfig.DEFAULT_KEY_SIZE);
+ keyAlgorithm = commandLine.getOptionValue(KEY_ALGORITHM_ARG, TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+ keyStoreType = commandLine.getOptionValue(KEY_STORE_TYPE_ARG, getKeyStoreTypeDefault());
+ signingAlgorithm = commandLine.getOptionValue(SIGNING_ALGORITHM_ARG, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ differentPasswordForKeyAndKeystore = commandLine.hasOption(DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG);
+ } catch (ParseException e) {
+ return printUsageAndThrow("Error parsing command line. (" + e.getMessage() + ")", ExitCode.ERROR_PARSING_COMMAND_LINE);
+ }
+ return commandLine;
+ }
+
+ public void parse(String... args) throws CommandLineParseException {
+ doParse(args);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/CommandLineParseException.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/CommandLineParseException.java
new file mode 100644
index 0000000000..d09cc08d33
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/CommandLineParseException.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.toolkit.tls.commandLine;
+
+public class CommandLineParseException extends Exception {
+ private final int exitCode;
+
+ public CommandLineParseException(String message, int exitCode) {
+ super(message);
+ this.exitCode = exitCode;
+ }
+
+ public int getExitCode() {
+ return exitCode;
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java
new file mode 100644
index 0000000000..b99b1af531
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tls.commandLine;
+
+public enum ExitCode {
+ SUCCESS,
+ HELP,
+ INVALID_ARGS,
+ SERVICE_ERROR,
+ ERROR_PARSING_COMMAND_LINE,
+ ERROR_GENERATING_CONFIG,
+ ERROR_SAME_KEY_AND_KEY_PASSWORD,
+ ERROR_INCORRECT_NUMBER_OF_PASSWORDS,
+ ERROR_PARSING_INT_ARG,
+ ERROR_TOKEN_ARG_EMPTY,
+ ERROR_READING_NIFI_PROPERTIES
+}
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
new file mode 100644
index 0000000000..78ca9d6e0c
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsClientConfig.java
@@ -0,0 +1,80 @@
+/*
+ * 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.tls.configuration;
+
+import org.apache.nifi.toolkit.tls.service.client.TlsCertificateSigningRequestPerformer;
+import org.apache.nifi.util.StringUtils;
+
+import java.security.NoSuchAlgorithmException;
+
+public class TlsClientConfig extends TlsConfig {
+ private String trustStore;
+ private String trustStorePassword;
+ private String trustStoreType = DEFAULT_KEY_STORE_TYPE;
+
+ public TlsClientConfig() {
+ }
+
+ public TlsClientConfig(TlsConfig tlsConfig) {
+ setToken(tlsConfig.getToken());
+ setCaHostname(tlsConfig.getCaHostname());
+ setPort(tlsConfig.getPort());
+ setKeyStoreType(tlsConfig.getKeyStoreType());
+ setTrustStoreType(tlsConfig.getKeyStoreType());
+ setKeyPairAlgorithm(tlsConfig.getKeyPairAlgorithm());
+ setKeySize(tlsConfig.getKeySize());
+ setSigningAlgorithm(tlsConfig.getSigningAlgorithm());
+ }
+
+
+ public String getTrustStoreType() {
+ return trustStoreType;
+ }
+
+ public void setTrustStoreType(String trustStoreType) {
+ this.trustStoreType = trustStoreType;
+ }
+
+ public String getTrustStore() {
+ return trustStore;
+ }
+
+ public void setTrustStore(String trustStore) {
+ this.trustStore = trustStore;
+ }
+
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ public void setTrustStorePassword(String trustStorePassword) {
+ this.trustStorePassword = trustStorePassword;
+ }
+
+ public TlsCertificateSigningRequestPerformer createCertificateSigningRequestPerformer() throws NoSuchAlgorithmException {
+ return new TlsCertificateSigningRequestPerformer(this);
+ }
+
+ @Override
+ public void initDefaults() {
+ super.initDefaults();
+ if (StringUtils.isEmpty(trustStoreType)) {
+ trustStoreType = DEFAULT_KEY_STORE_TYPE;
+ }
+ }
+}
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
new file mode 100644
index 0000000000..819b41b8d8
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java
@@ -0,0 +1,171 @@
+/*
+ * 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.tls.configuration;
+
+import org.apache.nifi.util.StringUtils;
+
+public class TlsConfig {
+ public static final String DEFAULT_HOSTNAME = "localhost";
+ public static final String DEFAULT_KEY_STORE_TYPE = "jks";
+ public static final int DEFAULT_PORT = 8443;
+ public static final int DEFAULT_DAYS = 3 * 365;
+ public static final int DEFAULT_KEY_SIZE = 2048;
+ public static final String DEFAULT_KEY_PAIR_ALGORITHM = "RSA";
+ public static final String DEFAULT_SIGNING_ALGORITHM = "SHA256WITHRSA";
+
+ private int days = DEFAULT_DAYS;
+ private int keySize = DEFAULT_KEY_SIZE;
+ private String keyPairAlgorithm = DEFAULT_KEY_PAIR_ALGORITHM;
+ private String signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
+
+ private String dn;
+ private String keyStore;
+ private String keyStoreType = DEFAULT_KEY_STORE_TYPE;
+ private String keyStorePassword;
+ private String keyPassword;
+ private String token;
+ private String caHostname = DEFAULT_HOSTNAME;
+ private int port = DEFAULT_PORT;
+
+ public static String calcDefaultDn(String hostname) {
+ return "CN=" + hostname + ",OU=NIFI";
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getKeyStore() {
+ return keyStore;
+ }
+
+ public void setKeyStore(String keyStore) {
+ this.keyStore = keyStore;
+ }
+
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ public void setKeyStoreType(String keyStoreType) {
+ this.keyStoreType = keyStoreType;
+ }
+
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
+ public String getKeyPassword() {
+ return keyPassword;
+ }
+
+ public void setKeyPassword(String keyPassword) {
+ this.keyPassword = keyPassword;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getCaHostname() {
+ return caHostname;
+ }
+
+ public void setCaHostname(String caHostname) {
+ this.caHostname = caHostname;
+ }
+
+ public String getDn() {
+ return dn;
+ }
+
+ public void setDn(String dn) {
+ this.dn = dn;
+ }
+
+ public int getDays() {
+ return days;
+ }
+
+ public void setDays(int days) {
+ this.days = days;
+ }
+
+ public int getKeySize() {
+ return keySize;
+ }
+
+ public void setKeySize(int keySize) {
+ this.keySize = keySize;
+ }
+
+ public String getKeyPairAlgorithm() {
+ return keyPairAlgorithm;
+ }
+
+ public void setKeyPairAlgorithm(String keyPairAlgorithm) {
+ this.keyPairAlgorithm = keyPairAlgorithm;
+ }
+
+ public String getSigningAlgorithm() {
+ return signingAlgorithm;
+ }
+
+ public void setSigningAlgorithm(String signingAlgorithm) {
+ this.signingAlgorithm = signingAlgorithm;
+ }
+
+ public void initDefaults() {
+ if (days == 0) {
+ days = DEFAULT_DAYS;
+ }
+ if (keySize == 0) {
+ keySize = DEFAULT_KEY_SIZE;
+ }
+ if (StringUtils.isEmpty(keyPairAlgorithm)) {
+ keyPairAlgorithm = DEFAULT_KEY_PAIR_ALGORITHM;
+ }
+ if (StringUtils.isEmpty(signingAlgorithm)) {
+ signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
+ }
+ if (port == 0) {
+ port = DEFAULT_PORT;
+ }
+ if (StringUtils.isEmpty(keyStoreType)) {
+ keyStoreType = DEFAULT_KEY_STORE_TYPE;
+ }
+ if (StringUtils.isEmpty(caHostname)) {
+ caHostname = DEFAULT_HOSTNAME;
+ }
+ if (StringUtils.isEmpty(dn)) {
+ dn = calcDefaultDn(caHostname);
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java
new file mode 100644
index 0000000000..5a5e049556
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java
@@ -0,0 +1,159 @@
+/*
+ * 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.tls.manager;
+
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.PasswordUtil;
+import org.apache.nifi.util.StringUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BaseTlsManager {
+ public static final String PKCS_12 = "PKCS12";
+ private final TlsConfig tlsConfig;
+ private final PasswordUtil passwordUtil;
+ private final InputStreamFactory inputStreamFactory;
+ private final KeyStore keyStore;
+ private final List> configurationWriters;
+ private boolean differentKeyAndKeyStorePassword = false;
+
+ public BaseTlsManager(TlsConfig tlsConfig) throws GeneralSecurityException, IOException {
+ this(tlsConfig, new PasswordUtil(), FileInputStream::new);
+ }
+
+ public BaseTlsManager(TlsConfig tlsConfig, PasswordUtil passwordUtil, InputStreamFactory inputStreamFactory) throws GeneralSecurityException, IOException {
+ this.tlsConfig = tlsConfig;
+ this.passwordUtil = passwordUtil;
+ this.inputStreamFactory = inputStreamFactory;
+ this.keyStore = loadKeystore(tlsConfig.getKeyStore(), tlsConfig.getKeyStoreType(), getKeyStorePassword());
+ this.configurationWriters = new ArrayList<>();
+ }
+
+ public KeyStore getKeyStore() {
+ return keyStore;
+ }
+
+ public KeyStore.Entry getEntry(String alias) throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException {
+ String keyPassword = getKeyPassword();
+ return keyStore.getEntry(alias, new KeyStore.PasswordProtection(keyPassword == null ? null : keyPassword.toCharArray()));
+ }
+
+ public KeyStore.Entry addPrivateKeyToKeyStore(KeyPair keyPair, String alias, Certificate... certificates) throws GeneralSecurityException, IOException {
+ return addPrivateKeyToKeyStore(keyStore, keyPair, alias, getKeyPassword(), certificates);
+ }
+
+ private KeyStore.Entry addPrivateKeyToKeyStore(KeyStore keyStore, KeyPair keyPair, String alias, String passphrase, Certificate... certificates) throws GeneralSecurityException, IOException {
+ keyStore.setKeyEntry(alias, keyPair.getPrivate(), passphrase == null ? null : passphrase.toCharArray(), certificates);
+ return getEntry(alias);
+ }
+
+ public void setDifferentKeyAndKeyStorePassword(boolean differentKeyAndKeyStorePassword) {
+ this.differentKeyAndKeyStorePassword = differentKeyAndKeyStorePassword;
+ }
+
+ private String getKeyPassword() {
+ if (keyStore.getType().equalsIgnoreCase(PKCS_12)) {
+ tlsConfig.setKeyPassword(null);
+ return null;
+ } else {
+ String result = tlsConfig.getKeyPassword();
+ if (StringUtils.isEmpty(result)) {
+ if (differentKeyAndKeyStorePassword) {
+ result = passwordUtil.generatePassword();
+ } else {
+ result = getKeyStorePassword();
+ }
+ tlsConfig.setKeyPassword(result);
+ }
+ return result;
+ }
+ }
+
+ private String getKeyStorePassword() {
+ String result = tlsConfig.getKeyStorePassword();
+ if (StringUtils.isEmpty(result)) {
+ result = passwordUtil.generatePassword();
+ tlsConfig.setKeyStorePassword(result);
+ }
+ return result;
+ }
+
+ private KeyStore getInstance(String keyStoreType) throws KeyStoreException, NoSuchProviderException {
+ if (PKCS_12.equals(keyStoreType)) {
+ return KeyStore.getInstance(keyStoreType, BouncyCastleProvider.PROVIDER_NAME);
+ } else {
+ return KeyStore.getInstance(keyStoreType);
+ }
+ }
+
+ protected KeyStore loadKeystore(String keyStore, String keyStoreType, String keyStorePassword) throws GeneralSecurityException, IOException {
+ KeyStore result = getInstance(keyStoreType);
+ File file = new File(keyStore);
+ if (file.exists()) {
+ try (InputStream stream = inputStreamFactory.create(file)) {
+ result.load(stream, keyStorePassword.toCharArray());
+ }
+ return result;
+ }
+ result.load(null, null);
+ return result;
+ }
+
+ public void write(OutputStreamFactory outputStreamFactory) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
+ String keyStorePassword = getKeyStorePassword();
+
+ try (OutputStream outputStream = outputStreamFactory.create(new File(tlsConfig.getKeyStore()))) {
+ keyStore.store(outputStream, keyStorePassword.toCharArray());
+ }
+
+ for (ConfigurationWriter configurationWriter : configurationWriters) {
+ configurationWriter.write(tlsConfig);
+ }
+ }
+
+ public PasswordUtil getPasswordUtil() {
+ return passwordUtil;
+ }
+
+ public void addConfigurationWriter(ConfigurationWriter configurationWriter) {
+ configurationWriters.add(configurationWriter);
+ }
+
+ public TlsConfig getTlsConfig() {
+ return tlsConfig;
+ }
+}
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
new file mode 100644
index 0000000000..39faf28cd6
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java
@@ -0,0 +1,49 @@
+/*
+ * 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.tls.manager;
+
+import org.apache.nifi.security.util.CertificateUtils;
+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 java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+public class TlsCertificateAuthorityManager extends BaseTlsManager {
+ public TlsCertificateAuthorityManager(TlsConfig tlsConfig) throws GeneralSecurityException, IOException {
+ super(tlsConfig);
+ }
+
+ public KeyStore.PrivateKeyEntry getOrGenerateCertificateAuthority() throws GeneralSecurityException, IOException {
+ KeyStore.Entry entry = getEntry(TlsToolkitStandalone.NIFI_KEY);
+ if (entry == null) {
+ TlsConfig tlsConfig = getTlsConfig();
+ KeyPair keyPair = TlsHelper.generateKeyPair(tlsConfig.getKeyPairAlgorithm(), tlsConfig.getKeySize());
+ X509Certificate caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, tlsConfig.getDn(), tlsConfig.getSigningAlgorithm(), tlsConfig.getDays());
+ 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");
+ }
+
+ return (KeyStore.PrivateKeyEntry) entry;
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java
new file mode 100644
index 0000000000..52ea5db80f
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tls.manager;
+
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.PasswordUtil;
+import org.apache.nifi.util.StringUtils;
+import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class TlsClientManager extends BaseTlsManager {
+ private final TlsClientConfig tlsClientConfig;
+ private final KeyStore trustStore;
+ private final List> configurationWriters;
+ private final Set certificateAliases;
+ private File certificateAuthorityDirectory;
+
+ public TlsClientManager(TlsClientConfig tlsClientConfig) throws GeneralSecurityException, IOException {
+ this(tlsClientConfig, new PasswordUtil(), FileInputStream::new);
+ }
+
+ public TlsClientManager(TlsClientConfig tlsClientConfig, PasswordUtil passwordUtil, InputStreamFactory inputStreamFactory) throws GeneralSecurityException, IOException {
+ super(tlsClientConfig, passwordUtil, inputStreamFactory);
+ this.trustStore = loadKeystore(tlsClientConfig.getTrustStore(), tlsClientConfig.getTrustStoreType(), tlsClientConfig.getTrustStorePassword());
+ this.tlsClientConfig = tlsClientConfig;
+ this.configurationWriters = new ArrayList<>();
+ this.certificateAliases = new HashSet<>();
+ }
+
+ public void setCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
+ trustStore.setCertificateEntry(alias, cert);
+ certificateAliases.add(alias);
+ }
+
+ public void setCertificateAuthorityDirectory(File certificateAuthorityDirectory) {
+ this.certificateAuthorityDirectory = certificateAuthorityDirectory;
+ }
+
+ @Override
+ public void write(OutputStreamFactory outputStreamFactory) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
+ super.write(outputStreamFactory);
+
+ String trustStorePassword = tlsClientConfig.getTrustStorePassword();
+ if (StringUtils.isEmpty(trustStorePassword)) {
+ trustStorePassword = getPasswordUtil().generatePassword();
+ tlsClientConfig.setTrustStorePassword(trustStorePassword);
+ }
+
+ try (OutputStream outputStream = outputStreamFactory.create(new File(tlsClientConfig.getTrustStore()))) {
+ trustStore.store(outputStream, trustStorePassword.toCharArray());
+ }
+
+ for (ConfigurationWriter configurationWriter : configurationWriters) {
+ configurationWriter.write(tlsClientConfig);
+ }
+
+ if (certificateAuthorityDirectory != null) {
+ // Write out all trusted certificates from truststore
+ for (String alias : Collections.list(trustStore.aliases())) {
+ try {
+ KeyStore.Entry trustStoreEntry = trustStore.getEntry(alias, null);
+ if (trustStoreEntry instanceof KeyStore.TrustedCertificateEntry) {
+ Certificate trustedCertificate = ((KeyStore.TrustedCertificateEntry) trustStoreEntry).getTrustedCertificate();
+ try (OutputStream outputStream = outputStreamFactory.create(new File(certificateAuthorityDirectory, alias + ".pem"));
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
+ PemWriter pemWriter = new PemWriter(outputStreamWriter)) {
+ pemWriter.writeObject(new JcaMiscPEMGenerator(trustedCertificate));
+ }
+ }
+ } catch (UnrecoverableEntryException e) {
+ // Ignore, not a trusted cert
+ }
+ }
+ }
+ }
+
+ public void addClientConfigurationWriter(ConfigurationWriter configurationWriter) {
+ configurationWriters.add(configurationWriter);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/ConfigurationWriter.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/ConfigurationWriter.java
new file mode 100644
index 0000000000..e669d7e087
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/ConfigurationWriter.java
@@ -0,0 +1,24 @@
+/*
+ * 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.tls.manager.writer;
+
+import java.io.IOException;
+
+public interface ConfigurationWriter {
+ void write(T t) throws IOException;
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/JsonConfigurationWriter.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/JsonConfigurationWriter.java
new file mode 100644
index 0000000000..475553bf3b
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/JsonConfigurationWriter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.tls.manager.writer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class JsonConfigurationWriter implements ConfigurationWriter {
+ private final ObjectWriter objectWriter;
+ private final OutputStreamFactory outputStreamFactory;
+ private final File file;
+
+ public JsonConfigurationWriter(ObjectMapper objectMapper, OutputStreamFactory outputStreamFactory, File file) {
+ this.objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
+ this.outputStreamFactory = outputStreamFactory;
+ this.file = file;
+ }
+
+ @Override
+ public void write(T tlsConfig) throws IOException {
+ try (OutputStream stream = outputStreamFactory.create(file)) {
+ objectWriter.writeValue(stream, tlsConfig);
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/NifiPropertiesTlsClientConfigWriter.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/NifiPropertiesTlsClientConfigWriter.java
new file mode 100644
index 0000000000..5ee08f5b9a
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/writer/NifiPropertiesTlsClientConfigWriter.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.toolkit.tls.manager.writer;
+
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter;
+import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class NifiPropertiesTlsClientConfigWriter implements ConfigurationWriter {
+ private final NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
+ private final OutputStreamFactory outputStreamFactory;
+ private final File file;
+ private final String hostname;
+ private final String httpsPort;
+
+ public NifiPropertiesTlsClientConfigWriter(NiFiPropertiesWriterFactory niFiPropertiesWriterFactory, OutputStreamFactory outputStreamFactory, File file, String hostname, String httpsPort) {
+ this.niFiPropertiesWriterFactory = niFiPropertiesWriterFactory;
+ this.outputStreamFactory = outputStreamFactory;
+ this.file = file;
+ this.hostname = hostname;
+ this.httpsPort = httpsPort;
+ }
+
+ @Override
+ public void write(TlsClientConfig tlsClientConfig) throws IOException {
+ NiFiPropertiesWriter niFiPropertiesWriter = niFiPropertiesWriterFactory.create();
+ updateProperties(niFiPropertiesWriter, tlsClientConfig);
+ try (OutputStream stream = outputStreamFactory.create(file)) {
+ niFiPropertiesWriter.writeNiFiProperties(stream);
+ }
+ }
+
+ protected void updateProperties(NiFiPropertiesWriter niFiPropertiesWriter, TlsClientConfig tlsClientConfig) {
+ Path parentPath = Paths.get(file.getParentFile().getAbsolutePath());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE, parentPath.relativize(Paths.get(tlsClientConfig.getKeyStore())).toString());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsClientConfig.getKeyStoreType());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsClientConfig.getKeyStorePassword());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_KEY_PASSWD, tlsClientConfig.getKeyPassword());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE, parentPath.relativize(Paths.get(tlsClientConfig.getTrustStore())).toString());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsClientConfig.getTrustStoreType());
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsClientConfig.getTrustStorePassword());
+ if (!StringUtils.isEmpty(httpsPort)) {
+ if (!StringUtils.isEmpty(hostname)) {
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.WEB_HTTPS_HOST, hostname);
+ }
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.WEB_HTTPS_PORT, httpsPort);
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.WEB_HTTP_HOST, "");
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.WEB_HTTP_PORT, "");
+ niFiPropertiesWriter.setPropertyValue(NiFiProperties.SITE_TO_SITE_SECURE, "true");
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriter.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriter.java
new file mode 100644
index 0000000000..61cbc675d9
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriter.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.toolkit.tls.properties;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NiFiPropertiesWriter {
+ private final List lines;
+ private final Map updatedValues;
+
+ public NiFiPropertiesWriter(List lines) {
+ this.lines = new ArrayList<>(lines);
+ this.updatedValues = new HashMap<>();
+ }
+
+ public void setPropertyValue(String key, String value) {
+ updatedValues.put(key, value);
+ }
+
+ public void writeNiFiProperties(OutputStream outputStream) throws IOException {
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
+ Map remainingValues = new HashMap<>(updatedValues);
+ Set keysSeen = new HashSet<>();
+ for (String line : lines) {
+ String key = line.split("=")[0].trim();
+ boolean outputLine = true;
+ if (!key.isEmpty() && !key.startsWith("#")) {
+ if (!keysSeen.add(key)) {
+ throw new IOException("Found key more than once in nifi.properties: " + key);
+ }
+ String value = remainingValues.remove(key);
+ if (value != null) {
+ writer.write(key);
+ writer.write("=");
+ writer.write(value);
+ outputLine = false;
+ }
+ }
+ if (outputLine) {
+ writer.write(line);
+ }
+ writer.newLine();
+ }
+ for (Map.Entry keyValueEntry : remainingValues.entrySet()) {
+ writer.write(keyValueEntry.getKey());
+ writer.write("=");
+ writer.write(keyValueEntry.getValue());
+ writer.newLine();
+ }
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriterFactory.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriterFactory.java
new file mode 100644
index 0000000000..cf8212c9c7
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/properties/NiFiPropertiesWriterFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.tls.properties;
+
+import org.apache.nifi.toolkit.tls.TlsToolkitMain;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class NiFiPropertiesWriterFactory {
+ private final List lines;
+
+ public NiFiPropertiesWriterFactory() throws IOException {
+ this(TlsToolkitMain.class.getClassLoader().getResourceAsStream("conf/nifi.properties"));
+ }
+
+ public NiFiPropertiesWriterFactory(InputStream inputStream) throws IOException {
+ List lines = new ArrayList<>();
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ lines.add(line.trim());
+ }
+ }
+ this.lines = Collections.unmodifiableList(lines);
+ }
+
+ public NiFiPropertiesWriter create() {
+ return new NiFiPropertiesWriter(lines);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/BaseCertificateAuthorityCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/BaseCertificateAuthorityCommandLine.java
new file mode 100644
index 0000000000..97fa45b5f2
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/BaseCertificateAuthorityCommandLine.java
@@ -0,0 +1,95 @@
+/*
+ * 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.tls.service;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.util.StringUtils;
+
+import java.io.File;
+
+public abstract class BaseCertificateAuthorityCommandLine extends BaseCommandLine {
+ public static final String TOKEN_ARG = "token";
+ public static final String CONFIG_JSON_ARG = "configJson";
+ public static final String USE_CONFIG_JSON_ARG = "useConfigJson";
+ public static final String PORT_ARG = "PORT";
+
+ public static final String DEFAULT_CONFIG_JSON = new File("config.json").getPath();
+
+ private String token;
+ private String configJson;
+ private boolean onlyUseConfigJson;
+ private int port;
+ private String dn;
+
+ public BaseCertificateAuthorityCommandLine(String header) {
+ super(header);
+ addOptionWithArg("t", TOKEN_ARG, getTokenDescription());
+ addOptionWithArg("f", CONFIG_JSON_ARG, "The place to write configuration info", DEFAULT_CONFIG_JSON);
+ addOptionNoArg("F", USE_CONFIG_JSON_ARG, "Flag specifying that all configuration is read from " + CONFIG_JSON_ARG + " to facilitate automated use (otherwise "
+ + CONFIG_JSON_ARG + " will only be written to.");
+ addOptionWithArg("p", PORT_ARG, getPortDescription(), TlsConfig.DEFAULT_PORT);
+ addOptionWithArg("D", DN_ARG, getDnDescription(), TlsConfig.calcDefaultDn(getDnHostname()));
+ }
+
+ protected abstract String getTokenDescription();
+
+ protected abstract String getDnDescription();
+
+ protected abstract String getPortDescription();
+
+ protected abstract String getDnHostname();
+
+ @Override
+ protected CommandLine doParse(String[] args) throws CommandLineParseException {
+ CommandLine commandLine = super.doParse(args);
+
+ token = commandLine.getOptionValue(TOKEN_ARG);
+ onlyUseConfigJson = commandLine.hasOption(USE_CONFIG_JSON_ARG);
+ if (StringUtils.isEmpty(token) && !onlyUseConfigJson) {
+ printUsageAndThrow(TOKEN_ARG + " argument must not be empty unless " + USE_CONFIG_JSON_ARG + " set", ExitCode.ERROR_TOKEN_ARG_EMPTY);
+ }
+ configJson = commandLine.getOptionValue(CONFIG_JSON_ARG, DEFAULT_CONFIG_JSON);
+ port = getIntValue(commandLine, PORT_ARG, TlsConfig.DEFAULT_PORT);
+ dn = commandLine.getOptionValue(DN_ARG, TlsConfig.calcDefaultDn(getDnHostname()));
+ return commandLine;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public String getConfigJson() {
+ return configJson;
+ }
+
+ public boolean onlyUseConfigJson() {
+ return onlyUseConfigJson;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public String getDn() {
+ return dn;
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClient.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClient.java
new file mode 100644
index 0000000000..c0e2301aa9
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClient.java
@@ -0,0 +1,90 @@
+/*
+ * 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.tls.service.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.manager.TlsClientManager;
+import org.apache.nifi.toolkit.tls.manager.writer.JsonConfigurationWriter;
+import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
+import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.apache.nifi.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+/**
+ * Client that will generate a CSR and submit to a CA, writing out the results to a keystore and truststore along with a config file if successful
+ */
+public class TlsCertificateAuthorityClient {
+ private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClient.class);
+ private final OutputStreamFactory outputStreamFactory;
+
+ public TlsCertificateAuthorityClient() {
+ this(FileOutputStream::new);
+ }
+
+ public TlsCertificateAuthorityClient(OutputStreamFactory outputStreamFactory) {
+ this.outputStreamFactory = outputStreamFactory;
+ }
+
+ public void generateCertificateAndGetItSigned(TlsClientConfig tlsClientConfig, String certificateDirectory, String configJson, boolean differentKeyAndKeyStorePassword) throws Exception {
+ TlsClientManager tlsClientManager;
+ try {
+ tlsClientManager = new TlsClientManager(tlsClientConfig);
+ } catch (IOException e) {
+ logger.error("Unable to open existing keystore, it can be reused by specifiying both " + BaseCertificateAuthorityCommandLine.CONFIG_JSON_ARG + " and " +
+ BaseCertificateAuthorityCommandLine.USE_CONFIG_JSON_ARG);
+ throw e;
+ }
+ tlsClientManager.setDifferentKeyAndKeyStorePassword(differentKeyAndKeyStorePassword);
+
+ if (!StringUtils.isEmpty(certificateDirectory)) {
+ tlsClientManager.setCertificateAuthorityDirectory(new File(certificateDirectory));
+ }
+
+ if (!StringUtils.isEmpty(configJson)) {
+ tlsClientManager.addClientConfigurationWriter(new JsonConfigurationWriter<>(new ObjectMapper(), outputStreamFactory, new File(configJson)));
+ }
+
+ if (tlsClientManager.getEntry(TlsToolkitStandalone.NIFI_KEY) == null) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Requesting new certificate from " + tlsClientConfig.getCaHostname() + ":" + tlsClientConfig.getPort());
+ }
+ KeyPair keyPair = TlsHelper.generateKeyPair(tlsClientConfig.getKeyPairAlgorithm(), tlsClientConfig.getKeySize());
+
+ X509Certificate[] certificates = tlsClientConfig.createCertificateSigningRequestPerformer().perform(keyPair);
+
+ tlsClientManager.addPrivateKeyToKeyStore(keyPair, TlsToolkitStandalone.NIFI_KEY, certificates);
+ tlsClientManager.setCertificateEntry(TlsToolkitStandalone.NIFI_CERT, certificates[certificates.length - 1]);
+ } else {
+ if (logger.isInfoEnabled()) {
+ logger.info("Already had entry for " + TlsToolkitStandalone.NIFI_KEY + " not requesting new certificate.");
+ }
+ }
+
+ tlsClientManager.write(outputStreamFactory);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
new file mode 100644
index 0000000000..dd432c363e
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLine.java
@@ -0,0 +1,141 @@
+/*
+ * 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.tls.service.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.cli.CommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAuthorityCommandLine {
+ public static final String DESCRIPTION = "Generates a private key and gets it signed by the certificate authority.";
+ public static final String CERTIFICATE_DIRECTORY = "certificateDirectory";
+ public static final String DEFAULT_CERTIFICATE_DIRECTORY = ".";
+
+ private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClientCommandLine.class);
+ private final InputStreamFactory inputStreamFactory;
+
+ private String certificateDirectory;
+
+ public TlsCertificateAuthorityClientCommandLine() {
+ this(FileInputStream::new);
+ }
+
+ public TlsCertificateAuthorityClientCommandLine(InputStreamFactory inputStreamFactory) {
+ super(DESCRIPTION);
+ this.inputStreamFactory = inputStreamFactory;
+ addOptionWithArg("C", CERTIFICATE_DIRECTORY, "The file to write the CA certificate to", DEFAULT_CERTIFICATE_DIRECTORY);
+ }
+
+ public static void main(String[] args) throws Exception {
+ TlsHelper.addBouncyCastleProvider();
+ TlsCertificateAuthorityClientCommandLine tlsCertificateAuthorityClientCommandLine = new TlsCertificateAuthorityClientCommandLine();
+ try {
+ tlsCertificateAuthorityClientCommandLine.parse(args);
+ } catch (CommandLineParseException e) {
+ System.exit(e.getExitCode());
+ }
+ new TlsCertificateAuthorityClient().generateCertificateAndGetItSigned(tlsCertificateAuthorityClientCommandLine.createClientConfig(),
+ tlsCertificateAuthorityClientCommandLine.getCertificateDirectory(), tlsCertificateAuthorityClientCommandLine.getConfigJson(),
+ tlsCertificateAuthorityClientCommandLine.differentPasswordForKeyAndKeystore());
+ System.exit(ExitCode.SUCCESS.ordinal());
+ }
+
+ @Override
+ protected boolean shouldAddDaysArg() {
+ return false;
+ }
+
+ @Override
+ protected boolean shouldAddSigningAlgorithmArg() {
+ return false;
+ }
+
+ @Override
+ protected String getTokenDescription() {
+ return "The token to use to prevent MITM (required and must be same as one used by CA)";
+ }
+
+ @Override
+ protected String getDnDescription() {
+ return "The dn to use for the client certificate";
+ }
+
+ @Override
+ protected String getPortDescription() {
+ return "The port to use to communicate with the Certificate Authority";
+ }
+
+ @Override
+ protected String getDnHostname() {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ logger.warn("Unable to determine hostname", e);
+ return "localhost";
+ }
+ }
+
+ @Override
+ protected CommandLine doParse(String[] args) throws CommandLineParseException {
+ CommandLine commandLine = super.doParse(args);
+ certificateDirectory = commandLine.getOptionValue(CERTIFICATE_DIRECTORY, DEFAULT_CERTIFICATE_DIRECTORY);
+ return commandLine;
+ }
+
+ public String getCertificateDirectory() {
+ return certificateDirectory;
+ }
+
+ public TlsClientConfig createClientConfig() throws IOException {
+ if (onlyUseConfigJson()) {
+ try (InputStream inputStream = inputStreamFactory.create(new File(getConfigJson()))) {
+ TlsClientConfig tlsClientConfig = new ObjectMapper().readValue(inputStream, TlsClientConfig.class);
+ tlsClientConfig.initDefaults();
+ return tlsClientConfig;
+ }
+ } else {
+ TlsClientConfig tlsClientConfig = new TlsClientConfig();
+ tlsClientConfig.setCaHostname(getCertificateAuthorityHostname());
+ tlsClientConfig.setDn(getDn());
+ tlsClientConfig.setToken(getToken());
+ tlsClientConfig.setPort(getPort());
+ tlsClientConfig.setKeyStore(KEYSTORE + getKeyStoreType().toLowerCase());
+ tlsClientConfig.setKeyStoreType(getKeyStoreType());
+ tlsClientConfig.setTrustStore(TRUSTSTORE + getKeyStoreType().toLowerCase());
+ tlsClientConfig.setTrustStoreType(getKeyStoreType());
+ tlsClientConfig.setKeySize(getKeySize());
+ tlsClientConfig.setKeyPairAlgorithm(getKeyAlgorithm());
+ tlsClientConfig.setSigningAlgorithm(getSigningAlgorithm());
+ return tlsClientConfig;
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientSocketFactory.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientSocketFactory.java
new file mode 100644
index 0000000000..e5000c8e40
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientSocketFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tls.service.client;
+
+import org.apache.http.HttpHost;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.protocol.HttpContext;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Socket Factory validates that it is talking to a RootCa claiming to have the given hostname. It adds the certificate
+ * to a list for later validation against the payload's hmac
+ */
+public class TlsCertificateAuthorityClientSocketFactory extends SSLConnectionSocketFactory {
+ private final String caHostname;
+ private final List certificates;
+
+ public TlsCertificateAuthorityClientSocketFactory(SSLContext sslContext, String caHostname, List certificates) {
+ super(sslContext);
+ this.caHostname = caHostname;
+ this.certificates = certificates;
+ }
+
+ @Override
+ public synchronized Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
+ InetSocketAddress localAddress, HttpContext context) throws IOException {
+ Socket result = super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
+ if (!SSLSocket.class.isInstance(result)) {
+ throw new IOException("Expected tls socket");
+ }
+ SSLSocket sslSocket = (SSLSocket) result;
+ java.security.cert.Certificate[] peerCertificateChain = sslSocket.getSession().getPeerCertificates();
+ if (peerCertificateChain.length != 1) {
+ throw new IOException("Expected root ca cert");
+ }
+ if (!X509Certificate.class.isInstance(peerCertificateChain[0])) {
+ throw new IOException("Expected root ca cert in X509 format");
+ }
+ String cn;
+ try {
+ X509Certificate certificate = (X509Certificate) peerCertificateChain[0];
+ cn = IETFUtils.valueToString(new JcaX509CertificateHolder(certificate).getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue());
+ certificates.add(certificate);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ if (!caHostname.equals(cn)) {
+ throw new IOException("Expected cn of " + caHostname + " but got " + cn);
+ }
+ return result;
+ }
+}
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
new file mode 100644
index 0000000000..2c65964519
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java
@@ -0,0 +1,156 @@
+/*
+ * 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.tls.service.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+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.toolkit.tls.configuration.TlsClientConfig;
+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.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.eclipse.jetty.server.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class TlsCertificateSigningRequestPerformer {
+ public static final String RECEIVED_RESPONSE_CODE = "Received response code ";
+ public static final String EXPECTED_ONE_CERTIFICATE = "Expected one certificate";
+ public static final String EXPECTED_RESPONSE_TO_CONTAIN_HMAC = "Expected response to contain hmac";
+ public static final String UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE = "Unexpected hmac received, possible man in the middle";
+ public static final String EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE = "Expected response to contain certificate";
+ private final Logger logger = LoggerFactory.getLogger(TlsCertificateSigningRequestPerformer.class);
+ private final Supplier httpClientBuilderSupplier;
+ private final String caHostname;
+ private final String dn;
+ private final String token;
+ private final int port;
+ private final ObjectMapper objectMapper;
+ private final String signingAlgorithm;
+
+ public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
+ this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
+ }
+
+ protected TlsCertificateSigningRequestPerformer(Supplier httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
+ this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
+ }
+
+ private TlsCertificateSigningRequestPerformer(Supplier httpClientBuilderSupplier, String caHostname, String dn, String token, int port, String signingAlgorithm) {
+ this.httpClientBuilderSupplier = httpClientBuilderSupplier;
+ this.caHostname = caHostname;
+ this.dn = dn;
+ this.token = token;
+ this.port = port;
+ this.objectMapper = new ObjectMapper();
+ this.signingAlgorithm = signingAlgorithm;
+ }
+
+ /**
+ * Submits a CSR to the Certificate authority, checks the resulting hmac, and returns the chain if everything succeeds
+ *
+ * @param keyPair the keypair to generate the csr for
+ * @throws IOException if there is a problem during the process
+ * @returnd the resulting certificate chain
+ */
+ public X509Certificate[] perform(KeyPair keyPair) throws IOException {
+ try {
+ List certificates = new ArrayList<>();
+
+ HttpClientBuilder httpClientBuilder = httpClientBuilderSupplier.get();
+ SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
+ sslContextBuilder.useProtocol("TLSv1.2");
+
+ // We will be validating that we are talking to the correct host once we get the response's hmac of the token and public key of the ca
+ sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
+ httpClientBuilder.setSSLSocketFactory(new TlsCertificateAuthorityClientSocketFactory(sslContextBuilder.build(), caHostname, certificates));
+
+ String jsonResponseString;
+ int responseCode;
+ try (CloseableHttpClient client = httpClientBuilder.build()) {
+ JcaPKCS10CertificationRequest request = TlsHelper.generateCertificationRequest(dn, keyPair, signingAlgorithm);
+ TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(TlsHelper.calculateHMac(token, request.getPublicKey()),
+ TlsHelper.pemEncodeJcaObject(request));
+
+ HttpPost httpPost = new HttpPost();
+ httpPost.setEntity(new ByteArrayEntity(objectMapper.writeValueAsBytes(tlsCertificateAuthorityRequest)));
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Requesting certificate with dn " + dn + " from " + caHostname + ":" + port);
+ }
+ try (CloseableHttpResponse response = client.execute(new HttpHost(caHostname, port, "https"), httpPost)) {
+ jsonResponseString = IOUtils.toString(new BoundedInputStream(response.getEntity().getContent(), 1024 * 1024), StandardCharsets.UTF_8);
+ responseCode = response.getStatusLine().getStatusCode();
+ }
+ }
+
+ if (responseCode != Response.SC_OK) {
+ throw new IOException(RECEIVED_RESPONSE_CODE + responseCode + " with payload " + jsonResponseString);
+ }
+
+ if (certificates.size() != 1) {
+ throw new IOException(EXPECTED_ONE_CERTIFICATE);
+ }
+
+ TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse = objectMapper.readValue(jsonResponseString, TlsCertificateAuthorityResponse.class);
+ if (!tlsCertificateAuthorityResponse.hasHmac()) {
+ throw new IOException(EXPECTED_RESPONSE_TO_CONTAIN_HMAC);
+ }
+
+ X509Certificate caCertificate = certificates.get(0);
+ byte[] expectedHmac = TlsHelper.calculateHMac(token, caCertificate.getPublicKey());
+
+ if (!MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityResponse.getHmac())) {
+ throw new IOException(UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE);
+ }
+
+ if (!tlsCertificateAuthorityResponse.hasCertificate()) {
+ throw new IOException(EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE);
+ }
+ X509Certificate x509Certificate = TlsHelper.parseCertificate(tlsCertificateAuthorityResponse.getPemEncodedCertificate());
+ x509Certificate.verify(caCertificate.getPublicKey());
+ if (logger.isInfoEnabled()) {
+ logger.info("Got certificate with dn " + x509Certificate.getSubjectDN());
+ }
+ return new X509Certificate[]{x509Certificate, caCertificate};
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityRequest.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityRequest.java
new file mode 100644
index 0000000000..ca6f0ce023
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityRequest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tls.service.dto;
+
+import org.apache.nifi.util.StringUtils;
+
+public class TlsCertificateAuthorityRequest {
+ private byte[] hmac;
+ private String csr;
+
+ public TlsCertificateAuthorityRequest() {
+ }
+
+ public TlsCertificateAuthorityRequest(byte[] hmac, String csr) {
+ this.hmac = hmac;
+ this.csr = csr;
+ }
+
+ public byte[] getHmac() {
+ return hmac;
+ }
+
+ public void setHmac(byte[] hmac) {
+ this.hmac = hmac;
+ }
+
+ public boolean hasHmac() {
+ return hmac != null && hmac.length > 0;
+ }
+
+ public String getCsr() {
+ return csr;
+ }
+
+ public void setCsr(String csr) {
+ this.csr = csr;
+ }
+
+ public boolean hasCsr() {
+ return !StringUtils.isEmpty(csr);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityResponse.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityResponse.java
new file mode 100644
index 0000000000..3a7c238249
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/dto/TlsCertificateAuthorityResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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.tls.service.dto;
+
+import org.apache.nifi.util.StringUtils;
+
+public class TlsCertificateAuthorityResponse {
+ private byte[] hmac;
+ private String pemEncodedCertificate;
+ private String error;
+
+ public TlsCertificateAuthorityResponse() {
+ }
+
+ public TlsCertificateAuthorityResponse(byte[] hmac, String pemEncodedCertificate) {
+ this.hmac = hmac;
+ this.pemEncodedCertificate = pemEncodedCertificate;
+ }
+
+ public TlsCertificateAuthorityResponse(String error) {
+ this.error = error;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public byte[] getHmac() {
+ return hmac;
+ }
+
+ public void setHmac(byte[] hmac) {
+ this.hmac = hmac;
+ }
+
+ public String getPemEncodedCertificate() {
+ return pemEncodedCertificate;
+ }
+
+ public void setPemEncodedCertificate(String pemEncodedCertificate) {
+ this.pemEncodedCertificate = pemEncodedCertificate;
+ }
+
+ public boolean hasCertificate() {
+ return !StringUtils.isEmpty(pemEncodedCertificate);
+ }
+
+ public boolean hasHmac() {
+ return hmac != null && hmac.length > 0;
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityService.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityService.java
new file mode 100644
index 0000000000..d21ea36f39
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityService.java
@@ -0,0 +1,126 @@
+/*
+ * 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.tls.service.server;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager;
+import org.apache.nifi.toolkit.tls.manager.writer.JsonConfigurationWriter;
+import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+/**
+ * Starts a Jetty server that will either load an existing CA or create one and use it to sign CSRs
+ */
+public class TlsCertificateAuthorityService {
+ private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityService.class);
+ private final OutputStreamFactory outputStreamFactory;
+ private Server server;
+
+ public TlsCertificateAuthorityService() {
+ this(FileOutputStream::new);
+ }
+
+ public TlsCertificateAuthorityService(OutputStreamFactory outputStreamFactory) {
+ this.outputStreamFactory = outputStreamFactory;
+ }
+
+ private static Server createServer(Handler handler, int port, KeyStore keyStore, String keyPassword) throws Exception {
+ Server server = new Server();
+
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setIncludeProtocols("TLSv1.2");
+ sslContextFactory.setKeyStore(keyStore);
+ sslContextFactory.setKeyManagerPassword(keyPassword);
+
+ HttpConfiguration httpsConfig = new HttpConfiguration();
+ httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+ ServerConnector sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConfig));
+ sslConnector.setPort(port);
+
+ server.addConnector(sslConnector);
+ server.setHandler(handler);
+
+ return server;
+ }
+
+ public synchronized void start(TlsConfig tlsConfig, String configJson, boolean differentPasswordsForKeyAndKeystore) throws Exception {
+ if (server != null) {
+ throw new IllegalStateException("Server already started");
+ }
+ ObjectMapper objectMapper = new ObjectMapper();
+ TlsCertificateAuthorityManager tlsManager;
+ try {
+ tlsManager = new TlsCertificateAuthorityManager(tlsConfig);
+ tlsManager.setDifferentKeyAndKeyStorePassword(differentPasswordsForKeyAndKeystore);
+ } catch (IOException e) {
+ logger.error("Unable to open existing keystore, it can be reused by specifiying both " + BaseCertificateAuthorityCommandLine.CONFIG_JSON_ARG + " and " +
+ BaseCertificateAuthorityCommandLine.USE_CONFIG_JSON_ARG);
+ throw e;
+ }
+ tlsManager.addConfigurationWriter(new JsonConfigurationWriter<>(objectMapper, outputStreamFactory, new File(configJson)));
+
+ KeyStore.PrivateKeyEntry privateKeyEntry = tlsManager.getOrGenerateCertificateAuthority();
+ KeyPair keyPair = new KeyPair(privateKeyEntry.getCertificate().getPublicKey(), privateKeyEntry.getPrivateKey());
+ Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
+ if (certificateChain.length != 1) {
+ throw new IOException("Expected root ca cert to be only certificate in chain");
+ }
+ Certificate certificate = certificateChain[0];
+ X509Certificate caCert;
+ if (certificate instanceof X509Certificate) {
+ caCert = (X509Certificate) certificate;
+ } else {
+ throw new IOException("Expected " + X509Certificate.class + " as root ca cert");
+ }
+ tlsManager.write(outputStreamFactory);
+ String signingAlgorithm = tlsConfig.getSigningAlgorithm();
+ int days = tlsConfig.getDays();
+ server = createServer(new TlsCertificateAuthorityServiceHandler(signingAlgorithm, days, tlsConfig.getToken(), caCert, keyPair, objectMapper), tlsConfig.getPort(), tlsManager.getKeyStore(),
+ tlsConfig.getKeyPassword());
+ server.start();
+ }
+
+ public synchronized void shutdown() throws Exception {
+ if (server == null) {
+ throw new IllegalStateException("Server already shutdown");
+ }
+ server.stop();
+ server.join();
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLine.java
new file mode 100644
index 0000000000..2b1db39cd8
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLine.java
@@ -0,0 +1,109 @@
+/*
+ * 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.tls.service.server;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.service.BaseCertificateAuthorityCommandLine;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.apache.nifi.util.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class TlsCertificateAuthorityServiceCommandLine extends BaseCertificateAuthorityCommandLine {
+ public static final String DESCRIPTION = "Acts as a Certificate Authority that can be used by clients to get Certificates";
+ public static final String NIFI_CA_KEYSTORE = "nifi-ca-" + KEYSTORE;
+
+ private final InputStreamFactory inputStreamFactory;
+
+ public TlsCertificateAuthorityServiceCommandLine() {
+ this(FileInputStream::new);
+ }
+
+ public TlsCertificateAuthorityServiceCommandLine(InputStreamFactory inputStreamFactory) {
+ super(DESCRIPTION);
+ this.inputStreamFactory = inputStreamFactory;
+ }
+
+ public static void main(String[] args) throws Exception {
+ TlsHelper.addBouncyCastleProvider();
+ TlsCertificateAuthorityServiceCommandLine tlsCertificateAuthorityServiceCommandLine = new TlsCertificateAuthorityServiceCommandLine();
+ try {
+ tlsCertificateAuthorityServiceCommandLine.parse(args);
+ } catch (CommandLineParseException e) {
+ System.exit(e.getExitCode());
+ }
+ TlsCertificateAuthorityService tlsCertificateAuthorityService = new TlsCertificateAuthorityService();
+ tlsCertificateAuthorityService.start(tlsCertificateAuthorityServiceCommandLine.createConfig(), tlsCertificateAuthorityServiceCommandLine.getConfigJson(),
+ tlsCertificateAuthorityServiceCommandLine.differentPasswordForKeyAndKeystore());
+ System.out.println("Server Started");
+ System.out.flush();
+ }
+
+ public TlsConfig createConfig() throws IOException {
+ if (onlyUseConfigJson()) {
+ try (InputStream inputStream = inputStreamFactory.create(new File(getConfigJson()))) {
+ TlsConfig tlsConfig = new ObjectMapper().readValue(inputStream, TlsConfig.class);
+ tlsConfig.initDefaults();
+ return tlsConfig;
+ }
+ } else {
+ TlsConfig tlsConfig = new TlsConfig();
+ tlsConfig.setCaHostname(getCertificateAuthorityHostname());
+ tlsConfig.setDn(getDn());
+ tlsConfig.setToken(getToken());
+ tlsConfig.setPort(getPort());
+ tlsConfig.setKeyStore(NIFI_CA_KEYSTORE + getKeyStoreType().toLowerCase());
+ tlsConfig.setKeyStoreType(getKeyStoreType());
+ tlsConfig.setKeySize(getKeySize());
+ tlsConfig.setKeyPairAlgorithm(getKeyAlgorithm());
+ tlsConfig.setSigningAlgorithm(getSigningAlgorithm());
+ tlsConfig.setDays(getDays());
+ return tlsConfig;
+ }
+ }
+
+ @Override
+ protected String getTokenDescription() {
+ return "The token to use to prevent MITM (required and must be same as one used by clients)";
+ }
+
+ @Override
+ protected String getDnDescription() {
+ return "The dn to use for the CA certificate";
+ }
+
+ @Override
+ protected String getPortDescription() {
+ return "The port for the Certificate Authority to listen on";
+ }
+
+ @Override
+ protected String getDnHostname() {
+ String dnHostname = getCertificateAuthorityHostname();
+ if (StringUtils.isEmpty(dnHostname)) {
+ return "YOUR_CA_HOSTNAME";
+ }
+ return dnHostname;
+ }
+}
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
new file mode 100644
index 0000000000..fb83498421
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java
@@ -0,0 +1,117 @@
+/*
+ * 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.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.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.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
+
+/**
+ * Jetty service handler that validates the hmac of a CSR and issues a certificate if it checks out
+ */
+public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
+ public static final String CSR_FIELD_MUST_BE_SET = "csr field must be set";
+ public static final String HMAC_FIELD_MUST_BE_SET = "hmac field must be set";
+ public static final String FORBIDDEN = "forbidden";
+ private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityServiceHandler.class);
+ private final String signingAlgorithm;
+ private final int days;
+ private final String token;
+ private final X509Certificate caCert;
+ private final KeyPair keyPair;
+ private final ObjectMapper objectMapper;
+
+ public TlsCertificateAuthorityServiceHandler(String signingAlgorithm, int days, String token, X509Certificate caCert, KeyPair keyPair, ObjectMapper objectMapper) {
+ this.signingAlgorithm = signingAlgorithm;
+ this.days = days;
+ this.token = token;
+ this.caCert = caCert;
+ this.keyPair = keyPair;
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ try {
+ TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(new BoundedReader(request.getReader(), 1024 * 1024), TlsCertificateAuthorityRequest.class);
+
+ if (!tlsCertificateAuthorityRequest.hasHmac()) {
+ writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(HMAC_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (!tlsCertificateAuthorityRequest.hasCsr()) {
+ writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(CSR_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
+ return;
+ }
+
+ JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
+ byte[] expectedHmac = TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey());
+
+ if (MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityRequest.getHmac())) {
+ X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(jcaPKCS10CertificationRequest.getSubject().toString(),
+ jcaPKCS10CertificationRequest.getPublicKey(), caCert, keyPair, signingAlgorithm, days);
+ 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");
+ } finally {
+ baseRequest.setHandled(true);
+ }
+ }
+
+ private void writeResponse(ObjectMapper objectMapper, HttpServletRequest request, HttpServletResponse response, TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse,
+ int responseCode) throws IOException {
+ if (logger.isInfoEnabled()) {
+ logger.info(new StringBuilder("Returning code:").append(responseCode).append(" payload ").append(objectMapper.writeValueAsString(tlsCertificateAuthorityResponse))
+ .append(" to ").append(request.getRemoteHost()).toString());
+ }
+ if (responseCode == Response.SC_OK) {
+ objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
+ response.setStatus(responseCode);
+ } else {
+ response.setStatus(responseCode);
+ response.setContentType("application/json");
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
+ }
+ }
+}
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
new file mode 100644
index 0000000000..cef1bc3962
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java
@@ -0,0 +1,136 @@
+/*
+ * 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.tls.standalone;
+
+import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager;
+import org.apache.nifi.toolkit.tls.manager.TlsClientManager;
+import org.apache.nifi.toolkit.tls.manager.writer.NifiPropertiesTlsClientConfigWriter;
+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.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
+import org.bouncycastle.util.io.pem.PemWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+public class TlsToolkitStandalone {
+ public static final String NIFI_KEY = "nifi-key";
+ public static final String NIFI_CERT = "nifi-cert";
+ public static final String NIFI_PROPERTIES = "nifi.properties";
+
+ private final Logger logger = LoggerFactory.getLogger(TlsToolkitStandalone.class);
+ private final OutputStreamFactory outputStreamFactory;
+
+ public TlsToolkitStandalone() {
+ this(FileOutputStream::new);
+ }
+
+ public TlsToolkitStandalone(OutputStreamFactory outputStreamFactory) {
+ this.outputStreamFactory = outputStreamFactory;
+ }
+
+ public void createNifiKeystoresAndTrustStores(File baseDir, TlsConfig tlsConfig, NiFiPropertiesWriterFactory niFiPropertiesWriterFactory, List hostnames, List keyStorePasswords,
+ List keyPasswords, List trustStorePasswords, String httpsPort) throws GeneralSecurityException, IOException {
+ String signingAlgorithm = tlsConfig.getSigningAlgorithm();
+ int days = tlsConfig.getDays();
+ String keyPairAlgorithm = tlsConfig.getKeyPairAlgorithm();
+ int keySize = tlsConfig.getKeySize();
+ TlsCertificateAuthorityManager tlsCertificateAuthorityManager = new TlsCertificateAuthorityManager(tlsConfig);
+ KeyStore.PrivateKeyEntry privateKeyEntry = tlsCertificateAuthorityManager.getOrGenerateCertificateAuthority();
+ X509Certificate certificate = (X509Certificate) privateKeyEntry.getCertificateChain()[0];
+ KeyPair caKeyPair = new KeyPair(certificate.getPublicKey(), privateKeyEntry.getPrivateKey());
+
+ if (!baseDir.exists() && !baseDir.mkdirs()) {
+ throw new IOException(baseDir + " doesn't exist and unable to create it.");
+ }
+
+ if (!baseDir.isDirectory()) {
+ throw new IOException("Expected directory to output to");
+ }
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Running standalone certificate generation with output directory " + baseDir + " and hostnames " + hostnames);
+ }
+
+ File nifiCert = new File(baseDir, NIFI_CERT + ".pem");
+ if (nifiCert.exists()) {
+ throw new IOException(nifiCert.getAbsolutePath() + " exists already.");
+ }
+
+ File nifiKey = new File(baseDir, NIFI_KEY + ".key");
+ if (nifiKey.exists()) {
+ throw new IOException(nifiKey.getAbsolutePath() + " exists already.");
+ }
+
+ for (String hostname : hostnames) {
+ File hostDirectory = new File(baseDir, hostname);
+ if (hostDirectory.exists()) {
+ throw new IOException("Output destination for host " + hostname + " (" + hostDirectory.getAbsolutePath() + ") exists already.");
+ }
+ }
+
+ try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiCert)))) {
+ pemWriter.writeObject(new JcaMiscPEMGenerator(certificate));
+ }
+
+ try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiKey)))) {
+ pemWriter.writeObject(new JcaMiscPEMGenerator(caKeyPair));
+ }
+
+ for (int i = 0; i < hostnames.size(); i++) {
+ String hostname = hostnames.get(i);
+ File hostDir = new File(baseDir, hostname);
+
+ if (!hostDir.mkdirs()) {
+ throw new IOException("Unable to make directory: " + hostDir.getAbsolutePath());
+ }
+
+ TlsClientConfig tlsClientConfig = new TlsClientConfig(tlsConfig);
+ tlsClientConfig.setKeyStore(new File(hostDir, "keystore." + tlsClientConfig.getKeyStoreType().toLowerCase()).getAbsolutePath());
+ tlsClientConfig.setKeyStorePassword(keyStorePasswords.get(i));
+ tlsClientConfig.setKeyPassword(keyPasswords.get(i));
+ tlsClientConfig.setTrustStore(new File(hostDir, "truststore." + tlsClientConfig.getTrustStoreType().toLowerCase()).getAbsolutePath());
+ tlsClientConfig.setTrustStorePassword(trustStorePasswords.get(i));
+ TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig);
+ KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
+ tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, CertificateUtils.generateIssuedCertificate(TlsConfig.calcDefaultDn(hostname),
+ keyPair.getPublic(), certificate, caKeyPair, signingAlgorithm, days), certificate);
+ tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
+ tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, outputStreamFactory, new File(hostDir, "nifi.properties"),
+ hostname, httpsPort));
+ tlsClientManager.write(outputStreamFactory);
+ }
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Successfully generated TLS configuration");
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java
new file mode 100644
index 0000000000..7faabde31f
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java
@@ -0,0 +1,183 @@
+/*
+ * 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.tls.standalone;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
+import org.apache.nifi.toolkit.tls.util.PasswordUtil;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.apache.nifi.util.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class TlsToolkitStandaloneCommandLine extends BaseCommandLine {
+ public static final String OUTPUT_DIRECTORY_ARG = "outputDirectory";
+ public static final String NIFI_PROPERTIES_FILE_ARG = "nifiPropertiesFile";
+ public static final String KEY_STORE_PASSWORD_ARG = "keyStorePassword";
+ public static final String TRUST_STORE_PASSWORD_ARG = "trustStorePassword";
+ public static final String KEY_PASSWORD_ARG = "keyPassword";
+ public static final String HOSTNAMES_ARG = "hostnames";
+ public static final String HTTPS_PORT_ARG = "httpsPort";
+
+ public static final String DEFAULT_OUTPUT_DIRECTORY = new File(".").getPath();
+
+ public static final String DESCRIPTION = "Creates certificates and config files for nifi cluster.";
+
+ private final PasswordUtil passwordUtil;
+ private File baseDir;
+ private List hostnames;
+ private String httpsPort;
+ private NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
+ private List keyStorePasswords;
+ private List keyPasswords;
+ private List trustStorePasswords;
+
+ public TlsToolkitStandaloneCommandLine() {
+ this(new PasswordUtil());
+ }
+
+ protected TlsToolkitStandaloneCommandLine(PasswordUtil passwordUtil) {
+ super(DESCRIPTION);
+ this.passwordUtil = passwordUtil;
+ addOptionWithArg("o", OUTPUT_DIRECTORY_ARG, "The directory to output keystores, truststore, config files.", DEFAULT_OUTPUT_DIRECTORY);
+ addOptionWithArg("n", HOSTNAMES_ARG, "Comma separated list of hostnames.", TlsConfig.DEFAULT_HOSTNAME);
+ addOptionWithArg("p", HTTPS_PORT_ARG, "Https port to use.", "");
+ addOptionWithArg("f", NIFI_PROPERTIES_FILE_ARG, "Base nifi.properties file to update.", "");
+ addOptionWithArg("S", KEY_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)");
+ addOptionWithArg("K", KEY_PASSWORD_ARG, "Key password to use. Must either be one value or one for each host. (autogenerate if not specified)");
+ addOptionWithArg("P", TRUST_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)");
+ }
+
+ public static void main(String[] args) {
+ TlsHelper.addBouncyCastleProvider();
+ TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
+ try {
+ tlsToolkitStandaloneCommandLine.parse(args);
+ } catch (CommandLineParseException e) {
+ System.exit(e.getExitCode());
+ }
+ try {
+ new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.getBaseDir(), tlsToolkitStandaloneCommandLine.createConfig(),
+ tlsToolkitStandaloneCommandLine.getNiFiPropertiesWriterFactory(), tlsToolkitStandaloneCommandLine.getHostnames(), tlsToolkitStandaloneCommandLine.getKeyStorePasswords(),
+ tlsToolkitStandaloneCommandLine.getKeyPasswords(), tlsToolkitStandaloneCommandLine.getTrustStorePasswords(), tlsToolkitStandaloneCommandLine.getHttpsPort());
+ } catch (Exception e) {
+ tlsToolkitStandaloneCommandLine.printUsage("Error creating generating tls configuration. (" + e.getMessage() + ")");
+ System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal());
+ }
+ System.exit(ExitCode.SUCCESS.ordinal());
+ }
+
+ @Override
+ protected CommandLine doParse(String... args) throws CommandLineParseException {
+ CommandLine commandLine = super.doParse(args);
+ String outputDirectory = commandLine.getOptionValue(OUTPUT_DIRECTORY_ARG, DEFAULT_OUTPUT_DIRECTORY);
+ baseDir = new File(outputDirectory);
+ hostnames = Arrays.stream(commandLine.getOptionValue(HOSTNAMES_ARG, TlsConfig.DEFAULT_HOSTNAME).split(",")).map(String::trim).collect(Collectors.toList());
+ httpsPort = commandLine.getOptionValue(HTTPS_PORT_ARG, "");
+
+ int numHosts = hostnames.size();
+ keyStorePasswords = Collections.unmodifiableList(getPasswords(KEY_STORE_PASSWORD_ARG, commandLine, numHosts));
+ keyPasswords = Collections.unmodifiableList(getKeyPasswords(commandLine, keyStorePasswords));
+ trustStorePasswords = Collections.unmodifiableList(getPasswords(TRUST_STORE_PASSWORD_ARG, commandLine, numHosts));
+
+ String nifiPropertiesFile = commandLine.getOptionValue(NIFI_PROPERTIES_FILE_ARG, "");
+ try {
+ if (StringUtils.isEmpty(nifiPropertiesFile)) {
+ niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory();
+ } else {
+ niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory(new FileInputStream(nifiPropertiesFile));
+ }
+ } catch (IOException e) {
+ printUsageAndThrow("Unable to read nifi.properties from " + (StringUtils.isEmpty(nifiPropertiesFile) ? "classpath" : nifiPropertiesFile), ExitCode.ERROR_READING_NIFI_PROPERTIES);
+ }
+ return commandLine;
+ }
+
+ private List getPasswords(String arg, CommandLine commandLine, int numHosts) throws CommandLineParseException {
+ String[] optionValues = commandLine.getOptionValues(arg);
+ if (optionValues == null) {
+ return IntStream.range(0, numHosts).mapToObj(operand -> passwordUtil.generatePassword()).collect(Collectors.toList());
+ }
+ if (optionValues.length == 1) {
+ return IntStream.range(0, numHosts).mapToObj(value -> optionValues[0]).collect(Collectors.toList());
+ } else if (optionValues.length == numHosts) {
+ return Arrays.stream(optionValues).collect(Collectors.toList());
+ }
+ return printUsageAndThrow("Expected either 1 value or " + numHosts + " (the number of hostnames) values for " + arg, ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS);
+ }
+
+ private List getKeyPasswords(CommandLine commandLine, List keyStorePasswords) throws CommandLineParseException {
+ if (differentPasswordForKeyAndKeystore() || commandLine.hasOption(KEY_PASSWORD_ARG)) {
+ return getPasswords(KEY_PASSWORD_ARG, commandLine, keyStorePasswords.size());
+ }
+ return new ArrayList<>(keyStorePasswords);
+ }
+
+ public File getBaseDir() {
+ return baseDir;
+ }
+
+ public List getHostnames() {
+ return hostnames;
+ }
+
+ public String getHttpsPort() {
+ return httpsPort;
+ }
+
+ public NiFiPropertiesWriterFactory getNiFiPropertiesWriterFactory() {
+ return niFiPropertiesWriterFactory;
+ }
+
+ public List getKeyStorePasswords() {
+ return keyStorePasswords;
+ }
+
+ public List getKeyPasswords() {
+ return keyPasswords;
+ }
+
+ public List getTrustStorePasswords() {
+ return trustStorePasswords;
+ }
+
+ public TlsConfig createConfig() throws IOException {
+ TlsConfig tlsConfig = new TlsConfig();
+ tlsConfig.setCaHostname(getCertificateAuthorityHostname());
+ tlsConfig.setKeyStore("nifi-ca-" + KEYSTORE + getKeyStoreType().toLowerCase());
+ tlsConfig.setKeyStoreType(getKeyStoreType());
+ tlsConfig.setKeySize(getKeySize());
+ tlsConfig.setKeyPairAlgorithm(getKeyAlgorithm());
+ tlsConfig.setSigningAlgorithm(getSigningAlgorithm());
+ tlsConfig.setDays(getDays());
+ tlsConfig.initDefaults();
+ return tlsConfig;
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/InputStreamFactory.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/InputStreamFactory.java
new file mode 100644
index 0000000000..50104f46d4
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/InputStreamFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.tls.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public interface InputStreamFactory {
+ InputStream create(File file) throws FileNotFoundException;
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/OutputStreamFactory.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/OutputStreamFactory.java
new file mode 100644
index 0000000000..759e34ff34
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/OutputStreamFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.tls.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+
+public interface OutputStreamFactory {
+ OutputStream create(File file) throws FileNotFoundException;
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/PasswordUtil.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/PasswordUtil.java
new file mode 100644
index 0000000000..c26fd7f127
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/PasswordUtil.java
@@ -0,0 +1,43 @@
+/*
+ * 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.tls.util;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+public class PasswordUtil {
+ private final SecureRandom secureRandom;
+
+ public PasswordUtil() {
+ this(new SecureRandom());
+ }
+
+ public PasswordUtil(SecureRandom secureRandom) {
+ this.secureRandom = secureRandom;
+ }
+
+ public String generatePassword() {
+ // [see http://stackoverflow.com/questions/41107/how-to-generate-a-random-alpha-numeric-string#answer-41156]
+ String string = Base64.getEncoder().encodeToString(new BigInteger(256, secureRandom).toByteArray());
+ while (string.endsWith("=")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ return string;
+ }
+}
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
new file mode 100644
index 0000000000..0622b9e29d
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java
@@ -0,0 +1,112 @@
+/*
+ * 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.tls.util;
+
+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.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+public class TlsHelper {
+ private TlsHelper() {
+
+ }
+
+ public static void addBouncyCastleProvider() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ private static KeyPairGenerator createKeyPairGenerator(String algorithm, int keySize) throws NoSuchAlgorithmException {
+ KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm);
+ instance.initialize(keySize);
+ return instance;
+ }
+
+ public static byte[] calculateHMac(String token, PublicKey publicKey) throws GeneralSecurityException {
+ SecretKeySpec keySpec = new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "RAW");
+ Mac mac = Mac.getInstance("Hmac-SHA256", BouncyCastleProvider.PROVIDER_NAME);
+ mac.init(keySpec);
+ return mac.doFinal(getKeyIdentifier(publicKey));
+ }
+
+ public static byte[] getKeyIdentifier(PublicKey publicKey) throws NoSuchAlgorithmException {
+ return new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey).getKeyIdentifier();
+ }
+
+ public static String pemEncodeJcaObject(Object object) throws IOException {
+ StringWriter writer = new StringWriter();
+ try (PemWriter pemWriter = new PemWriter(writer)) {
+ pemWriter.writeObject(new JcaMiscPEMGenerator(object));
+ }
+ return writer.toString();
+ }
+
+ public static JcaPKCS10CertificationRequest parseCsr(String pemEncodedCsr) throws IOException {
+ try (PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCsr))) {
+ Object o = pemParser.readObject();
+ if (!PKCS10CertificationRequest.class.isInstance(o)) {
+ throw new IOException("Expecting instance of " + PKCS10CertificationRequest.class + " but got " + o);
+ }
+ return new JcaPKCS10CertificationRequest((PKCS10CertificationRequest) o);
+ }
+ }
+
+ public static X509Certificate parseCertificate(String pemEncodedCertificate) throws IOException, CertificateException {
+ try (PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCertificate))) {
+ Object object = pemParser.readObject();
+ if (!X509CertificateHolder.class.isInstance(object)) {
+ throw new IOException("Expected " + X509CertificateHolder.class);
+ }
+ return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate((X509CertificateHolder) object);
+ }
+ }
+
+ public static KeyPair generateKeyPair(String algorithm, int keySize) throws NoSuchAlgorithmException {
+ return createKeyPairGenerator(algorithm, keySize).generateKeyPair();
+ }
+
+ public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn, KeyPair keyPair, String signingAlgorithm) throws OperatorCreationException {
+ JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(requestedDn), keyPair.getPublic());
+ JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm);
+ return new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/properties/NifiPropertiesWriterTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/properties/NifiPropertiesWriterTest.java
new file mode 100644
index 0000000000..c3ef345f41
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/properties/NifiPropertiesWriterTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tls.properties;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+public class NifiPropertiesWriterTest {
+ private static NiFiPropertiesWriterFactory nifiPropertiesWriterFactory;
+ private NiFiPropertiesWriter niFiPropertiesWriter;
+
+ @BeforeClass
+ public static void beforeClass() throws IOException {
+ nifiPropertiesWriterFactory = new NiFiPropertiesWriterFactory();
+ }
+
+ @Before
+ public void setup() throws IOException {
+ niFiPropertiesWriter = nifiPropertiesWriterFactory.create();
+ }
+
+ @Test
+ public void testCanUpdateAllKeys() throws IllegalAccessException, IOException {
+ String testValue = NifiPropertiesWriterTest.class.getCanonicalName();
+
+ // Get all property keys from NiFiProperties and set them to testValue
+ Set propertyKeys = new HashSet<>();
+ for (Field field : NiFiProperties.class.getDeclaredFields()) {
+ int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && field.getType() == String.class) {
+ if (!field.getName().toLowerCase().startsWith("default")) {
+ String fieldName = (String) field.get(null);
+ propertyKeys.add(fieldName);
+ niFiPropertiesWriter.setPropertyValue(fieldName, testValue);
+ }
+ }
+ }
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ niFiPropertiesWriter.writeNiFiProperties(outputStream);
+
+ // Verify operation worked
+ Properties properties = new Properties();
+ properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
+ for (String propertyKey : propertyKeys) {
+ assertEquals(testValue, properties.getProperty(propertyKey));
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java
new file mode 100644
index 0000000000..78815003af
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.tls.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.service.client.TlsCertificateAuthorityClient;
+import org.apache.nifi.toolkit.tls.service.server.TlsCertificateAuthorityService;
+import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.apache.nifi.toolkit.tls.util.OutputStreamFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.AdditionalMatchers.or;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TlsCertificateAuthorityTest {
+ private File serverConfigFile;
+ private File clientConfigFile;
+ private OutputStreamFactory outputStreamFactory;
+ private InputStreamFactory inputStreamFactory;
+ private TlsConfig serverConfig;
+ private TlsClientConfig clientConfig;
+ private ObjectMapper objectMapper;
+ private ByteArrayOutputStream serverKeyStoreOutputStream;
+ private ByteArrayOutputStream clientKeyStoreOutputStream;
+ private ByteArrayOutputStream clientTrustStoreOutputStream;
+ private ByteArrayOutputStream serverConfigFileOutputStream;
+ private ByteArrayOutputStream clientConfigFileOutputStream;
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @Before
+ public void setup() throws FileNotFoundException {
+ objectMapper = new ObjectMapper();
+ serverConfigFile = new File("fake.server.config");
+ clientConfigFile = new File("fake.client.config");
+ String serverKeyStore = "serverKeyStore";
+ String clientKeyStore = "clientKeyStore";
+ String clientTrustStore = "clientTrustStore";
+ serverKeyStoreOutputStream = new ByteArrayOutputStream();
+ clientKeyStoreOutputStream = new ByteArrayOutputStream();
+ clientTrustStoreOutputStream = new ByteArrayOutputStream();
+ serverConfigFileOutputStream = new ByteArrayOutputStream();
+ clientConfigFileOutputStream = new ByteArrayOutputStream();
+
+ String myTestTokenUseSomethingStronger = "myTestTokenUseSomethingStronger";
+ int port = availablePort();
+
+ serverConfig = new TlsConfig();
+ serverConfig.setCaHostname("localhost");
+ serverConfig.setToken(myTestTokenUseSomethingStronger);
+ serverConfig.setKeyStore(serverKeyStore);
+ serverConfig.setPort(port);
+ serverConfig.setDays(5);
+ serverConfig.setKeySize(2048);
+ serverConfig.initDefaults();
+
+ clientConfig = new TlsClientConfig();
+ clientConfig.setCaHostname("localhost");
+ clientConfig.setDn("CN=otherHostname,OU=NIFI");
+ clientConfig.setKeyStore(clientKeyStore);
+ clientConfig.setTrustStore(clientTrustStore);
+ clientConfig.setToken(myTestTokenUseSomethingStronger);
+ clientConfig.setPort(port);
+ clientConfig.setKeySize(2048);
+ clientConfig.initDefaults();
+
+ outputStreamFactory = mock(OutputStreamFactory.class);
+ mockReturnOutputStream(outputStreamFactory, new File(serverKeyStore), serverKeyStoreOutputStream);
+ mockReturnOutputStream(outputStreamFactory, new File(clientKeyStore), clientKeyStoreOutputStream);
+ mockReturnOutputStream(outputStreamFactory, new File(clientTrustStore), clientTrustStoreOutputStream);
+ mockReturnOutputStream(outputStreamFactory, serverConfigFile, serverConfigFileOutputStream);
+ mockReturnOutputStream(outputStreamFactory, clientConfigFile, clientConfigFileOutputStream);
+
+ inputStreamFactory = mock(InputStreamFactory.class);
+ mockReturnProperties(inputStreamFactory, serverConfigFile, serverConfig);
+ mockReturnProperties(inputStreamFactory, clientConfigFile, clientConfig);
+ }
+
+ private void mockReturnProperties(InputStreamFactory inputStreamFactory, File file, TlsConfig tlsConfig) throws FileNotFoundException {
+ when(inputStreamFactory.create(eq(file))).thenAnswer(invocation -> {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ objectMapper.writeValue(byteArrayOutputStream, tlsConfig);
+ return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
+ });
+ }
+
+ private void mockReturnOutputStream(OutputStreamFactory outputStreamFactory, File file, OutputStream outputStream) throws FileNotFoundException {
+ when(outputStreamFactory.create(or(eq(file), eq(new File(file.getAbsolutePath()))))).thenReturn(outputStream).thenReturn(null);
+ }
+
+ @Test
+ public void testClientGetCertDifferentPasswordsForKeyAndKeyStore() throws Exception {
+ TlsCertificateAuthorityService tlsCertificateAuthorityService = null;
+ try {
+ tlsCertificateAuthorityService = new TlsCertificateAuthorityService(outputStreamFactory);
+ tlsCertificateAuthorityService.start(serverConfig, serverConfigFile.getAbsolutePath(), true);
+ TlsCertificateAuthorityClient tlsCertificateAuthorityClient = new TlsCertificateAuthorityClient(outputStreamFactory);
+ tlsCertificateAuthorityClient.generateCertificateAndGetItSigned(clientConfig, null, clientConfigFile.getAbsolutePath(), true);
+ validate();
+ } finally {
+ if (tlsCertificateAuthorityService != null) {
+ tlsCertificateAuthorityService.shutdown();
+ }
+ }
+ }
+
+ @Test
+ public void testClientGetCertSamePasswordsForKeyAndKeyStore() throws Exception {
+ TlsCertificateAuthorityService tlsCertificateAuthorityService = null;
+ try {
+ tlsCertificateAuthorityService = new TlsCertificateAuthorityService(outputStreamFactory);
+ tlsCertificateAuthorityService.start(serverConfig, serverConfigFile.getAbsolutePath(), false);
+ TlsCertificateAuthorityClient tlsCertificateAuthorityClient = new TlsCertificateAuthorityClient(outputStreamFactory);
+ tlsCertificateAuthorityClient.generateCertificateAndGetItSigned(clientConfig, null, clientConfigFile.getAbsolutePath(), false);
+ validate();
+ } finally {
+ if (tlsCertificateAuthorityService != null) {
+ tlsCertificateAuthorityService.shutdown();
+ }
+ }
+ }
+
+ @Test
+ public void testTokenMismatch() throws Exception {
+ serverConfig.setToken("a different token...");
+ try {
+ testClientGetCertSamePasswordsForKeyAndKeyStore();
+ fail("Expected error with mismatching token");
+ } catch (IOException e) {
+ assertTrue(e.getMessage().contains("forbidden"));
+ }
+ }
+
+ private void validate() throws CertificateException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, SignatureException,
+ NoSuchProviderException, UnrecoverableEntryException, IOException {
+ Certificate caCertificate = validateServerKeyStore();
+ validateClient(caCertificate);
+ }
+
+ private Certificate validateServerKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableEntryException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ serverConfig = objectMapper.readValue(new ByteArrayInputStream(serverConfigFileOutputStream.toByteArray()), TlsConfig.class);
+
+ KeyStore serverKeyStore = KeyStore.getInstance(serverConfig.getKeyStoreType());
+ serverKeyStore.load(new ByteArrayInputStream(serverKeyStoreOutputStream.toByteArray()), serverConfig.getKeyStorePassword().toCharArray());
+ KeyStore.Entry serverKeyEntry = serverKeyStore.getEntry(TlsToolkitStandalone.NIFI_KEY, new KeyStore.PasswordProtection(serverConfig.getKeyPassword().toCharArray()));
+
+ assertTrue(serverKeyEntry instanceof KeyStore.PrivateKeyEntry);
+ KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) serverKeyEntry;
+ Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
+ assertEquals(1, certificateChain.length);
+ Certificate caCertificate = certificateChain[0];
+ caCertificate.verify(caCertificate.getPublicKey());
+ assertPrivateAndPublicKeyMatch(privateKeyEntry.getPrivateKey(), caCertificate.getPublicKey());
+ return caCertificate;
+ }
+
+ private void validateClient(Certificate caCertificate) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException,
+ UnrecoverableEntryException, InvalidKeyException, NoSuchProviderException, SignatureException {
+ clientConfig = objectMapper.readValue(new ByteArrayInputStream(clientConfigFileOutputStream.toByteArray()), TlsClientConfig.class);
+
+ KeyStore clientKeyStore = KeyStore.getInstance(clientConfig.getKeyStoreType());
+ clientKeyStore.load(new ByteArrayInputStream(clientKeyStoreOutputStream.toByteArray()), clientConfig.getKeyStorePassword().toCharArray());
+ KeyStore.Entry clientKeyStoreEntry = clientKeyStore.getEntry(TlsToolkitStandalone.NIFI_KEY, new KeyStore.PasswordProtection(clientConfig.getKeyPassword().toCharArray()));
+
+ assertTrue(clientKeyStoreEntry instanceof KeyStore.PrivateKeyEntry);
+ KeyStore.PrivateKeyEntry clientPrivateKeyEntry = (KeyStore.PrivateKeyEntry) clientKeyStoreEntry;
+ Certificate[] certificateChain = clientPrivateKeyEntry.getCertificateChain();
+ assertEquals(2, certificateChain.length);
+ assertEquals(caCertificate, certificateChain[1]);
+ certificateChain[0].verify(caCertificate.getPublicKey());
+ assertPrivateAndPublicKeyMatch(clientPrivateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey());
+
+ KeyStore clientTrustStore = KeyStore.getInstance(clientConfig.getTrustStoreType());
+ clientTrustStore.load(new ByteArrayInputStream(clientTrustStoreOutputStream.toByteArray()), clientConfig.getTrustStorePassword().toCharArray());
+ assertEquals(caCertificate, clientTrustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT));
+ }
+
+ private void assertPrivateAndPublicKeyMatch(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+ Signature signature = Signature.getInstance(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ signature.initSign(privateKey);
+ byte[] bytes = "test string".getBytes(StandardCharsets.UTF_8);
+ signature.update(bytes);
+
+ Signature verify = Signature.getInstance(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ verify.initVerify(publicKey);
+ verify.update(bytes);
+ verify.verify(signature.sign());
+ }
+
+ /**
+ * Will determine the available port used by ca server
+ */
+ private int availablePort() {
+ ServerSocket s = null;
+ try {
+ s = new ServerSocket(0);
+ s.setReuseAddress(true);
+ return s.getLocalPort();
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to discover available port.", e);
+ } finally {
+ try {
+ s.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLineTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLineTest.java
new file mode 100644
index 0000000000..40a8268005
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateAuthorityClientCommandLineTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.tls.service.client;
+
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class TlsCertificateAuthorityClientCommandLineTest {
+
+ private TlsCertificateAuthorityClientCommandLine tlsCertificateAuthorityClientCommandLine;
+ private String testToken;
+
+ @Before
+ public void setup() {
+ tlsCertificateAuthorityClientCommandLine = new TlsCertificateAuthorityClientCommandLine();
+ testToken = "testToken";
+ }
+
+ @Test
+ public void testNoToken() {
+ try {
+ tlsCertificateAuthorityClientCommandLine.parse(new String[0]);
+ fail("Expected failure with no token argument");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_TOKEN_ARG_EMPTY.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testDefaults() throws CommandLineParseException, IOException {
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken);
+ TlsClientConfig clientConfig = tlsCertificateAuthorityClientCommandLine.createClientConfig();
+
+ assertEquals(TlsConfig.DEFAULT_HOSTNAME, clientConfig.getCaHostname());
+ Assert.assertEquals(TlsConfig.calcDefaultDn(InetAddress.getLocalHost().getHostName()), clientConfig.getDn());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.KEYSTORE + TlsConfig.DEFAULT_KEY_STORE_TYPE.toLowerCase(), clientConfig.getKeyStore());
+ assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, clientConfig.getKeyStoreType());
+ assertNull(clientConfig.getKeyStorePassword());
+ assertNull(clientConfig.getKeyPassword());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.TRUSTSTORE + TlsConfig.DEFAULT_KEY_STORE_TYPE.toLowerCase(), clientConfig.getTrustStore());
+ assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, clientConfig.getTrustStoreType());
+ assertNull(clientConfig.getTrustStorePassword());
+ assertEquals(TlsConfig.DEFAULT_KEY_SIZE, clientConfig.getKeySize());
+ assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, clientConfig.getKeyPairAlgorithm());
+ assertEquals(testToken, clientConfig.getToken());
+ assertEquals(TlsConfig.DEFAULT_PORT, clientConfig.getPort());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.DEFAULT_CONFIG_JSON, tlsCertificateAuthorityClientCommandLine.getConfigJson());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.DEFAULT_CERTIFICATE_DIRECTORY, tlsCertificateAuthorityClientCommandLine.getCertificateDirectory());
+ }
+
+ @Test
+ public void testKeySize() throws CommandLineParseException {
+ int keySize = 1234;
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-k", Integer.toString(keySize));
+ assertEquals(keySize, tlsCertificateAuthorityClientCommandLine.getKeySize());
+ }
+
+ @Test
+ public void testKeyPairAlgorithm() throws CommandLineParseException {
+ String testAlgorithm = "testAlgorithm";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-a", testAlgorithm);
+ assertEquals(testAlgorithm, tlsCertificateAuthorityClientCommandLine.getKeyAlgorithm());
+ }
+
+ @Test
+ public void testHelp() {
+ try {
+ tlsCertificateAuthorityClientCommandLine.parse("-h");
+ fail("Expected exception");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.HELP.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testCaHostname() throws CommandLineParseException, IOException {
+ String testCaHostname = "testCaHostname";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-c", testCaHostname);
+ assertEquals(testCaHostname, tlsCertificateAuthorityClientCommandLine.createClientConfig().getCaHostname());
+ }
+
+ @Test
+ public void testDn() throws CommandLineParseException, IOException {
+ String testDn = "testDn";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-D", testDn);
+ assertEquals(testDn, tlsCertificateAuthorityClientCommandLine.createClientConfig().getDn());
+ }
+
+ @Test
+ public void testPort() throws CommandLineParseException, IOException {
+ int testPort = 2345;
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-p", Integer.toString(testPort));
+ assertEquals(testPort, tlsCertificateAuthorityClientCommandLine.createClientConfig().getPort());
+ }
+
+ @Test
+ public void testKeyStoreType() throws CommandLineParseException, IOException {
+ String testType = "testType";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-T", testType);
+
+ TlsClientConfig clientConfig = tlsCertificateAuthorityClientCommandLine.createClientConfig();
+ assertEquals(testType, clientConfig.getKeyStoreType());
+ assertEquals(testType, clientConfig.getTrustStoreType());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.KEYSTORE + testType.toLowerCase(), clientConfig.getKeyStore());
+ assertEquals(TlsCertificateAuthorityClientCommandLine.TRUSTSTORE + testType.toLowerCase(), clientConfig.getTrustStore());
+ }
+
+ @Test
+ public void testConfigFile() throws CommandLineParseException {
+ String testPath = "/1/2/3/4";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-f", testPath);
+ assertEquals(testPath, tlsCertificateAuthorityClientCommandLine.getConfigJson());
+ }
+
+ @Test
+ public void testCertificateFile() throws CommandLineParseException {
+ String testCertificateFile = "testCertificateFile";
+ tlsCertificateAuthorityClientCommandLine.parse("-t", testToken, "-C", testCertificateFile);
+ assertEquals(testCertificateFile, tlsCertificateAuthorityClientCommandLine.getCertificateDirectory());
+ }
+}
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
new file mode 100644
index 0000000000..bdc01dfb9d
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformerTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.tls.service.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.HttpHost;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+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.toolkit.tls.configuration.TlsClientConfig;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
+import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.eclipse.jetty.server.Response;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TlsCertificateSigningRequestPerformerTest {
+ @Mock
+ Supplier httpClientBuilderSupplier;
+
+ @Mock
+ HttpClientBuilder httpClientBuilder;
+
+ @Mock
+ CloseableHttpClient closeableHttpClient;
+
+ @Mock
+ TlsClientConfig tlsClientConfig;
+
+ X509Certificate caCertificate;
+
+ X509Certificate signedCsr;
+
+ ObjectMapper objectMapper;
+ KeyPair keyPair;
+ TlsCertificateSigningRequestPerformer tlsCertificateSigningRequestPerformer;
+ String testToken;
+ String testCaHostname;
+ int testPort;
+ List certificates;
+
+ TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse;
+ int statusCode;
+ private byte[] testHmac;
+ private String testSignedCsr;
+
+ @BeforeClass
+ public static void before() {
+ TlsHelper.addBouncyCastleProvider();
+ }
+
+ @Before
+ public void setup() throws GeneralSecurityException, OperatorCreationException, IOException {
+ objectMapper = new ObjectMapper();
+ keyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
+
+ testToken = "testToken";
+ testCaHostname = "testCaHostname";
+ testPort = 8993;
+ certificates = new ArrayList<>();
+
+ when(tlsClientConfig.getToken()).thenReturn(testToken);
+ when(tlsClientConfig.getCaHostname()).thenReturn(testCaHostname);
+ when(tlsClientConfig.getDn()).thenReturn(TlsConfig.calcDefaultDn(testCaHostname));
+ when(tlsClientConfig.getPort()).thenReturn(testPort);
+ when(tlsClientConfig.createCertificateSigningRequestPerformer()).thenReturn(tlsCertificateSigningRequestPerformer);
+ when(tlsClientConfig.getSigningAlgorithm()).thenReturn(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ String testCsrPem = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
+ when(httpClientBuilderSupplier.get()).thenReturn(httpClientBuilder);
+ when(httpClientBuilder.build()).thenAnswer(invocation -> {
+ Field sslSocketFactory = HttpClientBuilder.class.getDeclaredField("sslSocketFactory");
+ sslSocketFactory.setAccessible(true);
+ Object o = sslSocketFactory.get(httpClientBuilder);
+ Field field = TlsCertificateAuthorityClientSocketFactory.class.getDeclaredField("certificates");
+ field.setAccessible(true);
+ ((List) field.get(o)).addAll(certificates);
+ return closeableHttpClient;
+ });
+ StatusLine statusLine = mock(StatusLine.class);
+ when(statusLine.getStatusCode()).thenAnswer(i -> statusCode);
+ when(closeableHttpClient.execute(eq(new HttpHost(testCaHostname, testPort, "https")), any(HttpPost.class))).thenAnswer(invocation -> {
+ HttpPost httpPost = (HttpPost) invocation.getArguments()[1];
+ TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(httpPost.getEntity().getContent(), TlsCertificateAuthorityRequest.class);
+ assertEquals(tlsCertificateAuthorityRequest.getCsr(), testCsrPem);
+ CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
+ when(closeableHttpResponse.getEntity()).thenAnswer(i -> {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ objectMapper.writeValue(byteArrayOutputStream, tlsCertificateAuthorityResponse);
+ return new ByteArrayEntity(byteArrayOutputStream.toByteArray());
+ });
+ when(closeableHttpResponse.getStatusLine()).thenReturn(statusLine);
+ 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);
+ testHmac = TlsHelper.calculateHMac(testToken, caCertificate.getPublicKey());
+ signedCsr = CertificateUtils.generateIssuedCertificate(jcaPKCS10CertificationRequest.getSubject().toString(), jcaPKCS10CertificationRequest.getPublicKey(),
+ caCertificate, caKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
+ testSignedCsr = TlsHelper.pemEncodeJcaObject(signedCsr);
+
+ tlsCertificateSigningRequestPerformer = new TlsCertificateSigningRequestPerformer(httpClientBuilderSupplier, tlsClientConfig);
+ }
+
+ @Test
+ public void testOk() throws Exception {
+ certificates.add(caCertificate);
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(testHmac, testSignedCsr);
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ }
+
+ @Test
+ public void testBadStatusCode() throws Exception {
+ statusCode = Response.SC_FORBIDDEN;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertTrue(e.getMessage().startsWith(TlsCertificateSigningRequestPerformer.RECEIVED_RESPONSE_CODE + statusCode));
+ }
+ }
+
+ @Test
+ public void test0CertSize() throws Exception {
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_ONE_CERTIFICATE, e.getMessage());
+ }
+ }
+
+ @Test
+ public void test2CertSize() throws Exception {
+ certificates.add(caCertificate);
+ certificates.add(caCertificate);
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse();
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_ONE_CERTIFICATE, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNoHmac() throws Exception {
+ certificates.add(caCertificate);
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(null, testSignedCsr);
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_RESPONSE_TO_CONTAIN_HMAC, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBadHmac() throws Exception {
+ certificates.add(caCertificate);
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse("badHmac".getBytes(StandardCharsets.UTF_8), testSignedCsr);
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertEquals(TlsCertificateSigningRequestPerformer.UNEXPECTED_HMAC_RECEIVED_POSSIBLE_MAN_IN_THE_MIDDLE, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNoCertificate() throws Exception {
+ certificates.add(caCertificate);
+ statusCode = Response.SC_OK;
+ tlsCertificateAuthorityResponse = new TlsCertificateAuthorityResponse(testHmac, null);
+ try {
+ tlsCertificateSigningRequestPerformer.perform(keyPair);
+ fail("Expected IOE");
+ } catch (IOException e) {
+ assertEquals(TlsCertificateSigningRequestPerformer.EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE, e.getMessage());
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLineTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLineTest.java
new file mode 100644
index 0000000000..9654186d48
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceCommandLineTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tls.service.server;
+
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.util.InputStreamFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TlsCertificateAuthorityServiceCommandLineTest {
+ @Mock
+ InputStreamFactory inputStreamFactory;
+
+ TlsCertificateAuthorityServiceCommandLine tlsCertificateAuthorityServiceCommandLine;
+
+ String testToken;
+
+ @Before
+ public void setup() {
+ tlsCertificateAuthorityServiceCommandLine = new TlsCertificateAuthorityServiceCommandLine(inputStreamFactory);
+ testToken = "testToken";
+ }
+
+ @Test
+ public void testDefaults() throws CommandLineParseException, IOException {
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken);
+ TlsConfig tlsConfig = tlsCertificateAuthorityServiceCommandLine.createConfig();
+ assertEquals(TlsConfig.DEFAULT_HOSTNAME, tlsConfig.getCaHostname());
+ assertEquals(testToken, tlsConfig.getToken());
+ assertEquals(TlsConfig.DEFAULT_PORT, tlsConfig.getPort());
+ assertEquals(TlsConfig.DEFAULT_KEY_STORE_TYPE, tlsConfig.getKeyStoreType());
+ assertEquals(TlsCertificateAuthorityServiceCommandLine.NIFI_CA_KEYSTORE + tlsConfig.getKeyStoreType().toLowerCase(), tlsConfig.getKeyStore());
+ assertNull(tlsConfig.getKeyStorePassword());
+ assertNull(tlsConfig.getKeyPassword());
+ assertEquals(TlsConfig.DEFAULT_KEY_SIZE, tlsConfig.getKeySize());
+ assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, tlsConfig.getKeyPairAlgorithm());
+ assertEquals(TlsConfig.DEFAULT_SIGNING_ALGORITHM, tlsConfig.getSigningAlgorithm());
+ assertEquals(TlsConfig.DEFAULT_DAYS, tlsConfig.getDays());
+ }
+
+ @Test
+ public void testCaHostname() throws CommandLineParseException, IOException {
+ String testCaHostname = "testCaHostname";
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-c", testCaHostname);
+ assertEquals(testCaHostname, tlsCertificateAuthorityServiceCommandLine.createConfig().getCaHostname());
+ }
+
+ @Test
+ public void testPort() throws CommandLineParseException, IOException {
+ int testPort = 4321;
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-p", Integer.toString(testPort));
+ assertEquals(testPort, tlsCertificateAuthorityServiceCommandLine.createConfig().getPort());
+ }
+
+ @Test
+ public void testKeyStoreType() throws CommandLineParseException, IOException {
+ String testKeyStoreType = "testKeyStoreType";
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-T", testKeyStoreType);
+ TlsConfig tlsConfig = tlsCertificateAuthorityServiceCommandLine.createConfig();
+ assertEquals(testKeyStoreType, tlsConfig.getKeyStoreType());
+ assertEquals(TlsCertificateAuthorityServiceCommandLine.NIFI_CA_KEYSTORE + tlsConfig.getKeyStoreType().toLowerCase(), tlsConfig.getKeyStore());
+ }
+
+ @Test
+ public void testKeySize() throws CommandLineParseException, IOException {
+ int testKeySize = 8192;
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-k", Integer.toString(testKeySize));
+ assertEquals(testKeySize, tlsCertificateAuthorityServiceCommandLine.createConfig().getKeySize());
+ }
+
+ @Test
+ public void testKeyPairAlgorithm() throws CommandLineParseException, IOException {
+ String testAlgorithm = "testAlgorithm";
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-a", testAlgorithm);
+ assertEquals(testAlgorithm, tlsCertificateAuthorityServiceCommandLine.createConfig().getKeyPairAlgorithm());
+ }
+
+ @Test
+ public void testSigningAlgorithm() throws CommandLineParseException, IOException {
+ String testSigningAlgorithm = "testSigningAlgorithm";
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-s", testSigningAlgorithm);
+ assertEquals(testSigningAlgorithm, tlsCertificateAuthorityServiceCommandLine.createConfig().getSigningAlgorithm());
+ }
+
+ @Test
+ public void testDays() throws CommandLineParseException, IOException {
+ int days = 1234;
+ tlsCertificateAuthorityServiceCommandLine.parse("-t", testToken, "-d", Integer.toString(days));
+ assertEquals(days, tlsCertificateAuthorityServiceCommandLine.createConfig().getDays());
+ }
+}
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
new file mode 100644
index 0000000000..bf355fa0ee
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.tls.service.server;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
+import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
+import org.apache.nifi.toolkit.tls.util.TlsHelper;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.crmf.CRMFException;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.X509Certificate;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TlsCertificateAuthorityServiceHandlerTest {
+ X509Certificate caCert;
+
+ @Mock
+ Request baseRequest;
+
+ @Mock
+ HttpServletRequest httpServletRequest;
+
+ @Mock
+ HttpServletResponse httpServletResponse;
+
+ JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest;
+
+ KeyPair keyPair;
+
+ String testToken;
+
+ String testPemEncodedCsr;
+
+ String testPemEncodedSignedCertificate;
+
+ ObjectMapper objectMapper;
+
+ TlsCertificateAuthorityServiceHandler tlsCertificateAuthorityServiceHandler;
+
+ TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest;
+
+ int statusCode;
+
+ StringWriter response;
+ private byte[] testCaHmac;
+ private byte[] testHmac;
+ private String requestedDn;
+ private KeyPair certificateKeyPair;
+
+ @BeforeClass
+ public static void before() {
+ TlsHelper.addBouncyCastleProvider();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ testToken = "testToken";
+ testPemEncodedSignedCertificate = "testPemEncodedSignedCertificate";
+ keyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
+ objectMapper = new ObjectMapper();
+ when(httpServletRequest.getReader()).thenAnswer(invocation -> {
+ StringWriter stringWriter = new StringWriter();
+ objectMapper.writeValue(stringWriter, tlsCertificateAuthorityRequest);
+ return new BufferedReader(new StringReader(stringWriter.toString()));
+ });
+ doAnswer(invocation -> statusCode = (int) invocation.getArguments()[0]).when(httpServletResponse).setStatus(anyInt());
+ doAnswer(invocation -> {
+ statusCode = (int) invocation.getArguments()[0];
+ StringWriter stringWriter = new StringWriter();
+ stringWriter.write((String) invocation.getArguments()[1]);
+ response = stringWriter;
+ return null;
+ }).when(httpServletResponse).sendError(anyInt(), anyString());
+ when(httpServletResponse.getWriter()).thenAnswer(invocation -> {
+ response = new StringWriter();
+ return new PrintWriter(response);
+ });
+ caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, "CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
+ requestedDn = TlsConfig.calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME);
+ certificateKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
+ jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(requestedDn, certificateKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
+ testPemEncodedCsr = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
+ tlsCertificateAuthorityServiceHandler = new TlsCertificateAuthorityServiceHandler(TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS, testToken, caCert, keyPair, objectMapper);
+ testHmac = TlsHelper.calculateHMac(testToken, jcaPKCS10CertificationRequest.getPublicKey());
+ testCaHmac = TlsHelper.calculateHMac(testToken, caCert.getPublicKey());
+ }
+
+ private TlsCertificateAuthorityResponse getResponse() throws IOException {
+ return objectMapper.readValue(new StringReader(response.toString()), TlsCertificateAuthorityResponse.class);
+ }
+
+ @Test
+ public void testSuccess() throws IOException, ServletException, GeneralSecurityException, CRMFException {
+ tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(testHmac, testPemEncodedCsr);
+ tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
+ assertEquals(Response.SC_OK, statusCode);
+ assertArrayEquals(testCaHmac, getResponse().getHmac());
+ X509Certificate certificate = TlsHelper.parseCertificate(getResponse().getPemEncodedCertificate());
+ assertEquals(certificateKeyPair.getPublic(), certificate.getPublicKey());
+ assertEquals(new X500Name(requestedDn), new X500Name(certificate.getSubjectDN().toString()));
+ certificate.verify(caCert.getPublicKey());
+ }
+
+ @Test
+ public void testNoCsr() throws IOException, ServletException {
+ tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(testHmac, null);
+ tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
+ assertEquals(Response.SC_BAD_REQUEST, statusCode);
+ assertEquals(TlsCertificateAuthorityServiceHandler.CSR_FIELD_MUST_BE_SET, getResponse().getError());
+ }
+
+ @Test
+ public void testNoHmac() throws IOException, ServletException {
+ tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(null, testPemEncodedCsr);
+ tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
+ assertEquals(Response.SC_BAD_REQUEST, statusCode);
+ assertEquals(TlsCertificateAuthorityServiceHandler.HMAC_FIELD_MUST_BE_SET, getResponse().getError());
+ }
+
+ @Test
+ public void testForbidden() throws IOException, ServletException, NoSuchAlgorithmException, CRMFException, NoSuchProviderException, InvalidKeyException {
+ tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest("badHmac".getBytes(StandardCharsets.UTF_8), testPemEncodedCsr);
+ tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
+ assertEquals(Response.SC_FORBIDDEN, statusCode);
+ assertEquals(TlsCertificateAuthorityServiceHandler.FORBIDDEN, getResponse().getError());
+ }
+
+ @Test(expected = ServletException.class)
+ public void testServletException() throws IOException, ServletException {
+ tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse);
+ }
+
+ @After
+ public void verifyHandled() {
+ verify(baseRequest).setHandled(true);
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java
new file mode 100644
index 0000000000..2152d45929
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.tls.standalone;
+
+import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter;
+import org.apache.nifi.toolkit.tls.util.PasswordUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Properties;
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+public class TlsToolkitStandaloneCommandLineTest {
+ private SecureRandom secureRandom;
+ private TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine;
+
+ @Before
+ public void setup() {
+ secureRandom = mock(SecureRandom.class);
+ doAnswer(new ForwardsInvocations(new Random())).when(secureRandom).nextBytes(any(byte[].class));
+ tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine(new PasswordUtil(secureRandom));
+ }
+
+ @Test
+ public void testHelp() {
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-h");
+ fail("Expected usage and help exit");
+ } catch (CommandLineParseException e) {
+ Assert.assertEquals(ExitCode.HELP.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testUnknownArg() {
+ try {
+ tlsToolkitStandaloneCommandLine.parse("--unknownArg");
+ fail("Expected error parsing command line");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_PARSING_COMMAND_LINE.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testKeyAlgorithm() throws CommandLineParseException, IOException {
+ String testKeyAlgorithm = "testKeyAlgorithm";
+ tlsToolkitStandaloneCommandLine.parse("-a", testKeyAlgorithm);
+ assertEquals(testKeyAlgorithm, tlsToolkitStandaloneCommandLine.createConfig().getKeyPairAlgorithm());
+ }
+
+ @Test
+ public void testKeySizeArgNotInteger() {
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-k", "badVal");
+ fail("Expected bad keysize exit code");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_PARSING_INT_ARG.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testKeySize() throws CommandLineParseException, IOException {
+ int testKeySize = 4096;
+ tlsToolkitStandaloneCommandLine.parse("-k", Integer.toString(testKeySize));
+ assertEquals(testKeySize, tlsToolkitStandaloneCommandLine.createConfig().getKeySize());
+ }
+
+ @Test
+ public void testSigningAlgorithm() throws CommandLineParseException, IOException {
+ String testSigningAlgorithm = "testSigningAlgorithm";
+ tlsToolkitStandaloneCommandLine.parse("-s", testSigningAlgorithm);
+ assertEquals(testSigningAlgorithm, tlsToolkitStandaloneCommandLine.createConfig().getSigningAlgorithm());
+ }
+
+ @Test
+ public void testDaysNotInteger() {
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-d", "badVal");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_PARSING_INT_ARG.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testDays() throws CommandLineParseException, IOException {
+ int testDays = 29;
+ tlsToolkitStandaloneCommandLine.parse("-d", Integer.toString(testDays));
+ assertEquals(testDays, tlsToolkitStandaloneCommandLine.createConfig().getDays());
+ }
+
+ @Test
+ public void testKeyStoreType() throws CommandLineParseException {
+ String testKeyStoreType = "testKeyStoreType";
+ tlsToolkitStandaloneCommandLine.parse("-T", testKeyStoreType);
+ assertEquals(testKeyStoreType, tlsToolkitStandaloneCommandLine.getKeyStoreType());
+ }
+
+ @Test
+ public void testOutputDirectory() throws CommandLineParseException {
+ String testPath = "/fake/path/doesnt/exist";
+ tlsToolkitStandaloneCommandLine.parse("-o", testPath);
+ assertEquals(testPath, tlsToolkitStandaloneCommandLine.getBaseDir().getAbsolutePath());
+ }
+
+ @Test
+ public void testHostnames() throws CommandLineParseException {
+ String nifi1 = "nifi1";
+ String nifi2 = "nifi2";
+
+ tlsToolkitStandaloneCommandLine.parse("-n", nifi1 + " , " + nifi2);
+
+ List hostnames = tlsToolkitStandaloneCommandLine.getHostnames();
+ assertEquals(2, hostnames.size());
+ assertEquals(nifi1, hostnames.get(0));
+ assertEquals(nifi2, hostnames.get(1));
+ }
+
+ @Test
+ public void testHttpsPort() throws CommandLineParseException {
+ String testPort = "8998";
+ tlsToolkitStandaloneCommandLine.parse("-p", testPort);
+ assertEquals(testPort, tlsToolkitStandaloneCommandLine.getHttpsPort());
+ }
+
+ @Test
+ public void testNifiPropertiesFile() throws CommandLineParseException, IOException {
+ tlsToolkitStandaloneCommandLine.parse("-f", TlsToolkitStandaloneTest.TEST_NIFI_PROPERTIES);
+ assertEquals(TlsToolkitStandaloneTest.FAKE_VALUE, getProperties().get(TlsToolkitStandaloneTest.NIFI_FAKE_PROPERTY));
+ }
+
+ @Test
+ public void testNifiPropertiesFileDefault() throws CommandLineParseException, IOException {
+ tlsToolkitStandaloneCommandLine.parse();
+ assertNull(getProperties().get(TlsToolkitStandaloneTest.NIFI_FAKE_PROPERTY));
+ }
+
+ @Test
+ public void testBadNifiPropertiesFile() {
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-f", "/this/file/should/not/exist.txt");
+ fail("Expected error when unable to read file");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_READING_NIFI_PROPERTIES.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testNotSameKeyAndKeystorePassword() throws CommandLineParseException {
+ tlsToolkitStandaloneCommandLine.parse("-g");
+ List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
+ List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
+ assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size());
+ assertEquals(1, keyStorePasswords.size());
+ assertEquals(1, keyPasswords.size());
+ assertNotEquals(keyStorePasswords.get(0), keyPasswords.get(0));
+ }
+
+ @Test
+ public void testSameKeyAndKeystorePassword() throws CommandLineParseException {
+ tlsToolkitStandaloneCommandLine.parse();
+ List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
+ List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
+ assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size());
+ assertEquals(1, keyStorePasswords.size());
+ assertEquals(1, keyPasswords.size());
+ assertEquals(keyStorePasswords.get(0), keyPasswords.get(0));
+ }
+
+ @Test
+ public void testSameKeyAndKeystorePasswordWithKeystorePasswordSpecified() throws CommandLineParseException {
+ String testPassword = "testPassword";
+ tlsToolkitStandaloneCommandLine.parse("-S", testPassword);
+ List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
+ assertEquals(1, keyStorePasswords.size());
+ assertEquals(testPassword, keyStorePasswords.get(0));
+ assertEquals(keyStorePasswords, tlsToolkitStandaloneCommandLine.getKeyPasswords());
+ }
+
+ @Test
+ public void testSameKeyAndKeystorePasswordWithKeyPasswordSpecified() throws CommandLineParseException {
+ String testPassword = "testPassword";
+ tlsToolkitStandaloneCommandLine.parse("-K", testPassword);
+ List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
+ assertNotEquals(tlsToolkitStandaloneCommandLine.getKeyStorePasswords(), keyPasswords);
+ assertEquals(1, keyPasswords.size());
+ assertEquals(testPassword, keyPasswords.get(0));
+ }
+
+ @Test
+ public void testKeyStorePasswordArg() throws CommandLineParseException {
+ String testPassword = "testPassword";
+ tlsToolkitStandaloneCommandLine.parse("-S", testPassword);
+ List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
+ assertEquals(1, keyStorePasswords.size());
+ assertEquals(testPassword, keyStorePasswords.get(0));
+ }
+
+ @Test
+ public void testMultipleKeystorePasswordArgs() throws CommandLineParseException {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-S", testPassword1, "-S", testPassword2);
+ List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
+ assertEquals(2, keyStorePasswords.size());
+ assertEquals(testPassword1, keyStorePasswords.get(0));
+ assertEquals(testPassword2, keyStorePasswords.get(1));
+ }
+
+ @Test
+ public void testMultipleKeystorePasswordArgSingleHost() {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-S", testPassword1, "-S", testPassword2);
+ fail("Expected error with mismatch keystore password number");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testKeyPasswordArg() throws CommandLineParseException {
+ String testPassword = "testPassword";
+ tlsToolkitStandaloneCommandLine.parse("-K", testPassword);
+ List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
+ assertEquals(1, keyPasswords.size());
+ assertEquals(testPassword, keyPasswords.get(0));
+ }
+
+ @Test
+ public void testMultipleKeyPasswordArgs() throws CommandLineParseException {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-K", testPassword1, "-K", testPassword2);
+ List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
+ assertEquals(2, keyPasswords.size());
+ assertEquals(testPassword1, keyPasswords.get(0));
+ assertEquals(testPassword2, keyPasswords.get(1));
+ }
+
+ @Test
+ public void testMultipleKeyPasswordArgSingleHost() {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-K", testPassword1, "-K", testPassword2);
+ fail("Expected error with mismatch keystore password number");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS.ordinal(), e.getExitCode());
+ }
+ }
+
+ @Test
+ public void testTruststorePasswordArg() throws CommandLineParseException {
+ String testPassword = "testPassword";
+ tlsToolkitStandaloneCommandLine.parse("-P", testPassword);
+ List trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords();
+ assertEquals(1, trustStorePasswords.size());
+ assertEquals(testPassword, trustStorePasswords.get(0));
+ }
+
+ @Test
+ public void testMultipleTruststorePasswordArgs() throws CommandLineParseException {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-P", testPassword1, "-P", testPassword2);
+ List trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords();
+ assertEquals(2, trustStorePasswords.size());
+ assertEquals(testPassword1, trustStorePasswords.get(0));
+ assertEquals(testPassword2, trustStorePasswords.get(1));
+ }
+
+ @Test
+ public void testMultipleTruststorePasswordArgSingleHost() {
+ String testPassword1 = "testPassword1";
+ String testPassword2 = "testPassword2";
+ try {
+ tlsToolkitStandaloneCommandLine.parse("-P", testPassword1, "-P", testPassword2);
+ fail("Expected error with mismatch keystore password number");
+ } catch (CommandLineParseException e) {
+ assertEquals(ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS.ordinal(), e.getExitCode());
+ }
+ }
+
+ private Properties getProperties() throws IOException {
+ NiFiPropertiesWriter niFiPropertiesWriter = tlsToolkitStandaloneCommandLine.getNiFiPropertiesWriterFactory().create();
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ niFiPropertiesWriter.writeNiFiProperties(byteArrayOutputStream);
+ Properties properties = new Properties();
+ properties.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
+ return properties;
+ }
+}
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
new file mode 100644
index 0000000000..6746c40a36
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.tls.standalone;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
+import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
+import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
+import org.apache.nifi.toolkit.tls.util.TlsHelperTest;
+import org.apache.nifi.util.NiFiProperties;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.Permission;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Properties;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class TlsToolkitStandaloneTest {
+ public static final String NIFI_FAKE_PROPERTY = "nifi.fake.property";
+ public static final String FAKE_VALUE = "fake value";
+ public static final String TEST_NIFI_PROPERTIES = "src/test/resources/localhost/nifi.properties";
+ private SecurityManager originalSecurityManager;
+
+ private File tempDir;
+
+ @Before
+ public void setup() throws IOException {
+ tempDir = File.createTempFile("tls-test", UUID.randomUUID().toString());
+ if (!tempDir.delete()) {
+ throw new IOException("Couldn't delete " + tempDir);
+ }
+
+ if (!tempDir.mkdirs()) {
+ throw new IOException("Couldn't make directory " + tempDir);
+ }
+
+ originalSecurityManager = System.getSecurityManager();
+ // [see http://stackoverflow.com/questions/309396/java-how-to-test-methods-that-call-system-exit#answer-309427]
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public void checkPermission(Permission perm) {
+ // Noop
+ }
+
+ @Override
+ public void checkPermission(Permission perm, Object context) {
+ // Noop
+ }
+
+ @Override
+ public void checkExit(int status) {
+ super.checkExit(status);
+ throw new ExitException(status);
+ }
+ });
+ }
+
+ @After
+ public void teardown() throws IOException {
+ System.setSecurityManager(originalSecurityManager);
+ FileUtils.deleteDirectory(tempDir);
+ }
+
+ @Test
+ public void testBadParse() {
+ runAndAssertExitCode(ExitCode.ERROR_PARSING_COMMAND_LINE.ordinal(), "--unknownArgument");
+ }
+
+ @Test
+ public void testHelp() {
+ runAndAssertExitCode(ExitCode.HELP.ordinal(), "-h");
+ runAndAssertExitCode(ExitCode.HELP.ordinal(), "--help");
+ }
+
+ @Test
+ public void testDirOutput() throws Exception {
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath());
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertNull(nifiProperties.get("nifi.fake.property"));
+ assertEquals(nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD), nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
+ }
+
+ @Test
+ public void testDifferentArg() throws Exception {
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-g");
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertNull(nifiProperties.get("nifi.fake.property"));
+ assertNotEquals(nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD), nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
+ }
+
+ @Test
+ public void testFileArg() throws Exception {
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-f", TEST_NIFI_PROPERTIES);
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertEquals(FAKE_VALUE, nifiProperties.get(NIFI_FAKE_PROPERTY));
+ }
+
+ @Test
+ public void testHostnamesArgument() throws Exception {
+ String nifi1 = "nifi1";
+ String nifi2 = "nifi2";
+ String nifi3 = "nifi3";
+
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-n", nifi1 + "," + nifi2 + "," + nifi3);
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ checkHostDirAndReturnNifiProperties(nifi1, x509Certificate);
+ checkHostDirAndReturnNifiProperties(nifi2, x509Certificate);
+ checkHostDirAndReturnNifiProperties(nifi3, x509Certificate);
+ }
+
+ @Test
+ public void testKeyPasswordArg() throws Exception {
+ String testKey = "testKey";
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-K", testKey);
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertEquals(testKey, nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
+ }
+
+ @Test
+ public void testKeyStorePasswordArg() throws Exception {
+ String testKeyStore = "testKeyStore";
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-S", testKeyStore);
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertEquals(testKeyStore, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD));
+ }
+
+ @Test
+ public void testTrustStorePasswordArg() throws Exception {
+ String testTrustStore = "testTrustStore";
+ runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-P", testTrustStore);
+ X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
+
+ Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
+ assertEquals(testTrustStore, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
+ }
+
+ private X509Certificate checkLoadCertPrivateKey(String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, CertificateException {
+ KeyPair keyPair = TlsHelperTest.loadKeyPair(new File(tempDir, TlsToolkitStandalone.NIFI_KEY + ".key"));
+
+ assertEquals(algorithm, keyPair.getPrivate().getAlgorithm());
+ assertEquals(algorithm, keyPair.getPublic().getAlgorithm());
+
+ X509Certificate x509Certificate = TlsHelperTest.loadCertificate(new File(tempDir, TlsToolkitStandalone.NIFI_CERT + ".pem"));
+ assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey());
+ return x509Certificate;
+ }
+
+ private Properties checkHostDirAndReturnNifiProperties(String hostname, X509Certificate rootCert) throws Exception {
+ File hostDir = new File(tempDir, hostname);
+ Properties nifiProperties = new Properties();
+ try (InputStream inputStream = new FileInputStream(new File(hostDir, TlsToolkitStandalone.NIFI_PROPERTIES))) {
+ nifiProperties.load(inputStream);
+ }
+
+ String trustStoreType = nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
+ KeyStore trustStore = KeyStore.getInstance(trustStoreType);
+ try (InputStream inputStream = new FileInputStream(new File(hostDir, "truststore." + trustStoreType))) {
+ trustStore.load(inputStream, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray());
+ }
+
+ Certificate certificate = trustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT);
+ assertEquals(rootCert, certificate);
+
+ String keyStoreType = nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
+ String keyStoreFilename = BaseCommandLine.KEYSTORE + keyStoreType;
+ File keyStoreFile = new File(hostDir, keyStoreFilename);
+ assertEquals(keyStoreFilename, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE));
+
+ KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+ try (InputStream inputStream = new FileInputStream(keyStoreFile)) {
+ keyStore.load(inputStream, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray());
+ }
+
+ char[] keyPassword = nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray();
+
+ KeyStore.Entry entry = keyStore.getEntry(TlsToolkitStandalone.NIFI_KEY, new KeyStore.PasswordProtection(keyPassword));
+ assertEquals(KeyStore.PrivateKeyEntry.class, entry.getClass());
+
+ KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;
+
+ Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
+
+ assertEquals(2, certificateChain.length);
+ assertEquals(rootCert, certificateChain[1]);
+ certificateChain[1].verify(rootCert.getPublicKey());
+ certificateChain[0].verify(rootCert.getPublicKey());
+ return nifiProperties;
+ }
+
+ private void runAndAssertExitCode(int exitCode, String... args) {
+ try {
+ TlsToolkitStandaloneCommandLine.main(args);
+ fail("Expecting exit code: " + exitCode);
+ } catch (ExitException e) {
+ assertEquals(exitCode, e.getExitCode());
+ }
+ }
+
+ private static class ExitException extends SecurityException {
+ private final int exitCode;
+
+ public ExitException(int exitCode) {
+ this.exitCode = exitCode;
+ }
+
+ public int getExitCode() {
+ return exitCode;
+ }
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/PasswordUtilTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/PasswordUtilTest.java
new file mode 100644
index 0000000000..e79815ae5c
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/PasswordUtilTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.tls.util;
+
+import org.junit.Test;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Base64;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+public class PasswordUtilTest {
+ @Test
+ public void testGeneratePassword() {
+ SecureRandom secureRandom = mock(SecureRandom.class);
+ PasswordUtil passwordUtil = new PasswordUtil(secureRandom);
+ int value = 8675309;
+ doAnswer(invocation -> {
+ byte[] bytes = (byte[]) invocation.getArguments()[0];
+ assertEquals(32, bytes.length);
+ Arrays.fill(bytes, (byte) 0);
+ byte[] val = ByteBuffer.allocate(Long.BYTES).putLong(value).array();
+ System.arraycopy(val, 0, bytes, bytes.length - val.length, val.length);
+ return null;
+ }).when(secureRandom).nextBytes(any(byte[].class));
+ String expected = Base64.getEncoder().encodeToString(BigInteger.valueOf(Integer.valueOf(value).longValue()).toByteArray()).split("=")[0];
+ String actual = passwordUtil.generatePassword();
+ assertEquals(expected, actual);
+ }
+}
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
new file mode 100644
index 0000000000..a900a4f07a
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.tls.util;
+
+import org.apache.nifi.security.util.CertificateUtils;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class TlsHelperTest {
+ private int days;
+
+ private int keySize;
+
+ private String keyPairAlgorithm;
+
+ private String signingAlgorithm;
+
+ private String keyStoreType;
+
+ private SecureRandom secureRandom;
+
+ private KeyPairGenerator keyPairGenerator;
+
+ public static KeyPair loadKeyPair(Reader reader) throws IOException {
+ try (PEMParser pemParser = new PEMParser(reader)) {
+ Object object = pemParser.readObject();
+ assertEquals(PEMKeyPair.class, object.getClass());
+ return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) object);
+ }
+ }
+
+ public static KeyPair loadKeyPair(File file) throws IOException {
+ return loadKeyPair(new FileReader(file));
+ }
+
+ public static X509Certificate loadCertificate(Reader reader) throws IOException, CertificateException {
+ try (PEMParser pemParser = new PEMParser(reader)) {
+ Object object = pemParser.readObject();
+ assertEquals(X509CertificateHolder.class, object.getClass());
+ return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate((X509CertificateHolder) object);
+ }
+ }
+
+ public static X509Certificate loadCertificate(File file) throws IOException, CertificateException {
+ return loadCertificate(new FileReader(file));
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @Before
+ public void setup() throws NoSuchAlgorithmException {
+ days = 360;
+ keySize = 2048;
+ keyPairAlgorithm = "RSA";
+ signingAlgorithm = "SHA1WITHRSA";
+ keyStoreType = KeyStore.getDefaultType();
+ secureRandom = mock(SecureRandom.class);
+ keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm);
+ keyPairGenerator.initialize(keySize);
+ }
+
+ private Date inFuture(int days) {
+ return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days));
+ }
+
+ @Test
+ public void testGenerateSelfSignedCert() throws GeneralSecurityException, IOException, OperatorCreationException {
+ String dn = "CN=testDN,O=testOrg";
+
+ X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), dn, signingAlgorithm, days);
+
+ 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(dn, x509Certificate.getIssuerDN().getName());
+ assertEquals(signingAlgorithm, x509Certificate.getSigAlgName());
+ assertEquals(keyPairAlgorithm, x509Certificate.getPublicKey().getAlgorithm());
+
+ x509Certificate.checkValidity();
+ }
+
+ @Test
+ public void testIssueCert() throws IOException, CertificateException, NoSuchAlgorithmException, OperatorCreationException, 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.getSubjectDN().toString());
+ assertEquals(issuer.getSubjectDN().toString(), x509Certificate.getIssuerDN().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());
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/nifi.properties b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/nifi.properties
new file mode 100644
index 0000000000..41b091c8ab
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/nifi.properties
@@ -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.
+#
+
+# Core Properties #
+nifi.fake.property=fake value
+nifi.version=1.0.0-SNAPSHOT
+nifi.flow.configuration.file=./conf/flow.xml.gz
+nifi.flow.configuration.archive.dir=./conf/archive/
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=500 ms
+nifi.administrative.yield.duration=30 sec
+# If a component has no work to do (is "bored"), how long should we wait before checking again for work?
+nifi.bored.yield.duration=10 millis
+
+nifi.authorizer.configuration.file=./conf/authorizers.xml
+nifi.login.identity.provider.configuration.file=./conf/login-identity-providers.xml
+nifi.templates.directory=./conf/templates
+nifi.ui.banner.text=
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=./lib
+nifi.nar.working.directory=./work/nar/
+nifi.documentation.working.directory=./work/docs/components
+
+####################
+# State Management #
+####################
+nifi.state.management.configuration.file=./conf/state-management.xml
+# The ID of the local state provider
+nifi.state.management.provider.local=local-provider
+# The ID of the cluster-wide state provider. This will be ignored if NiFi is not clustered but must be populated if running in a cluster.
+nifi.state.management.provider.cluster=zk-provider
+# Specifies whether or not this instance of NiFi should run an embedded ZooKeeper server
+nifi.state.management.embedded.zookeeper.start=false
+# Properties file that provides the ZooKeeper properties to use if is set to true
+nifi.state.management.embedded.zookeeper.properties=./conf/zookeeper.properties
+
+
+# H2 Settings
+nifi.database.directory=./database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.flowfile.repository.implementation=org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
+nifi.flowfile.repository.directory=./flowfile_repository
+nifi.flowfile.repository.partitions=256
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.flowfile.repository.always.sync=false
+
+nifi.swap.manager.implementation=org.apache.nifi.controller.FileSystemSwapManager
+nifi.queue.swap.threshold=20000
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.repository.implementation=org.apache.nifi.controller.repository.FileSystemRepository
+nifi.content.claim.max.appendable.size=10 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=./content_repository
+nifi.content.repository.archive.max.retention.period=12 hours
+nifi.content.repository.archive.max.usage.percentage=50%
+nifi.content.repository.archive.enabled=true
+nifi.content.repository.always.sync=false
+nifi.content.viewer.url=/nifi-content-viewer/
+
+# Provenance Repository Properties
+nifi.provenance.repository.implementation=org.apache.nifi.provenance.PersistentProvenanceRepository
+
+# Persistent Provenance Repository Properties
+nifi.provenance.repository.directory.default=./provenance_repository
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+nifi.provenance.repository.query.threads=2
+nifi.provenance.repository.index.threads=1
+nifi.provenance.repository.compress.on.rollover=true
+nifi.provenance.repository.always.sync=false
+nifi.provenance.repository.journal.count=16
+# Comma-separated list of fields. Fields that are not indexed will not be searchable. Valid fields are:
+# EventType, FlowFileUUID, Filename, TransitURI, ProcessorID, AlternateIdentifierURI, Relationship, Details
+nifi.provenance.repository.indexed.fields=EventType, FlowFileUUID, Filename, ProcessorID, Relationship
+# FlowFile Attributes that should be indexed and made searchable. Some examples to consider are filename, uuid, mime.type
+nifi.provenance.repository.indexed.attributes=
+# Large values for the shard size will result in more Java heap usage when searching the Provenance Repository
+# but should provide better performance
+nifi.provenance.repository.index.shard.size=500 MB
+# Indicates the maximum length that a FlowFile attribute can be when retrieving a Provenance Event from
+# the repository. If the length of any attribute exceeds this value, it will be truncated when the event is retrieved.
+nifi.provenance.repository.max.attribute.length=65536
+
+# Volatile Provenance Respository Properties
+nifi.provenance.repository.buffer.size=100000
+
+# Component Status Repository
+nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
+nifi.components.status.repository.buffer.size=1440
+nifi.components.status.snapshot.frequency=1 min
+
+# Site to Site properties
+nifi.remote.input.host=
+nifi.remote.input.secure=false
+nifi.remote.input.socket.port=
+nifi.remote.input.http.enabled=true
+nifi.remote.input.http.transaction.ttl=30 sec
+
+# web properties #
+nifi.web.war.directory=./lib
+nifi.web.http.host=
+nifi.web.http.port=8080
+nifi.web.https.host=
+nifi.web.https.port=
+nifi.web.jetty.working.directory=./work/jetty
+nifi.web.jetty.threads=200
+
+# security properties #
+nifi.sensitive.props.key=
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=./conf/localhost.jks
+nifi.security.keystoreType=jks
+nifi.security.keystorePasswd=vidmcgplvih3dn2fqjnckns77g
+nifi.security.keyPasswd=qgs57rmnot6p8gm97pfjutnu5g
+nifi.security.truststore=./conf/truststore.jks
+nifi.security.truststoreType=jks
+nifi.security.truststorePasswd=t7rmn1fg8np2ck1sduqdd85opv
+nifi.security.needClientAuth=
+nifi.security.user.authorizer=file-provider
+nifi.security.user.login.identity.provider=
+nifi.security.ocsp.responder.url=
+nifi.security.ocsp.responder.pemEncodedCertificate=
+
+# cluster common properties (all nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=10
+nifi.cluster.node.event.history.size=25
+nifi.cluster.node.connection.timeout=5 sec
+nifi.cluster.node.read.timeout=5 sec
+nifi.cluster.firewall.file=
+
+# How long a request should be allowed to hold a 'lock' on a component. #
+nifi.cluster.request.replication.claim.timeout=15 secs
+
+# zookeeper properties, used for cluster management #
+nifi.zookeeper.connect.string=
+nifi.zookeeper.connect.timeout=3 secs
+nifi.zookeeper.session.timeout=3 secs
+nifi.zookeeper.root.node=/nifi
+
+# kerberos #
+nifi.kerberos.krb5.file=
+nifi.kerberos.service.principal=
+nifi.kerberos.keytab.location=
+nifi.kerberos.authentication.expiration=12 hours
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/truststore.jks b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/truststore.jks
new file mode 100644
index 0000000000..8d0b4de755
Binary files /dev/null and b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/localhost/truststore.jks differ
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.crt b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.crt
new file mode 100644
index 0000000000..98bb577bf0
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAkGgAwIBAgIGAVXgcI3oMA0GCSqGSIb3DQEBCwUAMC0xFTATBgNVBAMM
+DG5pZmkucm9vdC5jYTEUMBIGA1UECwwLYXBhY2hlLm5pZmkwHhcNMTYwNzEyMTg0
+ODQwWhcNMTcwNzEyMTg0ODQwWjAtMRUwEwYDVQQDDAxuaWZpLnJvb3QuY2ExFDAS
+BgNVBAsMC2FwYWNoZS5uaWZpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuvjnFOoo5fYaq6vMiP2cmoSj3pzpLjvVPsQGP4OH7vy1f3C6IpWkrXjy/kst
+sP116WC4PdnLJ0FF4ogR/4b5XpeFuLXrmsGk3dWQac3zunZ1AOvJKflQqCiOfIG8
+9uMknJe0enHq4fhjy4qZrwOmMozGayWa5iRjGfY7mS44luYQvn9dz9G4IIoGVpxJ
+rltHDrl0fMcVqY6Kfxb//6jvo74mW0E85hju45JHseEUMHMsZaJAYfKg2dRQryE1
+UIW5Gai2Io17HjWIqWUnht5/CvJG3MKo/3XnVuwyVnbnt5bey+N+NVVsMKTig3m7
+PGK2R6fycKupIMMVZ60FbsLo7wIDAQABo38wfTAOBgNVHQ8BAf8EBAMCAf4wDAYD
+VR0TBAUwAwEB/zAdBgNVHQ4EFgQUxPKyo9LTvN7jTJ1x2jVUe4nNz1swHwYDVR0j
+BBgwFoAUxPKyo9LTvN7jTJ1x2jVUe4nNz1swHQYDVR0lBBYwFAYIKwYBBQUHAwIG
+CCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBcMRAlnWDco5WbjTV0uSxmS7Dh
+RxYgt7YguqA/tdfcn6hhJZ2ZZm5By6nwP4aGFY45tOv2NsjHgchgfJ6Osl3ZxGmF
++JrXW7mveOwZIfZzM2yFCusgkzrGOAWNL2G+lbXpRCnsTuJL6jUbRE1cFwU+iUYo
+E8Xfo6XZVNlzeab4WNerAPGssftV9C+0ya9+5+hFmBhzpGkpn5EVicxLAX6fI4K6
+N4ZCBU8DYQilYkE0xgxySSz7Ia1vo8D7Tr30CxoXGsqRLXanW0Jw1wVsLVheTLZw
+W3gp2XKOaP/BMFbIFw2iB7REeCao14u9pRAKEodpH+fpUosbFlpd8ysRvFJP
+-----END CERTIFICATE-----
diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.key b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.key
new file mode 100644
index 0000000000..64f9bb722c
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/rootCert.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAuvjnFOoo5fYaq6vMiP2cmoSj3pzpLjvVPsQGP4OH7vy1f3C6
+IpWkrXjy/kstsP116WC4PdnLJ0FF4ogR/4b5XpeFuLXrmsGk3dWQac3zunZ1AOvJ
+KflQqCiOfIG89uMknJe0enHq4fhjy4qZrwOmMozGayWa5iRjGfY7mS44luYQvn9d
+z9G4IIoGVpxJrltHDrl0fMcVqY6Kfxb//6jvo74mW0E85hju45JHseEUMHMsZaJA
+YfKg2dRQryE1UIW5Gai2Io17HjWIqWUnht5/CvJG3MKo/3XnVuwyVnbnt5bey+N+
+NVVsMKTig3m7PGK2R6fycKupIMMVZ60FbsLo7wIDAQABAoIBAAzqSpQQRLj10gvP
+tzYzRpEJ3oPvFNBjw6wtQD086bPhcuwVrlXbPl/ZPffA26whJfbz/mYPWFAi2x/1
+xECBY1JcZxlGP7sV6zyDlxEn79EOg7CHmQK0PRUQkB8bmyD2ub2zYP0gR1hKnyId
+NdZ4Cw/s13vpQhTpqIrPjnhbT/9kUMMxdWrN36/o/TL18VZMD2E3WGMzX+lbKNLs
+QvfAGUpbfbPtmUPFezbDoL5Pqkj4KZ4/5l1RfqQiamNijwxxm/oGbDh8AuyxvnoR
+yxDc9OWEoepEb72Qdec4LJnaYNXUtI2rg1QAbcXzuRLtv8o0J0gpVdqm2ROyf3tF
+aI+Fk6kCgYEA3jZM5Vta7BHBT1HmYGZQOuQHW+WD7CQgLeXCCB4O4xq/7l7iG2hM
+3pt65TGae78KHtJ2LciWrKRuNTruBoKax6MFw16ZfSQSFi0Knn+EbcxcmPQ2i/VO
+xpdOVAP9LyEKKGTgH2FTE7RkiGt/oK0eJrYHpHMpAs0jRyCIYGPmt10CgYEA12bg
+fuMAhiy3kv/IYtl3W7doGFVzu2C59VWxmkp/WvGQyrVUCURp5PNjlb0LIM4DZnKR
+/zCxoP0Sf1TuVmORO1Z+Z0NKJoowYyveScTnn+RqKw7ssx9KgGRXXHuWlDcBv5lI
+MP5vYs/5J47arZdbjI3Nbirvv8y4kHt1LkrPWLsCgYB0qV9vnqm6fIvXv/DKNYzz
+jhoK9hFvnXvDfPeEWXuJYdbYJ7pflz+cM1avE36+bwq6KdZfrQrl8qHlkl26z0DB
+hOYWrwi4OYLBX75OfjYhiwQmTTiB/DTycCdwVnPLFXaGxIciEm+STcfmE0H7Mkg9
+HJ4giVFDpj1aL0tKB8juBQKBgBED7m33jp4KwEGbdP/h/9y94zow3eCKZoYry8jN
+Y7wSYwawRkApKFhOpao5cbyYqYoZONE4zn9SHnjyg5VNbjaKwZd0qFHdDq920qtv
+I4Ds8ToPhsErkp4Lzx7eIGn64md5O0urfa8HkL4AOeQGldPHi9fNCn1TNa0sI3iR
+rklrAoGBALjnqDQFBuT3io0locXPHw6UbIAbD6Pm8Z1+HgK/hskyX/9bmnJn3oyz
+jxziJhpy96RoLu0sxBxo5fjHGc54Qn6Ccivu4oM+4rktfALhLuFUfie8tr6nZlnm
+mxRyNqJxD8L5Ttb66TiC9snf98MYAqsKD03fPYUAdpPlIbn+xS44
+-----END RSA PRIVATE KEY-----
diff --git a/nifi-toolkit/pom.xml b/nifi-toolkit/pom.xml
new file mode 100644
index 0000000000..4f9e7d1ee7
--- /dev/null
+++ b/nifi-toolkit/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+ org.apache.nifi
+ nifi
+ 1.0.0-SNAPSHOT
+
+ org.apache.nifi
+ nifi-toolkit
+ pom
+
+ nifi-toolkit-tls
+ nifi-toolkit-assembly
+
+
diff --git a/pom.xml b/pom.xml
index 602ac22605..d879adf03e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@ language governing permissions and limitations under the License. -->
nifi-docsnifi-maven-archetypesnifi-external
+ nifi-toolkithttp://nifi.apache.org
@@ -868,6 +869,11 @@ language governing permissions and limitations under the License. -->
nifi-bootstrap1.0.0-SNAPSHOT
+
+ org.apache.nifi
+ nifi-toolkit-tls
+ 1.0.0-SNAPSHOT
+ org.apache.nifinifi-resources
@@ -1257,6 +1263,11 @@ language governing permissions and limitations under the License. -->
nifi-hbase-client-service-api1.0.0-SNAPSHOT
+
+ org.apache.nifi
+ nifi-assembly
+ 1.0.0-SNAPSHOT
+ com.jayway.jsonpathjson-path