NIFI-8511 Added KeyStore implementation of KeyProvider
- KeyStoreKeyProvider supports PKCS12 and BCFKS
- Refactored KeyProvider and implementations to nifi-security-kms
- Updated Admin Guide and User Guide with KeyStoreKeyProvider details
NIFI-8511 Improved documentation and streamlined several methods
Signed-off-by: Nathan Gough <thenatog@gmail.com>
This closes #5110.
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 329f916..91b9f73 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -102,6 +102,7 @@
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_ID = "nifi.content.repository.encryption.key.id";
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.content.repository.encryption.key.provider.implementation";
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.content.repository.encryption.key.provider.location";
+ public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.content.repository.encryption.key.provider.password";
// flowfile repository properties
public static final String FLOWFILE_REPOSITORY_IMPLEMENTATION = "nifi.flowfile.repository.implementation";
@@ -113,6 +114,7 @@
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID = "nifi.flowfile.repository.encryption.key.id";
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.flowfile.repository.encryption.key.provider.implementation";
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.flowfile.repository.encryption.key.provider.location";
+ public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.flowfile.repository.encryption.key.provider.password";
public static final String FLOWFILE_SWAP_MANAGER_IMPLEMENTATION = "nifi.swap.manager.implementation";
public static final String QUEUE_SWAP_THRESHOLD = "nifi.queue.swap.threshold";
@@ -135,6 +137,7 @@
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_ID = "nifi.provenance.repository.encryption.key.id";
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.provenance.repository.encryption.key.provider.implementation";
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.provenance.repository.encryption.key.provider.location";
+ public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.provenance.repository.encryption.key.provider.password";
public static final String PROVENANCE_REPO_DEBUG_FREQUENCY = "nifi.provenance.repository.debug.frequency";
// status repository properties
@@ -1614,8 +1617,7 @@
/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
- * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
- * {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
+ * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
@@ -1746,8 +1748,7 @@
/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
- * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
- * {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
+ * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
@@ -1778,8 +1779,7 @@
/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
- * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
- * {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
+ * {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
diff --git a/nifi-commons/nifi-security-kms/pom.xml b/nifi-commons/nifi-security-kms/pom.xml
new file mode 100644
index 0000000..a11d3a9
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-security-kms</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.15</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java
new file mode 100644
index 0000000..3157c0d
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.nifi.security.kms.reader.StandardFileBasedKeyReader;
+import org.apache.nifi.security.kms.reader.FileBasedKeyReader;
+
+import java.nio.file.Path;
+import javax.crypto.SecretKey;
+
+/**
+ * File Based Key Provider reads encrypted Secret Keys from a properties file containing one or more entries
+ */
+public class FileBasedKeyProvider extends StaticKeyProvider {
+ private static final FileBasedKeyReader READER = new StandardFileBasedKeyReader();
+
+ public FileBasedKeyProvider(final Path location, final SecretKey rootKey) {
+ super(READER.readSecretKeys(location, rootKey));
+ }
+}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProvider.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProvider.java
similarity index 73%
rename from nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProvider.java
rename to nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProvider.java
index f842a9b..cc9a1c9 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProvider.java
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProvider.java
@@ -19,10 +19,8 @@
import java.security.KeyManagementException;
import java.util.List;
import javax.crypto.SecretKey;
-import javax.naming.OperationNotSupportedException;
public interface KeyProvider {
-
/**
* Returns the key identified by this ID or throws an exception if one is not available.
*
@@ -46,17 +44,4 @@
* @return a List of keyIds (empty list if none are available)
*/
List<String> getAvailableKeyIds();
-
- /**
- * Adds the key to the provider and associates it with the given ID. Some implementations may not allow this operation.
- *
- * @param keyId the key identifier
- * @param key the key
- * @return true if the key was successfully added
- * @throws OperationNotSupportedException if this implementation doesn't support adding keys
- * @throws KeyManagementException if the key is invalid, the ID conflicts, etc.
- */
- boolean addKey(String keyId, SecretKey key) throws OperationNotSupportedException, KeyManagementException;
-
- // TODO: Add #getActiveKeyId() method
}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java
new file mode 100644
index 0000000..3cf6886
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.nifi.security.kms.reader.KeyReaderException;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Key Provider Factory
+ */
+public class KeyProviderFactory {
+ private static final String SECRET_KEY_ALGORITHM = "AES";
+
+ /**
+ * Get Key Provider based on Configuration
+ *
+ * @param configuration Key Provider Configuration
+ * @return Key Provider
+ */
+ public static KeyProvider getKeyProvider(final KeyProviderConfiguration<?> configuration) {
+ KeyProvider keyProvider;
+
+ if (configuration instanceof StaticKeyProviderConfiguration) {
+ final StaticKeyProviderConfiguration providerConfiguration = (StaticKeyProviderConfiguration) configuration;
+ final Map<String, SecretKey> secretKeys;
+ try {
+ secretKeys = getSecretKeys(providerConfiguration.getKeys());
+ keyProvider = new StaticKeyProvider(secretKeys);
+ } catch (final DecoderException e) {
+ throw new KeyReaderException("Decoding Hexadecimal Secret Keys failed", e);
+ }
+ } else if (configuration instanceof FileBasedKeyProviderConfiguration) {
+ final FileBasedKeyProviderConfiguration providerConfiguration = (FileBasedKeyProviderConfiguration) configuration;
+ final Path keyProviderPath = Paths.get(providerConfiguration.getLocation());
+ keyProvider = new FileBasedKeyProvider(keyProviderPath, providerConfiguration.getRootKey());
+ } else if (configuration instanceof KeyStoreKeyProviderConfiguration) {
+ final KeyStoreKeyProviderConfiguration providerConfiguration = (KeyStoreKeyProviderConfiguration) configuration;
+ final KeyStore keyStore = providerConfiguration.getKeyStore();
+ keyProvider = new KeyStoreKeyProvider(keyStore, providerConfiguration.getKeyPassword());
+ } else {
+ throw new UnsupportedOperationException(String.format("Key Provider [%s] not supported", configuration.getKeyProviderClass().getName()));
+ }
+
+ return keyProvider;
+ }
+
+ private static Map<String, SecretKey> getSecretKeys(final Map<String, String> keys) throws DecoderException {
+ final Map<String, SecretKey> secretKeys = new HashMap<>();
+
+ for (final Map.Entry<String, String> keyEntry : keys.entrySet()) {
+ final byte[] encodedSecretKey = Hex.decodeHex(keyEntry.getValue());
+ final SecretKey secretKey = new SecretKeySpec(encodedSecretKey, SECRET_KEY_ALGORITHM);
+ secretKeys.put(keyEntry.getKey(), secretKey);
+ }
+
+ return secretKeys;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyStoreKeyProvider.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyStoreKeyProvider.java
new file mode 100644
index 0000000..ad777d1
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/KeyStoreKeyProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.nifi.security.kms.reader.KeyReaderException;
+
+import javax.crypto.SecretKey;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * KeyStore implementation of Key Provider
+ */
+public class KeyStoreKeyProvider extends StaticKeyProvider {
+ /**
+ * KeyStore Key Provider constructor with KeyStore and password used to read Secret Key entries
+ *
+ * @param keyStore KeyStore
+ * @param keyPassword Password for reading Secret Key entries
+ */
+ public KeyStoreKeyProvider(final KeyStore keyStore, final char[] keyPassword) {
+ super(readSecretKeys(requireNonNull(keyStore, "KeyStore required"), requireNonNull(keyPassword, "Password required")));
+ }
+
+ private static Map<String, SecretKey> readSecretKeys(final KeyStore keyStore, final char[] keyPassword) throws KeyReaderException {
+ final Map<String, SecretKey> secretKeys = new HashMap<>();
+
+ try {
+ final Enumeration<String> aliases = keyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ final String alias = aliases.nextElement();
+ final Key key = keyStore.getKey(alias, keyPassword);
+ if (key instanceof SecretKey) {
+ final SecretKey secretKey = (SecretKey) key;
+ secretKeys.put(alias, secretKey);
+ }
+ }
+ } catch (final GeneralSecurityException e) {
+ throw new KeyReaderException("Reading KeyStore failed", e);
+ }
+
+ return secretKeys;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java
new file mode 100644
index 0000000..2445194
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import java.security.KeyManagementException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.crypto.SecretKey;
+
+/**
+ * Static Key Provider stores Secret Keys in memory based on initialized configuration properties
+ */
+public class StaticKeyProvider implements KeyProvider {
+ private final Map<String, SecretKey> keys;
+
+ /**
+ * Static Key Provider constructor of Map of Key Identifier to Secret Key
+ *
+ * @param keys Map of Key Identifier to Secret Key
+ */
+ public StaticKeyProvider(final Map<String, SecretKey> keys) {
+ this.keys = Collections.unmodifiableMap(Objects.requireNonNull(keys, "Keys required"));
+ }
+
+ /**
+ * Returns the key identified by this ID or throws an exception if one is not available.
+ *
+ * @param keyId the key identifier
+ * @return the key
+ * @throws KeyManagementException Thrown when Secret Key not found for Key Identifier
+ */
+ @Override
+ public SecretKey getKey(final String keyId) throws KeyManagementException {
+ final SecretKey secretKey = keys.get(keyId);
+ if (secretKey == null) {
+ throw new KeyManagementException(String.format("Secret Key [%s] not found", keyId));
+ }
+ return secretKey;
+ }
+
+ /**
+ * Returns true if the key exists and is available. Null or empty IDs will return false.
+ *
+ * @param keyId the key identifier
+ * @return true if the key can be used
+ */
+ @Override
+ public boolean keyExists(final String keyId) {
+ return keys.containsKey(keyId);
+ }
+
+ /**
+ * Returns a singleton list of the available key identifier.
+ *
+ * @return a List containing the {@code KEY_ID}
+ */
+ @Override
+ public List<String> getAvailableKeyIds() {
+ return new ArrayList<>(keys.keySet());
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/FileBasedKeyProviderConfiguration.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/FileBasedKeyProviderConfiguration.java
new file mode 100644
index 0000000..b4da6c9
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/FileBasedKeyProviderConfiguration.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.configuration;
+
+import org.apache.nifi.security.kms.FileBasedKeyProvider;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Configuration for File-based Key Provider
+ */
+public class FileBasedKeyProviderConfiguration implements KeyProviderConfiguration<FileBasedKeyProvider> {
+ private final String location;
+
+ private final SecretKey rootKey;
+
+ public FileBasedKeyProviderConfiguration(final String location, final SecretKey rootKey) {
+ this.location = location;
+ this.rootKey = rootKey;
+ }
+
+ @Override
+ public Class<FileBasedKeyProvider> getKeyProviderClass() {
+ return FileBasedKeyProvider.class;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public SecretKey getRootKey() {
+ return rootKey;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyProviderConfiguration.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyProviderConfiguration.java
new file mode 100644
index 0000000..e6e7f14
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyProviderConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.configuration;
+
+import org.apache.nifi.security.kms.KeyProvider;
+
+/**
+ * Key Provider Configuration
+ */
+public interface KeyProviderConfiguration<T extends KeyProvider> {
+ /**
+ * Get Key Provider Class
+ *
+ * @return Key Provider implementation Class
+ */
+ Class<T> getKeyProviderClass();
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyStoreKeyProviderConfiguration.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyStoreKeyProviderConfiguration.java
new file mode 100644
index 0000000..8259ec0
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/KeyStoreKeyProviderConfiguration.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.security.kms.configuration;
+
+import org.apache.nifi.security.kms.KeyStoreKeyProvider;
+
+import java.security.KeyStore;
+import java.util.Objects;
+
+/**
+ * Configuration for KeyStore Key Provider
+ */
+public class KeyStoreKeyProviderConfiguration implements KeyProviderConfiguration<KeyStoreKeyProvider> {
+ private final KeyStore keyStore;
+
+ private final char[] keyPassword;
+
+ public KeyStoreKeyProviderConfiguration(final KeyStore keyStore, final char[] keyPassword) {
+ this.keyStore = Objects.requireNonNull(keyStore, "Key Store required");
+ this.keyPassword = Objects.requireNonNull(keyPassword, "Key Password required");
+ }
+
+ @Override
+ public Class<KeyStoreKeyProvider> getKeyProviderClass() {
+ return KeyStoreKeyProvider.class;
+ }
+
+ public KeyStore getKeyStore() {
+ return keyStore;
+ }
+
+ public char[] getKeyPassword() {
+ return keyPassword;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/StaticKeyProviderConfiguration.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/StaticKeyProviderConfiguration.java
new file mode 100644
index 0000000..168fa1b
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/configuration/StaticKeyProviderConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.configuration;
+
+import org.apache.nifi.security.kms.StaticKeyProvider;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Configuration for Static Key Provider
+ */
+public class StaticKeyProviderConfiguration implements KeyProviderConfiguration<StaticKeyProvider> {
+ private final Map<String, String> keys;
+
+ public StaticKeyProviderConfiguration(final Map<String, String> keys) {
+ this.keys = Collections.unmodifiableMap(keys);
+ }
+
+ @Override
+ public Class<StaticKeyProvider> getKeyProviderClass() {
+ return StaticKeyProvider.class;
+ }
+
+ public Map<String, String> getKeys() {
+ return keys;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/FileBasedKeyReader.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/FileBasedKeyReader.java
new file mode 100644
index 0000000..641bea8
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/FileBasedKeyReader.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.reader;
+
+import javax.crypto.SecretKey;
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * File Based Key Reader
+ */
+public interface FileBasedKeyReader {
+ /**
+ * Read Secret Keys from File Path and decrypt using provided Root Key
+ *
+ * @param path File Path
+ * @param rootKey Root Key
+ * @return Map of Key Identifier to Secret key
+ */
+ Map<String, SecretKey> readSecretKeys(Path path, SecretKey rootKey);
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/KeyReaderException.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/KeyReaderException.java
new file mode 100644
index 0000000..7395de6
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/KeyReaderException.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.security.kms.reader;
+
+/**
+ * Key Reader Exception
+ */
+public class KeyReaderException extends RuntimeException {
+ /**
+ * Key Reader Exception with message and associated cause
+ *
+ * @param message Exception message
+ * @param cause Exception cause
+ */
+ public KeyReaderException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReader.java b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReader.java
new file mode 100644
index 0000000..d5464ae
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/main/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReader.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.reader;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+/**
+ * Standard File Based Key Reader reads Secret Keys from Properties files encrypted using AES-GCM with Tag Size of 128
+ */
+public class StandardFileBasedKeyReader implements FileBasedKeyReader {
+ protected static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+
+ protected static final int IV_LENGTH_BYTES = 16;
+
+ protected static final int TAG_SIZE_BITS = 128;
+
+ private static final Base64.Decoder DECODER = Base64.getDecoder();
+
+ private static final String SECRET_KEY_ALGORITHM = "AES";
+
+ /**
+ * Read Secret Keys using provided Root Secret Key
+ *
+ * @param path File Path contains a properties file with Key Identifier and Base64-encoded encrypted values
+ * @param rootKey Root Secret Key
+ * @return Map of Key Identifier to decrypted Secret Key
+ */
+ @Override
+ public Map<String, SecretKey> readSecretKeys(final Path path, final SecretKey rootKey) {
+ Objects.requireNonNull(path, "Path required");
+ Objects.requireNonNull(rootKey, "Root Key required");
+ final Map<String, SecretKey> secretKeys = new HashMap<>();
+
+ final Properties properties = getProperties(path);
+ for (final String keyId : properties.stringPropertyNames()) {
+ final String encodedProperty = properties.getProperty(keyId);
+ final SecretKey secretKey = readSecretKey(keyId, encodedProperty, rootKey);
+ secretKeys.put(keyId, secretKey);
+ }
+ return secretKeys;
+ }
+
+ private Properties getProperties(final Path path) {
+ final Properties properties = new Properties();
+ try (final FileInputStream inputStream = new FileInputStream(path.toFile())) {
+ properties.load(inputStream);
+ } catch (final IOException e) {
+ throw new KeyReaderException(String.format("Reading Secret Keys Failed [%s]", path), e);
+ }
+ return properties;
+ }
+
+ private SecretKey readSecretKey(final String keyId, final String encodedProperty, final SecretKey rootKey) {
+ final byte[] encryptedProperty = DECODER.decode(encodedProperty);
+ final Cipher cipher = getCipher(keyId, encryptedProperty, rootKey);
+ final byte[] encryptedSecretKey = Arrays.copyOfRange(encryptedProperty, IV_LENGTH_BYTES, encryptedProperty.length);
+ try {
+ final byte[] secretKey = cipher.doFinal(encryptedSecretKey);
+ return new SecretKeySpec(secretKey, SECRET_KEY_ALGORITHM);
+ } catch (final IllegalBlockSizeException|BadPaddingException e) {
+ throw new KeyReaderException(String.format("Key Identifier [%s] decryption failed", keyId), e);
+ }
+ }
+
+ private Cipher getCipher(final String keyId, final byte[] encryptedProperty, final SecretKey rootKey) {
+ final byte[] initializationVector = Arrays.copyOfRange(encryptedProperty, 0, IV_LENGTH_BYTES);
+ final Cipher cipher = getCipher();
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, rootKey, new GCMParameterSpec(TAG_SIZE_BITS, initializationVector));
+ } catch (final InvalidAlgorithmParameterException|InvalidKeyException e) {
+ throw new KeyReaderException(String.format("Cipher initialization failed for Key Identifier [%s]", keyId), e);
+ }
+ return cipher;
+ }
+
+ private Cipher getCipher() {
+ try {
+ return Cipher.getInstance(CIPHER_ALGORITHM);
+ } catch (final NoSuchAlgorithmException|NoSuchPaddingException e) {
+ throw new KeyReaderException(String.format("Cipher Algorithm [%s] initialization failed", CIPHER_ALGORITHM), e);
+ }
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/FileBasedKeyProviderTest.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/FileBasedKeyProviderTest.java
new file mode 100644
index 0000000..09daf8a
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/FileBasedKeyProviderTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.nifi.security.kms.util.SecretKeyUtils;
+import org.junit.Test;
+
+import javax.crypto.SecretKey;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class FileBasedKeyProviderTest {
+ private static final String KEYS_EXTENSION = ".keys";
+
+ private static final String KEY_ID = UUID.randomUUID().toString();
+
+ @Test
+ public void testGetKey() throws GeneralSecurityException, IOException {
+ final SecretKey rootKey = SecretKeyUtils.getSecretKey();
+ final SecretKey secretKey = SecretKeyUtils.getSecretKey();
+ final Path secretKeysPath = getSecretKeysPath(rootKey, Collections.singletonMap(KEY_ID, secretKey));
+ final FileBasedKeyProvider provider = new FileBasedKeyProvider(secretKeysPath, rootKey);
+
+ final SecretKey secretKeyFound = provider.getKey(KEY_ID);
+ assertEquals(secretKey, secretKeyFound);
+ assertTrue(provider.keyExists(KEY_ID));
+ assertFalse(provider.getAvailableKeyIds().isEmpty());
+ }
+
+ private Path getSecretKeysPath(final SecretKey rootKey, final Map<String, SecretKey> secretKeys) throws IOException, GeneralSecurityException {
+ final Path path = Files.createTempFile(FileBasedKeyProviderTest.class.getSimpleName(), KEYS_EXTENSION);
+ path.toFile().deleteOnExit();
+
+ final Properties properties = SecretKeyUtils.getEncryptedSecretKeys(rootKey, secretKeys);
+ try (final OutputStream outputStream = new FileOutputStream(path.toFile())) {
+ properties.store(outputStream, null);
+ }
+
+ return path;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyProviderFactoryTest.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyProviderFactoryTest.java
new file mode 100644
index 0000000..bf3ea04
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyProviderFactoryTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
+import org.apache.nifi.security.kms.util.SecretKeyUtils;
+import org.junit.Test;
+
+import javax.crypto.SecretKey;
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class KeyProviderFactoryTest {
+
+ @Test
+ public void testGetUnsupportedKeyProvider() {
+ final KeyProviderConfiguration<?> configuration = new UnsupportedKeyProviderConfiguration();
+ assertThrows(UnsupportedOperationException.class, () -> KeyProviderFactory.getKeyProvider(configuration));
+ }
+
+ @Test
+ public void testGetStaticKeyProvider() {
+ final SecretKey secretKey = SecretKeyUtils.getSecretKey();
+ final String encodedSecretKey = Hex.encodeHexString(secretKey.getEncoded());
+ final Map<String, String> keys = Collections.singletonMap(SecretKey.class.getSimpleName(), encodedSecretKey);
+
+ final KeyProviderConfiguration<?> configuration = new StaticKeyProviderConfiguration(keys);
+ final KeyProvider keyProvider = KeyProviderFactory.getKeyProvider(configuration);
+ assertEquals(StaticKeyProvider.class, keyProvider.getClass());
+ }
+
+ @Test
+ public void testGetFileBasedKeyProvider() throws IOException {
+ final File file = File.createTempFile(KeyProviderFactoryTest.class.getSimpleName(), FileBasedKeyProviderConfiguration.class.getSimpleName());
+ file.deleteOnExit();
+ final String location = file.getAbsolutePath();
+ final SecretKey rootKey = SecretKeyUtils.getSecretKey();
+ final KeyProviderConfiguration<?> configuration = new FileBasedKeyProviderConfiguration(location, rootKey);
+ final KeyProvider keyProvider = KeyProviderFactory.getKeyProvider(configuration);
+ assertEquals(FileBasedKeyProvider.class, keyProvider.getClass());
+ }
+
+ @Test
+ public void testGetKeyStoreKeyProvider() throws GeneralSecurityException, IOException {
+ final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null, null);
+ final char[] password = String.class.getSimpleName().toCharArray();
+ final KeyProviderConfiguration<?> configuration = new KeyStoreKeyProviderConfiguration(keyStore, password);
+ final KeyProvider keyProvider = KeyProviderFactory.getKeyProvider(configuration);
+ assertEquals(KeyStoreKeyProvider.class, keyProvider.getClass());
+ }
+
+ private static class UnsupportedKeyProviderConfiguration implements KeyProviderConfiguration<KeyProvider> {
+
+ @Override
+ public Class<KeyProvider> getKeyProviderClass() {
+ return KeyProvider.class;
+ }
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyStoreKeyProviderTest.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyStoreKeyProviderTest.java
new file mode 100644
index 0000000..5210ad5
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/KeyStoreKeyProviderTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.nifi.security.kms.util.SecretKeyUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class KeyStoreKeyProviderTest {
+ private static final String KEY_ID = UUID.randomUUID().toString();
+
+ private static final SecretKey SECRET_KEY = SecretKeyUtils.getSecretKey();
+
+ private static final char[] PASSWORD = UUID.randomUUID().toString().toCharArray();
+
+ private static final String KEY_STORE_TYPE = "PKCS12";
+
+ private static KeyStore keyStore;
+
+ @BeforeClass
+ public static void setKeyStore() throws GeneralSecurityException, IOException {
+ keyStore = getKeyStore();
+ }
+
+ @Test
+ public void testGetKey() throws KeyManagementException {
+ final KeyStoreKeyProvider provider = new KeyStoreKeyProvider(keyStore, PASSWORD);
+
+ final SecretKey secretKeyFound = provider.getKey(KEY_ID);
+ assertEquals(SECRET_KEY, secretKeyFound);
+ }
+
+ @Test
+ public void testKeyExists() {
+ final KeyStoreKeyProvider provider = new KeyStoreKeyProvider(keyStore, PASSWORD);
+
+ assertTrue(provider.keyExists(KEY_ID));
+ }
+
+ @Test
+ public void testGetAvailableKeys() {
+ final KeyStoreKeyProvider provider = new KeyStoreKeyProvider(keyStore, PASSWORD);
+
+ final List<String> keyIds = provider.getAvailableKeyIds();
+ assertTrue(keyIds.contains(KEY_ID));
+ }
+
+ @Test
+ public void testGetKeyNotFoundManagementException() {
+ final KeyStoreKeyProvider provider = new KeyStoreKeyProvider(keyStore, PASSWORD);
+ assertThrows(KeyManagementException.class, () -> provider.getKey(SecretKey.class.getName()));
+ }
+
+ private static KeyStore getKeyStore() throws GeneralSecurityException, IOException {
+ KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
+ keyStore.load(null, null);
+ keyStore.setEntry(KEY_ID, new KeyStore.SecretKeyEntry(SECRET_KEY), new KeyStore.PasswordProtection(PASSWORD));
+ return keyStore;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/StaticKeyProviderTest.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/StaticKeyProviderTest.java
new file mode 100644
index 0000000..3f1aaad
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/StaticKeyProviderTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms;
+
+import org.apache.nifi.security.kms.util.SecretKeyUtils;
+import org.junit.Test;
+
+import javax.crypto.SecretKey;
+import java.security.KeyManagementException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class StaticKeyProviderTest {
+ private static final String KEY_ID = UUID.randomUUID().toString();
+
+ private static final SecretKey SECRET_KEY = SecretKeyUtils.getSecretKey();
+
+ @Test
+ public void testGetKey() throws KeyManagementException {
+ final StaticKeyProvider provider = new StaticKeyProvider(Collections.singletonMap(KEY_ID, SECRET_KEY));
+
+ final SecretKey secretKeyFound = provider.getKey(KEY_ID);
+ assertEquals(SECRET_KEY, secretKeyFound);
+ }
+
+ @Test
+ public void testKeyExists() {
+ final StaticKeyProvider provider = new StaticKeyProvider(Collections.singletonMap(KEY_ID, SECRET_KEY));
+
+ assertTrue(provider.keyExists(KEY_ID));
+ }
+
+ @Test
+ public void testGetAvailableKeys() {
+ final StaticKeyProvider provider = new StaticKeyProvider(Collections.singletonMap(KEY_ID, SECRET_KEY));
+
+ final List<String> keyIds = provider.getAvailableKeyIds();
+ assertTrue(keyIds.contains(KEY_ID));
+ }
+
+ @Test
+ public void testGetKeyNotFoundManagementException() {
+ final StaticKeyProvider provider = new StaticKeyProvider(Collections.emptyMap());
+ assertThrows(KeyManagementException.class, () -> provider.getKey(SecretKey.class.getName()));
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReaderTest.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReaderTest.java
new file mode 100644
index 0000000..cd0f9e72
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/reader/StandardFileBasedKeyReaderTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.reader;
+
+import org.apache.nifi.security.kms.util.SecretKeyUtils;
+import org.junit.Test;
+
+import javax.crypto.SecretKey;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+
+public class StandardFileBasedKeyReaderTest {
+ private static final String KEYS_EXTENSION = ".keys";
+
+ private static final SecretKey ROOT_KEY = SecretKeyUtils.getSecretKey();
+
+ @Test
+ public void testReadSecretKeys() throws Exception {
+ final StandardFileBasedKeyReader reader = new StandardFileBasedKeyReader();
+
+ final SecretKey secretKey = SecretKeyUtils.getSecretKey();
+ final String keyId = SecretKey.class.getSimpleName();
+
+ final Path path = getSecretKeysPath(ROOT_KEY, Collections.singletonMap(keyId, secretKey));
+ final Map<String, SecretKey> secretKeys = reader.readSecretKeys(path, ROOT_KEY);
+ final SecretKey readSecretKey = secretKeys.get(keyId);
+ assertEquals("Secret Key not matched", secretKey, readSecretKey);
+ }
+
+ private Path getSecretKeysPath(final SecretKey rootKey, final Map<String, SecretKey> secretKeys) throws Exception {
+ final Path path = Files.createTempFile(StandardFileBasedKeyReaderTest.class.getSimpleName(), KEYS_EXTENSION);
+ path.toFile().deleteOnExit();
+
+ final Properties properties = SecretKeyUtils.getEncryptedSecretKeys(rootKey, secretKeys);
+ try (final OutputStream outputStream = new FileOutputStream(path.toFile())) {
+ properties.store(outputStream, null);
+ }
+
+ return path;
+ }
+}
diff --git a/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/util/SecretKeyUtils.java b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/util/SecretKeyUtils.java
new file mode 100644
index 0000000..919c55d
--- /dev/null
+++ b/nifi-commons/nifi-security-kms/src/test/java/org/apache/nifi/security/kms/util/SecretKeyUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.security.kms.util;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Map;
+import java.util.Properties;
+
+public class SecretKeyUtils {
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+ private static final Base64.Encoder ENCODER = Base64.getEncoder();
+
+ private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+
+ private static final String KEY_ALGORITHM = "AES";
+
+ private static final int KEY_LENGTH = 32;
+
+ private static final int IV_LENGTH = 16;
+
+ private static final int TAG_LENGTH = 128;
+
+ /**
+ * Get Encrypted Secret Keys as Properties
+ *
+ * @param rootKey Root Key used to encrypt Secret Keys
+ * @param secretKeys Map of Key Identifier to Secret Key
+ * @return Properties containing encrypted Secret Keys
+ * @throws GeneralSecurityException Thrown on getEncryptedSecretKey()
+ */
+ public static Properties getEncryptedSecretKeys(final SecretKey rootKey, final Map<String, SecretKey> secretKeys) throws GeneralSecurityException {
+ final Properties properties = new Properties();
+ for (final Map.Entry<String, SecretKey> secretKeyEntry : secretKeys.entrySet()) {
+ final SecretKey secretKey = secretKeyEntry.getValue();
+ final String encryptedSecretKey = getEncryptedSecretKey(rootKey, secretKey);
+ properties.setProperty(secretKeyEntry.getKey(), encryptedSecretKey);
+ }
+ return properties;
+ }
+
+ /**
+ * Get Random AES Secret Key
+ *
+ * @return Secret Key
+ */
+ public static SecretKey getSecretKey() {
+ final byte[] encodedKey = new byte[KEY_LENGTH];
+ SECURE_RANDOM.nextBytes(encodedKey);
+ return new SecretKeySpec(encodedKey, KEY_ALGORITHM);
+ }
+
+ /**
+ * Get Encrypted Secret Key using AES-GCM with Base64 encoded string prefixed with initialization vector
+ *
+ * @param rootKey Root Key used to encrypt Secret Key
+ * @param secretKey Secret Key to be encrypted
+ * @return Base64 encoded and encrypted Secret Key
+ * @throws GeneralSecurityException Thrown when unable to encrypt Secret Key
+ */
+ private static String getEncryptedSecretKey(final SecretKey rootKey, final SecretKey secretKey) throws GeneralSecurityException {
+ final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+
+ final byte[] initializationVector = new byte[IV_LENGTH];
+ SECURE_RANDOM.nextBytes(initializationVector);
+ cipher.init(Cipher.ENCRYPT_MODE, rootKey, new GCMParameterSpec(TAG_LENGTH, initializationVector));
+ final byte[] encryptedSecretKey = cipher.doFinal(secretKey.getEncoded());
+
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ outputStream.write(initializationVector);
+ outputStream.write(encryptedSecretKey);
+ } catch (final IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ final byte[] encryptedProperty = outputStream.toByteArray();
+ return ENCODER.encodeToString(encryptedProperty);
+ }
+}
diff --git a/nifi-commons/nifi-security-utils/pom.xml b/nifi-commons/nifi-security-utils/pom.xml
index 25be6ed..ced618a 100644
--- a/nifi-commons/nifi-security-utils/pom.xml
+++ b/nifi-commons/nifi-security-utils/pom.xml
@@ -43,6 +43,11 @@
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-kms</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>provided</scope>
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java
index c0b60c8..37909e9 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java
@@ -18,33 +18,26 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
-import org.apache.nifi.security.util.EncryptionMethod;
-import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
import org.apache.nifi.util.NiFiBootstrapUtils;
import org.bouncycastle.util.encoders.DecoderException;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import java.util.Base64;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -53,6 +46,7 @@
private static final Logger logger = LoggerFactory.getLogger(CryptoUtils.class);
public static final String STATIC_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.StaticKeyProvider";
public static final String FILE_BASED_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.FileBasedKeyProvider";
+ public static final String KEY_STORE_KEY_PROVIDER_CLASS_NAME = "org.apache.nifi.security.kms.KeyStoreKeyProvider";
// TODO: Move to RepositoryEncryptionUtils in NIFI-6617
public static final String LEGACY_SKP_FQCN = "org.apache.nifi.provenance.StaticKeyProvider";
@@ -63,7 +57,6 @@
private static final List<Integer> UNLIMITED_KEY_LENGTHS = Arrays.asList(32, 48, 64);
- public static final int IV_LENGTH = 16;
public static final String ENCRYPTED_FSR_CLASS_NAME = "org.apache.nifi.controller.repository.crypto.EncryptedFileSystemRepository";
public static final String EWAFFR_CLASS_NAME = "org.apache.nifi.controller.repository.crypto.EncryptedWriteAheadFlowFileRepository";
@@ -127,45 +120,29 @@
* @param encryptionKeys a map of key IDs to key material in hex format
* @return true if the provided configuration is valid
*/
- public static boolean isValidKeyProvider(String keyProviderImplementation, String keyProviderLocation, String keyId, Map<String, String> encryptionKeys) {
- logger.debug("Attempting to validate the key provider: keyProviderImplementation = "
- + keyProviderImplementation + ", keyProviderLocation = "
- + keyProviderLocation + ", keyId = "
- + keyId + ", encryptionKeys = "
- + ((encryptionKeys == null) ? "0" : encryptionKeys.size()));
-
+ public static boolean isValidKeyProvider(String keyProviderImplementation, final String keyProviderLocation, final String keyId, final Map<String, String> encryptionKeys) {
try {
keyProviderImplementation = handleLegacyPackages(keyProviderImplementation);
- } catch (KeyManagementException e) {
- logger.warn("The attempt to validate the key provider failed keyProviderImplementation = "
- + keyProviderImplementation + ", keyProviderLocation = "
- + keyProviderLocation + ", keyId = "
- + keyId + ", encryptionKeys = "
- + ((encryptionKeys == null) ? "0" : encryptionKeys.size()));
-
+ } catch (final KeyManagementException e) {
+ logger.warn("Key Provider [{}] Validation Failed: {}", keyProviderImplementation, e.getMessage());
return false;
}
- if (STATIC_KEY_PROVIDER_CLASS_NAME.equals(keyProviderImplementation)) {
- // Ensure the keyId and key(s) are valid
- if (encryptionKeys == null) {
+ switch (keyProviderImplementation) {
+ case STATIC_KEY_PROVIDER_CLASS_NAME:
+ if (encryptionKeys == null) {
+ return false;
+ } else {
+ boolean everyKeyValid = encryptionKeys.values().stream().allMatch(CryptoUtils::keyIsValid);
+ return everyKeyValid && StringUtils.isNotEmpty(keyId);
+ }
+ case FILE_BASED_KEY_PROVIDER_CLASS_NAME:
+ case KEY_STORE_KEY_PROVIDER_CLASS_NAME:
+ final Path keyProviderPath = Paths.get(keyProviderLocation);
+ return Files.isReadable(keyProviderPath) && StringUtils.isNotEmpty(keyId);
+ default:
+ logger.warn("Validation Failed: Key Provider [{}] Location [{}] Key ID [{}]", keyProviderImplementation, keyProviderLocation, keyId);
return false;
- } else {
- boolean everyKeyValid = encryptionKeys.values().stream().allMatch(CryptoUtils::keyIsValid);
- return everyKeyValid && StringUtils.isNotEmpty(keyId);
- }
- } else if (FILE_BASED_KEY_PROVIDER_CLASS_NAME.equals(keyProviderImplementation)) {
- // Ensure the file can be read and the keyId is populated (does not read file to validate)
- final File kpf = new File(keyProviderLocation);
- return kpf.exists() && kpf.canRead() && StringUtils.isNotEmpty(keyId);
- } else {
- logger.warn("The attempt to validate the key provider failed keyProviderImplementation = "
- + keyProviderImplementation + ", keyProviderLocation = "
- + keyProviderLocation + ", keyId = "
- + keyId + ", encryptionKeys = "
- + ((encryptionKeys == null) ? "0" : encryptionKeys.size()));
-
- return false;
}
}
@@ -206,96 +183,6 @@
}
/**
- * Returns a {@link SecretKey} formed from the hexadecimal key bytes (validity is checked).
- *
- * @param keyHex the key in hex form
- * @return the SecretKey
- */
- public static SecretKey formKeyFromHex(String keyHex) throws KeyManagementException {
- if (keyIsValid(keyHex)) {
- return new SecretKeySpec(Hex.decode(keyHex), "AES");
- } else {
- throw new KeyManagementException("The provided key material is not valid");
- }
- }
-
- /**
- * Returns a map containing the key IDs and the parsed key from a key provider definition file.
- * The values in the file are decrypted using the root key provided. If the file is missing or empty,
- * cannot be read, or if no valid keys are read, a {@link KeyManagementException} will be thrown.
- *
- * @param filepath the key definition file path
- * @param rootKey the root key used to decrypt each key definition
- * @return a Map of key IDs to SecretKeys
- * @throws KeyManagementException if the file is missing or invalid
- */
- public static Map<String, SecretKey> readKeys(String filepath, SecretKey rootKey) throws KeyManagementException {
- Map<String, SecretKey> keys = new HashMap<>();
-
- if (StringUtils.isBlank(filepath)) {
- throw new KeyManagementException("The key provider file is not present and readable");
- }
- if (rootKey == null) {
- throw new KeyManagementException("The root key must be provided to decrypt the individual keys");
- }
-
- File file = new File(filepath);
- if (!file.exists() || !file.canRead()) {
- throw new KeyManagementException("The key provider file is not present and readable");
- }
-
- try (BufferedReader br = new BufferedReader(new FileReader(file))) {
- AESKeyedCipherProvider rootCipherProvider = new AESKeyedCipherProvider();
-
- String line;
- int l = 1;
- while ((line = br.readLine()) != null) {
- String[] components = line.split("=", 2);
- if (components.length != 2 || StringUtils.isAnyEmpty(components)) {
- logger.warn("Line " + l + " is not properly formatted -- keyId=Base64EncodedKey...");
- }
- String keyId = components[0];
- if (StringUtils.isNotEmpty(keyId)) {
- try {
- byte[] base64Bytes = Base64.getDecoder().decode(components[1]);
- byte[] ivBytes = Arrays.copyOfRange(base64Bytes, 0, IV_LENGTH);
-
- Cipher rootCipher = null;
- try {
- rootCipher = rootCipherProvider.getCipher(EncryptionMethod.AES_GCM, rootKey, ivBytes, false);
- } catch (Exception e) {
- throw new KeyManagementException("Error building cipher to decrypt FileBaseKeyProvider definition at " + filepath, e);
- }
- byte[] individualKeyBytes = rootCipher.doFinal(Arrays.copyOfRange(base64Bytes, IV_LENGTH, base64Bytes.length));
-
- SecretKey key = new SecretKeySpec(individualKeyBytes, "AES");
- logger.debug("Read and decrypted key for " + keyId);
- if (keys.containsKey(keyId)) {
- logger.warn("Multiple key values defined for " + keyId + " -- using most recent value");
- }
- keys.put(keyId, key);
- } catch (IllegalArgumentException e) {
- logger.error("Encountered an error decoding Base64 for " + keyId + ": " + e.getLocalizedMessage());
- } catch (BadPaddingException | IllegalBlockSizeException e) {
- logger.error("Encountered an error decrypting key for " + keyId + ": " + e.getLocalizedMessage());
- }
- }
- l++;
- }
-
- if (keys.isEmpty()) {
- throw new KeyManagementException("The provided file contained no valid keys");
- }
-
- logger.info("Read " + keys.size() + " keys from FileBasedKeyProvider " + filepath);
- return keys;
- } catch (IOException e) {
- throw new KeyManagementException("Error reading FileBasedKeyProvider definition at " + filepath, e);
- }
-
- }
-
- /**
* Returns the root key from the {@code bootstrap.conf} file used to encrypt various sensitive properties and data encryption keys.
*
* @return the root key
@@ -367,7 +254,7 @@
*/
private static byte[] convertCharsToBytes(char[] chars) {
CharBuffer charBuffer = CharBuffer.wrap(chars);
- ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+ ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
return Arrays.copyOfRange(byteBuffer.array(),
byteBuffer.position(), byteBuffer.limit());
}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java
deleted file mode 100644
index b6c3842..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/FileBasedKeyProvider.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.security.kms;
-
-import java.security.KeyManagementException;
-import javax.crypto.SecretKey;
-import javax.naming.OperationNotSupportedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class FileBasedKeyProvider extends StaticKeyProvider {
- private static final Logger logger = LoggerFactory.getLogger(FileBasedKeyProvider.class);
-
- private final String filepath;
-
- public FileBasedKeyProvider(String location, SecretKey rootKey) throws KeyManagementException {
- super(CryptoUtils.readKeys(location, rootKey));
- this.filepath = location;
- }
-
- /**
- * Adds the key to the provider and associates it with the given ID. Some implementations may not allow this operation.
- *
- * @param keyId the key identifier
- * @param key the key
- * @return true if the key was successfully added
- * @throws OperationNotSupportedException if this implementation doesn't support adding keys
- * @throws KeyManagementException if the key is invalid, the ID conflicts, etc.
- */
- @Override
- public boolean addKey(String keyId, SecretKey key) throws OperationNotSupportedException, KeyManagementException {
- throw new OperationNotSupportedException("This implementation does not allow adding keys. Modify the file backing this provider at " + filepath);
- }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java
deleted file mode 100644
index 69bf8e2..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/KeyProviderFactory.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.security.kms;
-
-import java.security.KeyManagementException;
-import java.util.Map;
-import java.util.stream.Collectors;
-import javax.crypto.SecretKey;
-import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Factory class to build {@link KeyProvider} instances. Currently supports {@link StaticKeyProvider} and {@link FileBasedKeyProvider}.
- */
-public class KeyProviderFactory {
- private static final Logger logger = LoggerFactory.getLogger(KeyProviderFactory.class);
-
- /**
- * Returns a key provider instantiated from the configuration values in a {@link RepositoryEncryptionConfiguration} object.
- *
- * @param rec the data container for config values (usually extracted from {@link org.apache.nifi.util.NiFiProperties})
- * @param rootKey the root key used to decrypt wrapped keys
- * @return the configured key provider
- * @throws KeyManagementException if the key provider cannot be instantiated
- */
- public static KeyProvider buildKeyProvider(RepositoryEncryptionConfiguration rec, SecretKey rootKey) throws KeyManagementException {
- if (rec == null) {
- throw new KeyManagementException("The repository encryption configuration values are required to build a key provider");
- }
- return buildKeyProvider(rec.getKeyProviderImplementation(), rec.getKeyProviderLocation(), rec.getEncryptionKeyId(), rec.getEncryptionKeys(), rootKey);
- }
-
- /**
- * Returns a key provider instantiated from the configuration values in a {@link RepositoryEncryptionConfiguration} object.
- *
- * @param implementationClassName the key provider class name
- * @param keyProviderLocation the filepath/URL of the stored keys
- * @param keyId the active key id
- * @param encryptionKeys the available encryption keys
- * @param rootKey the root key used to decrypt wrapped keys
- * @return the configured key provider
- * @throws KeyManagementException if the key provider cannot be instantiated
- */
- public static KeyProvider buildKeyProvider(String implementationClassName, String keyProviderLocation, String keyId, Map<String, String> encryptionKeys,
- SecretKey rootKey) throws KeyManagementException {
- KeyProvider keyProvider;
-
- implementationClassName = CryptoUtils.handleLegacyPackages(implementationClassName);
-
- if (StaticKeyProvider.class.getName().equals(implementationClassName)) {
- // Get all the keys (map) from config
- if (CryptoUtils.isValidKeyProvider(implementationClassName, keyProviderLocation, keyId, encryptionKeys)) {
- Map<String, SecretKey> formedKeys = encryptionKeys.entrySet().stream()
- .collect(Collectors.toMap(
- Map.Entry::getKey,
- e -> {
- try {
- return CryptoUtils.formKeyFromHex(e.getValue());
- } catch (KeyManagementException e1) {
- // This should never happen because the hex has already been validated
- logger.error("Encountered an error: ", e1);
- return null;
- }
- }));
- keyProvider = new StaticKeyProvider(formedKeys);
- } else {
- final String msg = "The StaticKeyProvider definition is not valid";
- logger.error(msg);
- throw new KeyManagementException(msg);
- }
- } else if (FileBasedKeyProvider.class.getName().equals(implementationClassName)) {
- keyProvider = new FileBasedKeyProvider(keyProviderLocation, rootKey);
- if (!keyProvider.keyExists(keyId)) {
- throw new KeyManagementException("The specified key ID " + keyId + " is not in the key definition file");
- }
- } else {
- throw new KeyManagementException("Invalid key provider implementation provided: " + implementationClassName);
- }
-
- return keyProvider;
- }
-
- /**
- * Returns true if this {@link KeyProvider} implementation requires the presence of the {@code root key} in order to decrypt the available data encryption keys.
- *
- * @param implementationClassName the key provider implementation class
- * @return true if this implementation requires the root key to operate
- * @throws KeyManagementException if the provided class name is not a valid key provider implementation
- */
- public static boolean requiresRootKey(String implementationClassName) throws KeyManagementException {
- implementationClassName = CryptoUtils.handleLegacyPackages(implementationClassName);
- return FileBasedKeyProvider.class.getName().equals(implementationClassName);
- }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java
deleted file mode 100644
index f14d124..0000000
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/StaticKeyProvider.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.security.kms;
-
-import java.security.KeyManagementException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.crypto.SecretKey;
-import javax.naming.OperationNotSupportedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Reference implementation for static key provider (used during tests).
- */
-public class StaticKeyProvider implements KeyProvider {
- private static final Logger logger = LoggerFactory.getLogger(StaticKeyProvider.class);
-
- private Map<String, SecretKey> keys = new HashMap<>();
-
- public StaticKeyProvider(String keyId, String keyHex) throws KeyManagementException {
- this.keys.put(keyId, CryptoUtils.formKeyFromHex(keyHex));
- }
-
- public StaticKeyProvider(Map<String, SecretKey> keys) throws KeyManagementException {
- this.keys.putAll(keys);
- }
-
- /**
- * Returns the key identified by this ID or throws an exception if one is not available.
- *
- * @param keyId the key identifier
- * @return the key
- * @throws KeyManagementException if the key cannot be retrieved
- */
- @Override
- public SecretKey getKey(String keyId) throws KeyManagementException {
- logger.debug("Attempting to get key: " + keyId);
- if (keyExists(keyId)) {
- return keys.get(keyId);
- } else {
- throw new KeyManagementException("No key available for " + keyId);
- }
- }
-
- /**
- * Returns true if the key exists and is available. Null or empty IDs will return false.
- *
- * @param keyId the key identifier
- * @return true if the key can be used
- */
- @Override
- public boolean keyExists(String keyId) {
- return keys.containsKey(keyId);
- }
-
- /**
- * Returns a singleton list of the available key identifier.
- *
- * @return a List containing the {@code KEY_ID}
- */
- @Override
- public List<String> getAvailableKeyIds() {
- return new ArrayList<>(keys.keySet());
- }
-
- /**
- * Adds the key to the provider and associates it with the given ID. Some implementations may not allow this operation.
- *
- * @param keyId the key identifier
- * @param key the key
- * @return true if the key was successfully added
- * @throws OperationNotSupportedException if this implementation doesn't support adding keys
- * @throws KeyManagementException if the key is invalid, the ID conflicts, etc.
- */
- @Override
- public boolean addKey(String keyId, SecretKey key) throws OperationNotSupportedException, KeyManagementException {
- throw new OperationNotSupportedException("This implementation does not allow adding keys");
- }
-}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java
index dbd0732..4f8ebbc 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/RepositoryEncryptorUtils.java
@@ -25,19 +25,29 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyManagementException;
+import java.security.KeyStore;
import java.util.Arrays;
-import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.apache.nifi.security.kms.CryptoUtils;
import org.apache.nifi.security.kms.EncryptionException;
+import org.apache.nifi.security.kms.FileBasedKeyProvider;
import org.apache.nifi.security.kms.KeyProvider;
import org.apache.nifi.security.kms.KeyProviderFactory;
+import org.apache.nifi.security.kms.KeyStoreKeyProvider;
+import org.apache.nifi.security.kms.StaticKeyProvider;
+import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
+import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
import org.apache.nifi.security.util.EncryptionMethod;
+import org.apache.nifi.security.util.KeyStoreUtils;
+import org.apache.nifi.security.util.TlsException;
import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
import org.apache.nifi.stream.io.NonCloseableInputStream;
import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,11 +56,7 @@
private static final int CONTENT_HEADER_SIZE = 2;
private static final int IV_LENGTH = 16;
- private static final byte[] EMPTY_IV = new byte[IV_LENGTH];
- private static final String VERSION = "v1";
- private static final List<String> SUPPORTED_VERSIONS = Arrays.asList(VERSION);
private static final int MIN_METADATA_LENGTH = IV_LENGTH + 3 + 3; // 3 delimiters and 3 non-zero elements
- private static final int METADATA_DEFAULT_LENGTH = (20 + 17 + IV_LENGTH + VERSION.length()) * 2; // Default to twice the expected length
private static final String EWAPR_CLASS_NAME = "org.apache.nifi.provenance.EncryptedWriteAheadProvenanceRepository";
// TODO: Add Javadoc
@@ -204,53 +210,6 @@
}
/**
- * Returns a configured {@link KeyProvider} instance that does not require a {@code root key} to use (usually a {@link org.apache.nifi.security.kms.StaticKeyProvider}).
- *
- * @param niFiProperties the {@link NiFiProperties} object
- * @param repositoryType the {@link RepositoryType} indicator
- * @return the configured KeyProvider
- * @throws KeyManagementException if there is a problem with the configuration
- */
- private static KeyProvider buildKeyProvider(NiFiProperties niFiProperties, RepositoryType repositoryType) throws KeyManagementException {
- return buildKeyProvider(niFiProperties, null, repositoryType);
- }
-
- /**
- * Returns a configured {@link KeyProvider} instance that requires a {@code root key} to use
- * (usually a {@link org.apache.nifi.security.kms.FileBasedKeyProvider} or an encrypted
- * {@link org.apache.nifi.security.kms.StaticKeyProvider}).
- *
- * @param niFiProperties the {@link NiFiProperties} object
- * @param rootKey the root encryption key used to encrypt the data encryption keys in the key provider configuration
- * @param repositoryType the {@link RepositoryType} indicator
- * @return the configured KeyProvider
- * @throws KeyManagementException if there is a problem with the configuration
- */
- public static KeyProvider buildKeyProvider(NiFiProperties niFiProperties, SecretKey rootKey, RepositoryType repositoryType) throws KeyManagementException {
- RepositoryEncryptionConfiguration rec = RepositoryEncryptionConfiguration.fromNiFiProperties(niFiProperties, repositoryType);
-
- return buildKeyProviderFromConfig(rootKey, rec);
- }
-
- /**
- * Returns a configured {@link KeyProvider} instance given the {@link RepositoryEncryptionConfiguration}.
- *
- * @param rootKey the root encryption key used to encrypt the data encryption keys in the key provider configuration
- * @param rec the repository-specific encryption configuration
- * @return the configured KeyProvider
- * @throws KeyManagementException if there is a problem with the configuration
- */
- public static KeyProvider buildKeyProviderFromConfig(SecretKey rootKey, RepositoryEncryptionConfiguration rec) throws KeyManagementException {
- if (rec.getKeyProviderImplementation() == null) {
- final String keyProviderImplementationClass = determineKeyProviderImplementationClassName(rec.getRepositoryType());
- throw new KeyManagementException("Cannot create key provider because the NiFi properties are missing the following property: "
- + keyProviderImplementationClass);
- }
-
- return KeyProviderFactory.buildKeyProvider(rec, rootKey);
- }
-
- /**
* Utility method which returns the {@link KeyProvider} implementation class name for a given repository type.
*
* @param repositoryType the {@link RepositoryType} indicator
@@ -284,20 +243,12 @@
* @throws IOException if there is a problem reading the properties or they are not valid & complete
*/
public static KeyProvider validateAndBuildRepositoryKeyProvider(NiFiProperties niFiProperties, RepositoryType repositoryType) throws IOException {
- // Initialize the encryption-specific fields
if (isRepositoryEncryptionConfigured(niFiProperties, repositoryType)) {
try {
- KeyProvider keyProvider;
- final String keyProviderImplementation = niFiProperties.getProperty(determineKeyProviderImplementationClassName(repositoryType));
- if (KeyProviderFactory.requiresRootKey(keyProviderImplementation)) {
- SecretKey rootKey = CryptoUtils.getRootKey();
- keyProvider = buildKeyProvider(niFiProperties, rootKey, repositoryType);
- } else {
- keyProvider = buildKeyProvider(niFiProperties, repositoryType);
- }
- return keyProvider;
- } catch (KeyManagementException e) {
- String msg = "Encountered an error building the key provider";
+ final RepositoryEncryptionConfiguration configuration = RepositoryEncryptionConfiguration.fromNiFiProperties(niFiProperties, repositoryType);
+ return getKeyProvider(configuration);
+ } catch (final KeyManagementException e) {
+ final String msg = "Encountered an error building the key provider";
logger.error(msg, e);
throw new IOException(msg, e);
}
@@ -313,15 +264,51 @@
* @return the configured KeyProvider
* @throws IOException if there is a problem reading the properties or they are not valid & complete
*/
- public static KeyProvider validateAndBuildRepositoryKeyProvider(RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws IOException {
- // Initialize the encryption-specific fields
+ public static KeyProvider validateAndBuildRepositoryKeyProvider(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws IOException {
try {
- SecretKey rootKey = KeyProviderFactory.requiresRootKey(repositoryEncryptionConfiguration.getKeyProviderImplementation()) ? CryptoUtils.getRootKey() : null;
- return buildKeyProviderFromConfig(rootKey, repositoryEncryptionConfiguration);
- } catch (KeyManagementException e) {
- String msg = "Encountered an error building the key provider";
+ return getKeyProvider(repositoryEncryptionConfiguration);
+ } catch (final KeyManagementException e) {
+ final String msg = "Encountered an error building the key provider";
logger.error(msg, e);
throw new IOException(msg, e);
}
}
+
+ private static KeyProvider getKeyProvider(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws KeyManagementException {
+ final KeyProviderConfiguration<?> keyProviderConfiguration = getKeyProviderConfiguration(repositoryEncryptionConfiguration);
+ final KeyProvider keyProvider = KeyProviderFactory.getKeyProvider(keyProviderConfiguration);
+
+ final String keyId = repositoryEncryptionConfiguration.getEncryptionKeyId();
+ if (keyProvider.keyExists(keyId)) {
+ return keyProvider;
+ } else {
+ throw new KeyManagementException(String.format("Key Identifier [%s] not found in Key Provider", keyId));
+ }
+ }
+
+ private static KeyProviderConfiguration<?> getKeyProviderConfiguration(final RepositoryEncryptionConfiguration repositoryEncryptionConfiguration) throws KeyManagementException {
+ final String keyProviderImplementation = repositoryEncryptionConfiguration.getKeyProviderImplementation();
+ if (keyProviderImplementation.endsWith(StaticKeyProvider.class.getSimpleName())) {
+ return new StaticKeyProviderConfiguration(repositoryEncryptionConfiguration.getEncryptionKeys());
+ } else if (keyProviderImplementation.endsWith(FileBasedKeyProvider.class.getSimpleName())) {
+ final SecretKey rootKey = CryptoUtils.getRootKey();
+ return new FileBasedKeyProviderConfiguration(repositoryEncryptionConfiguration.getKeyProviderLocation(), rootKey);
+ } else if (keyProviderImplementation.endsWith(KeyStoreKeyProvider.class.getSimpleName())) {
+ final String keyProviderPassword = repositoryEncryptionConfiguration.getKeyProviderPassword();
+ if (StringUtils.isBlank(keyProviderPassword)) {
+ throw new KeyManagementException("Key Provider Password not configured");
+ }
+ final String location = repositoryEncryptionConfiguration.getKeyProviderLocation();
+ final char[] keyStorePassword = repositoryEncryptionConfiguration.getKeyProviderPassword().toCharArray();
+ final String keyStoreType = repositoryEncryptionConfiguration.getKeyStoreType();
+ try {
+ final KeyStore keyStore = KeyStoreUtils.loadSecretKeyStore(location, keyStorePassword, keyStoreType);
+ return new KeyStoreKeyProviderConfiguration(keyStore, keyStorePassword);
+ } catch (final TlsException e) {
+ throw new KeyManagementException("Key Store Provider loading failed", e);
+ }
+ } else {
+ throw new UnsupportedOperationException(String.format("Key Provider Implementation [%s] not supported", keyProviderImplementation));
+ }
+ }
}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java
index 0e4d5c6..355a170 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ContentRepositoryEncryptionConfiguration.java
@@ -18,13 +18,10 @@
import java.util.Map;
import org.apache.nifi.security.repository.RepositoryType;
+import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class ContentRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
- private static final Logger logger = LoggerFactory.getLogger(ContentRepositoryEncryptionConfiguration.class);
-
/**
* Contructor which accepts a {@link NiFiProperties} object and extracts the relevant
* property values directly.
@@ -36,7 +33,8 @@
niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
niFiProperties.getContentRepositoryEncryptionKeyId(),
niFiProperties.getContentRepositoryEncryptionKeys(),
- niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION)
+ niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION),
+ niFiProperties.getProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD)
);
}
@@ -48,17 +46,21 @@
* @param encryptionKeyId the active encryption key id
* @param encryptionKeys the map of available keys
* @param repositoryImplementation the repository implementation class
+ * @param keyProviderPassword Key Provider Password used based on provider implementation
*/
- public ContentRepositoryEncryptionConfiguration(String keyProviderImplementation,
- String keyProviderLocation,
- String encryptionKeyId,
- Map<String, String> encryptionKeys,
- String repositoryImplementation) {
+ public ContentRepositoryEncryptionConfiguration(final String keyProviderImplementation,
+ final String keyProviderLocation,
+ final String encryptionKeyId,
+ final Map<String, String> encryptionKeys,
+ final String repositoryImplementation,
+ final String keyProviderPassword) {
this.keyProviderImplementation = keyProviderImplementation;
this.keyProviderLocation = keyProviderLocation;
this.encryptionKeyId = encryptionKeyId;
this.encryptionKeys = encryptionKeys;
this.repositoryImplementation = repositoryImplementation;
this.repositoryType = RepositoryType.CONTENT;
+ this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
+ this.keyProviderPassword = keyProviderPassword;
}
}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java
index 74e4132..0c5666c 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/FlowFileRepositoryEncryptionConfiguration.java
@@ -18,13 +18,10 @@
import java.util.Map;
import org.apache.nifi.security.repository.RepositoryType;
+import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class FlowFileRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
- private static final Logger logger = LoggerFactory.getLogger(FlowFileRepositoryEncryptionConfiguration.class);
-
/**
* Constructor which accepts a {@link NiFiProperties} object and extracts the relevant
* property values directly.
@@ -36,7 +33,8 @@
niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION),
niFiProperties.getFlowFileRepoEncryptionKeyId(),
niFiProperties.getFlowFileRepoEncryptionKeys(),
- niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION)
+ niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_WAL_IMPLEMENTATION),
+ niFiProperties.getProperty(NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD)
);
}
@@ -51,17 +49,21 @@
* @param encryptionKeyId the active encryption key id
* @param encryptionKeys the map of available keys
* @param repositoryImplementation the write ahead log implementation
+ * @param keyProviderPassword Key Provider Password
*/
- public FlowFileRepositoryEncryptionConfiguration(String keyProviderImplementation,
- String keyProviderLocation,
- String encryptionKeyId,
- Map<String, String> encryptionKeys,
- String repositoryImplementation) {
+ public FlowFileRepositoryEncryptionConfiguration(final String keyProviderImplementation,
+ final String keyProviderLocation,
+ final String encryptionKeyId,
+ final Map<String, String> encryptionKeys,
+ final String repositoryImplementation,
+ final String keyProviderPassword) {
this.keyProviderImplementation = keyProviderImplementation;
this.keyProviderLocation = keyProviderLocation;
this.encryptionKeyId = encryptionKeyId;
this.encryptionKeys = encryptionKeys;
this.repositoryImplementation = repositoryImplementation;
this.repositoryType = RepositoryType.FLOWFILE;
+ this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
+ this.keyProviderPassword = keyProviderPassword;
}
}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java
index 4f85a75..66c6283 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/ProvenanceRepositoryEncryptionConfiguration.java
@@ -18,13 +18,10 @@
import java.util.Map;
import org.apache.nifi.security.repository.RepositoryType;
+import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.util.NiFiProperties;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class ProvenanceRepositoryEncryptionConfiguration extends RepositoryEncryptionConfiguration {
- private static final Logger logger = LoggerFactory.getLogger(ProvenanceRepositoryEncryptionConfiguration.class);
-
/**
* Constructor which accepts a {@link NiFiProperties} object and extracts the relevant
* property values directly.
@@ -36,7 +33,8 @@
niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION),
niFiProperties.getProvenanceRepoEncryptionKeyId(),
niFiProperties.getProvenanceRepoEncryptionKeys(),
- niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS)
+ niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS),
+ niFiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD)
);
}
@@ -48,17 +46,21 @@
* @param encryptionKeyId the active encryption key id
* @param encryptionKeys the map of available keys
* @param repositoryImplementation the repository implementation class
+ * @param keyProviderPassword Key Provider Password used based on provider implementation
*/
- public ProvenanceRepositoryEncryptionConfiguration(String keyProviderImplementation,
- String keyProviderLocation,
- String encryptionKeyId,
- Map<String, String> encryptionKeys,
- String repositoryImplementation) {
+ public ProvenanceRepositoryEncryptionConfiguration(final String keyProviderImplementation,
+ final String keyProviderLocation,
+ final String encryptionKeyId,
+ final Map<String, String> encryptionKeys,
+ final String repositoryImplementation,
+ final String keyProviderPassword) {
this.keyProviderImplementation = keyProviderImplementation;
this.keyProviderLocation = keyProviderLocation;
this.encryptionKeyId = encryptionKeyId;
this.encryptionKeys = encryptionKeys;
this.repositoryImplementation = repositoryImplementation;
this.repositoryType = RepositoryType.CONTENT;
+ this.keyStoreType = KeyStoreUtils.getKeystoreTypeFromExtension(keyProviderLocation).getType();
+ this.keyProviderPassword = keyProviderPassword;
}
}
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java
index 39da7ff..8b3cd2c 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/repository/config/RepositoryEncryptionConfiguration.java
@@ -17,8 +17,6 @@
package org.apache.nifi.security.repository.config;
import java.util.Map;
-import javax.crypto.SecretKey;
-import org.apache.nifi.security.kms.CryptoUtils;
import org.apache.nifi.security.kms.FileBasedKeyProvider;
import org.apache.nifi.security.kms.KeyProvider;
import org.apache.nifi.security.kms.StaticKeyProvider;
@@ -37,6 +35,8 @@
Map<String, String> encryptionKeys;
String repositoryImplementation;
RepositoryType repositoryType;
+ String keyStoreType;
+ String keyProviderPassword;
/**
* Returns the class name of the {@link KeyProvider} implementation used.
@@ -72,7 +72,6 @@
* {@link StaticKeyProvider}. For
* {@link FileBasedKeyProvider}, this method will return an
* empty map because the keys must be loaded using the {@code root key} to decrypt them
- * via {@link CryptoUtils#readKeys(String, SecretKey)}.
*
* @return a map of key ids & keys
* @see NiFiProperties#getContentRepositoryEncryptionKeys()
@@ -100,6 +99,24 @@
return repositoryType;
}
+ /**
+ * Get Key Store Type for Key Store implementation
+ *
+ * @return Key Store Type
+ */
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ /**
+ * Get Key Provider Password
+ *
+ * @return Key Provider Password
+ */
+ public String getKeyProviderPassword() {
+ return keyProviderPassword;
+ }
+
public static RepositoryEncryptionConfiguration fromNiFiProperties(NiFiProperties niFiProperties, RepositoryType repositoryType) {
switch (repositoryType) {
case CONTENT:
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
index ddee951..278265b 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
@@ -27,23 +27,24 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
-import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.codec.binary.Hex;
@@ -57,6 +58,7 @@
private static final Logger logger = LoggerFactory.getLogger(KeyStoreUtils.class);
public static final String SUN_PROVIDER_NAME = "SUN";
+ public static final String SUN_JSSE_PROVIDER_NAME = "SunJSSE";
private static final String JKS_EXT = ".jks";
private static final String PKCS12_EXT = ".p12";
private static final String BCFKS_EXT = ".bcfks";
@@ -75,6 +77,7 @@
private static final Map<String, String> KEY_STORE_TYPE_PROVIDERS = new HashMap<>();
private static final Map<KeystoreType, String> KEY_STORE_EXTENSIONS = new HashMap<>();
+ private static final Map<KeystoreType, String> SECRET_KEY_STORE_PROVIDERS = new HashMap<>();
static {
Security.addProvider(new BouncyCastleProvider());
@@ -82,6 +85,9 @@
KEY_STORE_TYPE_PROVIDERS.put(KeystoreType.BCFKS.getType(), BouncyCastleProvider.PROVIDER_NAME);
KEY_STORE_TYPE_PROVIDERS.put(KeystoreType.PKCS12.getType(), BouncyCastleProvider.PROVIDER_NAME);
KEY_STORE_TYPE_PROVIDERS.put(KeystoreType.JKS.getType(), SUN_PROVIDER_NAME);
+
+ SECRET_KEY_STORE_PROVIDERS.put(KeystoreType.BCFKS, BouncyCastleProvider.PROVIDER_NAME);
+ SECRET_KEY_STORE_PROVIDERS.put(KeystoreType.PKCS12, SUN_JSSE_PROVIDER_NAME);
}
static {
@@ -96,7 +102,7 @@
* @param keyStoreType the keyStoreType
* @return Key Store Provider Name or null when not found
*/
- public static String getKeyStoreProvider(String keyStoreType) {
+ public static String getKeyStoreProvider(final String keyStoreType) {
final String storeType = StringUtils.upperCase(keyStoreType);
return KEY_STORE_TYPE_PROVIDERS.get(storeType);
}
@@ -108,7 +114,7 @@
* @return an empty KeyStore
* @throws KeyStoreException if a KeyStore of the given type cannot be instantiated
*/
- public static KeyStore getKeyStore(String keyStoreType) throws KeyStoreException {
+ public static KeyStore getKeyStore(final String keyStoreType) throws KeyStoreException {
final String keyStoreProvider = getKeyStoreProvider(keyStoreType);
if (StringUtils.isNotEmpty(keyStoreProvider)) {
try {
@@ -121,6 +127,26 @@
}
/**
+ * Returns an empty KeyStore for Secret Keys backed by the appropriate provider
+ *
+ * @param keystoreTypeName Keystore Type Name
+ * @return an empty KeyStore
+ * @throws KeyStoreException if a KeyStore of the given type cannot be instantiated
+ */
+ public static KeyStore getSecretKeyStore(final String keystoreTypeName) throws KeyStoreException {
+ final KeystoreType keystoreType = getKeystoreType(keystoreTypeName);
+ final String provider = SECRET_KEY_STORE_PROVIDERS.get(keystoreType);
+ if (provider == null) {
+ throw new KeyStoreException(String.format("Keystore Type [%s] does not support Secret Keys", keystoreType.getType()));
+ }
+ try {
+ return KeyStore.getInstance(keystoreType.getType(), provider);
+ } catch (final NoSuchProviderException e) {
+ throw new KeyStoreException(String.format("KeyStore Type [%s] Provider [%s] not found", keystoreType.getType(), provider), e);
+ }
+ }
+
+ /**
* Returns a loaded {@link KeyStore} given the provided configuration values.
*
* @param keystorePath the file path to the keystore
@@ -144,6 +170,27 @@
}
/**
+ * Load {@link KeyStore} containing Secret Key entries using configured Security Provider
+ *
+ * @param keystorePath File path to KeyStore
+ * @param keystorePassword Password for loading KeyStore
+ * @param keystoreTypeName Keystore Type Name
+ * @return KeyStore loaded using specified configuration
+ * @throws TlsException Thrown when unable to load KeyStore or unsupported Keystore Type
+ */
+ public static KeyStore loadSecretKeyStore(final String keystorePath, final char[] keystorePassword, final String keystoreTypeName) throws TlsException {
+ try {
+ final KeyStore keyStore = getSecretKeyStore(keystoreTypeName);
+ try (final InputStream keyStoreStream = new FileInputStream(keystorePath)) {
+ keyStore.load(keyStoreStream, keystorePassword);
+ }
+ return keyStore;
+ } catch (final GeneralSecurityException|IOException e) {
+ throw new TlsException(String.format("Loading Secret Keystore [%s] Type [%s] Failed", keystorePath, keystoreTypeName), e);
+ }
+ }
+
+ /**
* Creates a temporary default Keystore and Truststore and returns it wrapped in a TLS configuration.
*
* @return a {@link org.apache.nifi.security.util.TlsConfiguration}
@@ -418,7 +465,7 @@
// Determine the default alias
String alias = ks.aliases().nextElement();
try {
- Key privateKeyEntry = ks.getKey(alias, keyPassword);
+ ks.getKey(alias, keyPassword);
return true;
} catch (UnrecoverableKeyException e) {
logger.warn("Tried to access a key in keystore " + keystore + " with a key password that failed");
@@ -437,6 +484,36 @@
}
}
+ /**
+ * Get Keystore Type based on file extension defaults to returning PKCS12
+ *
+ * @param keystorePath Path to KeyStore
+ * @return Keystore Type defaults to PKCS12
+ */
+ public static KeystoreType getKeystoreTypeFromExtension(final String keystorePath) {
+ KeystoreType keystoreType = KeystoreType.PKCS12;
+
+ for (final Map.Entry<KeystoreType, String> keystoreTypeEntry : KEY_STORE_EXTENSIONS.entrySet()) {
+ final String extension = keystoreTypeEntry.getValue();
+ if (StringUtils.endsWithIgnoreCase(keystorePath, extension)) {
+ keystoreType = keystoreTypeEntry.getKey();
+ break;
+ }
+ }
+
+ return keystoreType;
+ }
+
+ /**
+ * Is Secret Key Entry supported for specified Keystore Type
+ *
+ * @param keystoreType Keystore Type
+ * @return Secret Key Entry supported status
+ */
+ public static boolean isSecretKeyEntrySupported(final KeystoreType keystoreType) {
+ return SECRET_KEY_STORE_PROVIDERS.containsKey(keystoreType);
+ }
+
public static String sslContextToString(SSLContext sslContext) {
return new ToStringBuilder(sslContext)
.append("protocol", sslContext.getProtocol())
@@ -444,14 +521,6 @@
.toString();
}
- public static String sslParametersToString(SSLParameters sslParameters) {
- return new ToStringBuilder(sslParameters)
- .append("protocols", sslParameters.getProtocols())
- .append("wantClientAuth", sslParameters.getWantClientAuth())
- .append("needClientAuth", sslParameters.getNeedClientAuth())
- .toString();
- }
-
public static String sslServerSocketToString(SSLServerSocket sslServerSocket) {
return new ToStringBuilder(sslServerSocket)
.append("enabledProtocols", sslServerSocket.getEnabledProtocols())
@@ -469,22 +538,6 @@
* @param keyPassword the key password
* @param keyStorePath the keystore path
* @param keyStoreType the keystore type
- * @return a {@link X509Certificate}
- */
- private static X509Certificate createKeyStoreAndGetX509Certificate(
- final String alias, final String keyStorePassword, final String keyPassword, final String keyStorePath,
- final KeystoreType keyStoreType) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
- return createKeyStoreAndGetX509Certificate(alias, keyStorePassword, keyPassword, keyStorePath, keyStoreType, CERT_DURATION_DAYS,
- null);
- }
- /**
- * Loads the Keystore and returns a X509 Certificate with the given values.
- *
- * @param alias the certificate alias
- * @param keyStorePassword the keystore password
- * @param keyPassword the key password
- * @param keyStorePath the keystore path
- * @param keyStoreType the keystore type
* @param dnsSubjectAlternativeNames An optional array of dnsName SANs
* @param certDurationDays the duration of the validity of the certificate, in days
* @return a {@link X509Certificate}
@@ -588,4 +641,12 @@
new SecureRandom().nextBytes(password);
return Hex.encodeHexString(password);
}
+
+ private static KeystoreType getKeystoreType(final String keystoreTypeName) {
+ final String keystoreTypeFilter = keystoreTypeName.toUpperCase();
+ final Optional<KeystoreType> foundKeystoreType = Arrays.stream(KeystoreType.values())
+ .filter(keystoreType -> keystoreType.getType().equals(keystoreTypeFilter))
+ .findFirst();
+ return foundKeystoreType.orElseThrow(() -> new IllegalArgumentException(String.format("Keystore Type [%s] not found", keystoreTypeFilter)));
+ }
}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy
index 6065c5b..1472791 100644
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy
+++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/CryptoUtilsTest.groovy
@@ -33,18 +33,11 @@
import org.slf4j.LoggerFactory
import javax.crypto.Cipher
-import javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
-import java.security.KeyManagementException
-import java.security.SecureRandom
import java.security.Security
-import static groovy.test.GroovyAssert.shouldFail
-
@RunWith(JUnit4.class)
class CryptoUtilsTest {
private static final Logger logger = LoggerFactory.getLogger(CryptoUtilsTest.class)
@@ -341,154 +334,6 @@
}
@Test
- void testShouldReadKeys() {
- // Arrange
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- String keyFileName = "keys.nkp"
- File keyFile = tempFolder.newFile(keyFileName)
- final int KEY_COUNT = 5
- List<String> lines = []
- KEY_COUNT.times { int i ->
- lines.add("key${i + 1}=${generateEncryptedKey(rootKey)}")
- }
-
- keyFile.text = lines.join("\n")
-
- logger.info("File contents: \n${keyFile.text}")
-
- // Act
- def readKeys = CryptoUtils.readKeys(keyFile.path, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${keyFile.path}")
-
- // Assert
- assert readKeys.size() == KEY_COUNT
- }
-
- @Test
- void testShouldReadKeysWithDuplicates() {
- // Arrange
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- String keyFileName = "keys.nkp"
- File keyFile = tempFolder.newFile(keyFileName)
- final int KEY_COUNT = 3
- List<String> lines = []
- KEY_COUNT.times { int i ->
- lines.add("key${i + 1}=${generateEncryptedKey(rootKey)}")
- }
-
- lines.add("key3=${generateEncryptedKey(rootKey)}")
-
- keyFile.text = lines.join("\n")
-
- logger.info("File contents: \n${keyFile.text}")
-
- // Act
- def readKeys = CryptoUtils.readKeys(keyFile.path, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${keyFile.path}")
-
- // Assert
- assert readKeys.size() == KEY_COUNT
- }
-
- @Test
- void testShouldReadKeysWithSomeMalformed() {
- // Arrange
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- String keyFileName = "keys.nkp"
- File keyFile = tempFolder.newFile(keyFileName)
- final int KEY_COUNT = 5
- List<String> lines = []
- KEY_COUNT.times { int i ->
- lines.add("key${i + 1}=${generateEncryptedKey(rootKey)}")
- }
-
- // Insert the malformed keys in the middle
- lines.add(2, "keyX1==${generateEncryptedKey(rootKey)}")
- lines.add(4, "=${generateEncryptedKey(rootKey)}")
- lines.add(6, "keyX3=non Base64-encoded data")
-
- keyFile.text = lines.join("\n")
-
- logger.info("File contents: \n${keyFile.text}")
-
- // Act
- def readKeys = CryptoUtils.readKeys(keyFile.path, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${keyFile.path}")
-
- // Assert
- assert readKeys.size() == KEY_COUNT
- }
-
- @Test
- void testShouldNotReadKeysIfAllMalformed() {
- // Arrange
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- String keyFileName = "keys.nkp"
- File keyFile = tempFolder.newFile(keyFileName)
- final int KEY_COUNT = 5
- List<String> lines = []
-
- // All of these keys are malformed
- KEY_COUNT.times { int i ->
- lines.add("key${i + 1}=${generateEncryptedKey(rootKey)[0..<-4]}")
- }
-
- keyFile.text = lines.join("\n")
-
- logger.info("File contents: \n${keyFile.text}")
-
- // Act
- def msg = shouldFail(KeyManagementException) {
- def readKeys = CryptoUtils.readKeys(keyFile.path, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${keyFile.path}")
- }
-
- // Assert
- assert msg.getMessage() == "The provided file contained no valid keys"
- }
-
- @Test
- void testShouldNotReadKeysIfEmptyOrMissing() {
- // Arrange
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- String keyFileName = "empty.nkp"
- File keyFile = tempFolder.newFile(keyFileName)
- logger.info("File contents: \n${keyFile.text}")
-
- // Act
- def missingMsg = shouldFail(KeyManagementException) {
- def readKeys = CryptoUtils.readKeys(keyFile.path, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${keyFile.path}")
- }
- logger.expected("Missing file: ${missingMsg}")
-
- def emptyMsg = shouldFail(KeyManagementException) {
- def readKeys = CryptoUtils.readKeys(null, rootKey)
- logger.info("Read ${readKeys.size()} keys from ${null}")
- }
- logger.expected("Empty file: ${emptyMsg}")
-
- // Assert
- assert missingMsg.getMessage() == "The provided file contained no valid keys"
- assert emptyMsg.getMessage() == "The key provider file is not present and readable"
- }
-
- @Test
void testShouldEvaluateConstantTimeEqualsForStrings() {
// Arrange
String plaintext = "This is a short string."
@@ -684,20 +529,4 @@
long end = System.nanoTime()
end - start
}
-
- private static String generateEncryptedKey(SecretKey rootKey) {
- byte[] ivBytes = new byte[16]
- byte[] keyBytes = new byte[isUnlimitedStrengthCryptoAvailable() ? 32 : 16]
-
- SecureRandom sr = new SecureRandom()
- sr.nextBytes(ivBytes)
- sr.nextBytes(keyBytes)
-
- Cipher rootCipher = Cipher.getInstance("AES/GCM/NoPadding", "BC")
- rootCipher.init(Cipher.ENCRYPT_MODE, rootKey, new IvParameterSpec(ivBytes))
- byte[] cipherBytes = rootCipher.doFinal(keyBytes)
-
- Base64.encoder.encodeToString(CryptoUtils.concatByteArrays(ivBytes, cipherBytes))
- }
-
}
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/KeyProviderFactoryTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/KeyProviderFactoryTest.groovy
deleted file mode 100644
index 9fdcac3..0000000
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/kms/KeyProviderFactoryTest.groovy
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License") you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.security.kms
-
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.SecretKey
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.security.KeyManagementException
-import java.security.SecureRandom
-import java.security.Security
-
-import static groovy.test.GroovyAssert.shouldFail
-
-@RunWith(JUnit4.class)
-class KeyProviderFactoryTest {
- private static final Logger logger = LoggerFactory.getLogger(KeyProviderFactoryTest.class)
-
- private static final String KEY_ID = "K1"
- private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
- private static final String KEY_HEX_256 = KEY_HEX_128 * 2
- private static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
-
- private static final String LEGACY_SKP_FQCN = "org.apache.nifi.provenance.StaticKeyProvider"
- private static final String LEGACY_FBKP_FQCN = "org.apache.nifi.provenance.FileBasedKeyProvider"
-
- private static final String ORIGINAL_PROPERTIES_PATH = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
-
- private static final SecretKey ROOT_KEY = new SecretKeySpec(Hex.decode(KEY_HEX), "AES")
-
- @ClassRule
- public static TemporaryFolder tempFolder = new TemporaryFolder()
-
- @BeforeClass
- static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
-
- logger.info("Original \$PROPERTIES_FILE_PATH is ${ORIGINAL_PROPERTIES_PATH}")
- String testPath = new File("src/test/resources/${isUnlimitedStrengthCryptoAvailable() ? "256" : "128"}/conf/.").getAbsolutePath()
- logger.info("Temporarily setting to ${testPath}")
- System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, testPath)
- }
-
- @Before
- void setUp() throws Exception {
- tempFolder.create()
- }
-
- @After
- void tearDown() throws Exception {
- tempFolder?.delete()
- }
-
- @AfterClass
- static void tearDownOnce() throws Exception {
- if (ORIGINAL_PROPERTIES_PATH) {
- logger.info("Restored \$PROPERTIES_FILE_PATH to ${ORIGINAL_PROPERTIES_PATH}")
- System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, ORIGINAL_PROPERTIES_PATH)
- }
- }
-
- private static boolean isUnlimitedStrengthCryptoAvailable() {
- Cipher.getMaxAllowedKeyLength("AES") > 128
- }
-
- private static void populateKeyDefinitionsFile(String path = "src/test/resources/conf/filebased.kp") {
- String rootKeyHex = KEY_HEX
- SecretKey rootKey = new SecretKeySpec(Hex.decode(rootKeyHex), "AES")
-
- // Generate the file
- File keyFile = new File(path)
- final int KEY_COUNT = 1
- List<String> lines = []
- KEY_COUNT.times { int i ->
- lines.add("K${i + 1}=${generateEncryptedKey(rootKey)}")
- }
-
- keyFile.text = lines.join("\n")
- }
-
- private static String generateEncryptedKey(SecretKey rootKey) {
- byte[] ivBytes = new byte[16]
- byte[] keyBytes = new byte[isUnlimitedStrengthCryptoAvailable() ? 32 : 16]
-
- SecureRandom sr = new SecureRandom()
- sr.nextBytes(ivBytes)
- sr.nextBytes(keyBytes)
-
- Cipher rootCipher = Cipher.getInstance("AES/GCM/NoPadding", "BC")
- rootCipher.init(Cipher.ENCRYPT_MODE, rootKey, new IvParameterSpec(ivBytes))
- byte[] cipherBytes = rootCipher.doFinal(keyBytes)
-
- Base64.encoder.encodeToString(CryptoUtils.concatByteArrays(ivBytes, cipherBytes))
- }
-
- @Test
- void testShouldBuildStaticKeyProvider() {
- // Arrange
- String staticProvider = StaticKeyProvider.class.name
- String providerLocation = null
-
- // Act
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(staticProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], null)
- logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
-
- // Assert
- assert keyProvider instanceof StaticKeyProvider
- assert keyProvider.getAvailableKeyIds() == [KEY_ID]
- }
-
- @Test
- void testShouldBuildStaticKeyProviderWithLegacyPackage() {
- // Arrange
- String staticProvider = LEGACY_SKP_FQCN
- String providerLocation = null
-
- // Act
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(staticProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], null)
- logger.info("Key Provider ${staticProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
-
- // Assert
- assert keyProvider instanceof StaticKeyProvider
- assert keyProvider.getAvailableKeyIds() == [KEY_ID]
- }
-
- @Test
- void testShouldBuildFileBasedKeyProvider() {
- // Arrange
- String fileBasedProvider = FileBasedKeyProvider.class.name
- File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
- String providerLocation = fileBasedProviderFile.path
- populateKeyDefinitionsFile(providerLocation)
- logger.info("Created temporary file based key provider: ${providerLocation}")
-
- // Act
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(fileBasedProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], ROOT_KEY)
- logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
-
- // Assert
- assert keyProvider instanceof FileBasedKeyProvider
- assert keyProvider.getAvailableKeyIds() == [KEY_ID]
- }
-
- @Test
- void testShouldBuildFileBasedKeyProviderWithLegacyPackage() {
- // Arrange
- String fileBasedProvider = LEGACY_FBKP_FQCN
- File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
- String providerLocation = fileBasedProviderFile.path
- populateKeyDefinitionsFile(providerLocation)
- logger.info("Created temporary file based key provider: ${providerLocation}")
-
- // Act
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(fileBasedProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], ROOT_KEY)
- logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
-
- // Assert
- assert keyProvider instanceof FileBasedKeyProvider
- assert keyProvider.getAvailableKeyIds() == [KEY_ID]
- }
-
- @Test
- void testShouldNotBuildFileBasedKeyProviderWithoutRootKey() {
- // Arrange
- String fileBasedProvider = FileBasedKeyProvider.class.name
- File fileBasedProviderFile = tempFolder.newFile("filebased.kp")
- String providerLocation = fileBasedProviderFile.path
- populateKeyDefinitionsFile(providerLocation)
- logger.info("Created temporary file based key provider: ${providerLocation}")
-
- // Act
- def msg = shouldFail(KeyManagementException) {
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(fileBasedProvider, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], null)
- logger.info("Key Provider ${fileBasedProvider} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
- }
-
- // Assert
- assert msg =~ "The root key must be provided to decrypt the individual keys"
- }
-
- @Test
- void testShouldNotBuildUnknownKeyProvider() {
- // Arrange
- String providerImplementation = "org.apache.nifi.provenance.ImaginaryKeyProvider"
- String providerLocation = null
-
- // Act
- def msg = shouldFail(KeyManagementException) {
- KeyProvider keyProvider = KeyProviderFactory.buildKeyProvider(providerImplementation, providerLocation, KEY_ID, [(KEY_ID): KEY_HEX], null)
- logger.info("Key Provider ${providerImplementation} with location ${providerLocation} and keyId ${KEY_ID} / ${KEY_HEX} formed: ${keyProvider}")
- }
-
- // Assert
- assert msg =~ "Invalid key provider implementation provided"
- }
-}
diff --git a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
index a4db650..12d76dd 100644
--- a/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
+++ b/nifi-commons/nifi-security-utils/src/test/java/org/apache/nifi/security/util/KeyStoreUtilsTest.java
@@ -21,6 +21,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -31,9 +32,13 @@
import java.security.cert.X509Certificate;
import java.util.UUID;
+import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -46,13 +51,20 @@
private static final String ALIAS = "alias";
private static final String KEY_ALGORITHM = "RSA";
private static final String SUBJECT_DN = "CN=localhost";
+ private static final String SECRET_KEY_ALGORITHM = "AES";
+ private static final String KEY_PROTECTION_ALGORITHM = "PBEWithHmacSHA256AndAES_256";
+ private static final String HYPHEN_SEPARATOR = "-";
+
private static KeyPair keyPair;
private static X509Certificate certificate;
+ private static SecretKey secretKey;
@BeforeClass
public static void generateKeysAndCertificates() throws NoSuchAlgorithmException, CertificateException {
keyPair = KeyPairGenerator.getInstance(KEY_ALGORITHM).generateKeyPair();
certificate = CertificateUtils.generateSelfSignedX509Certificate(keyPair, SUBJECT_DN, SIGNING_ALGORITHM, DURATION_DAYS);
+ final byte[] encodedKey = StringUtils.remove(UUID.randomUUID().toString(), HYPHEN_SEPARATOR).getBytes(StandardCharsets.UTF_8);
+ secretKey = new SecretKeySpec(encodedKey, SECRET_KEY_ALGORITHM);
}
@Test
@@ -91,6 +103,21 @@
}
}
+ @Test
+ public void testKeystoreTypesSecretKeyEntry() throws GeneralSecurityException, IOException {
+ for (final KeystoreType keystoreType : KeystoreType.values()) {
+ if (KeyStoreUtils.isSecretKeyEntrySupported(keystoreType)) {
+ final KeyStore sourceKeyStore = KeyStoreUtils.getSecretKeyStore(keystoreType.getType());
+ final KeyStore destinationKeyStore = KeyStoreUtils.getSecretKeyStore(keystoreType.getType());
+ try {
+ assertSecretKeyStoredLoaded(sourceKeyStore, destinationKeyStore);
+ } catch (final GeneralSecurityException e) {
+ throw new GeneralSecurityException(String.format("Keystore Type [%s] Failed", keystoreType), e);
+ }
+ }
+ }
+ }
+
private void assertCertificateEntryStoredLoaded(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
sourceKeyStore.load(null, null);
sourceKeyStore.setCertificateEntry(ALIAS, certificate);
@@ -115,6 +142,16 @@
assertEquals(String.format("[%s] Public Key not matched", sourceKeyStore.getType()), keyPair.getPublic(), entryCertificateChain[0].getPublicKey());
}
+ private void assertSecretKeyStoredLoaded(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
+ sourceKeyStore.load(null, null);
+ final KeyStore.ProtectionParameter protection = getProtectionParameter(sourceKeyStore.getType());
+ sourceKeyStore.setEntry(ALIAS, new KeyStore.SecretKeyEntry(secretKey), protection);
+
+ final KeyStore copiedKeyStore = copyKeyStore(sourceKeyStore, destinationKeyStore);
+ final KeyStore.Entry entry = copiedKeyStore.getEntry(ALIAS, protection);
+ assertTrue(String.format("[%s] Secret Key entry not found", sourceKeyStore.getType()), entry instanceof KeyStore.SecretKeyEntry);
+ }
+
private KeyStore copyKeyStore(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
sourceKeyStore.store(byteArrayOutputStream, STORE_PASSWORD);
@@ -122,4 +159,13 @@
destinationKeyStore.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), STORE_PASSWORD);
return destinationKeyStore;
}
+
+ private KeyStore.ProtectionParameter getProtectionParameter(final String keyStoreType) {
+ if (KeystoreType.PKCS12.getType().equals(keyStoreType)) {
+ // Select Key Protection Algorithm for PKCS12 to avoid unsupported algorithm on Java 1.8.0.292
+ return new KeyStore.PasswordProtection(KEY_PASSWORD, KEY_PROTECTION_ALGORITHM, null);
+ } else {
+ return new KeyStore.PasswordProtection(KEY_PASSWORD);
+ }
+ }
}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 368a67a..59a0b58 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -43,6 +43,7 @@
<module>nifi-rocksdb-utils</module>
<module>nifi-schema-utils</module>
<module>nifi-security-kerberos</module>
+ <module>nifi-security-kms</module>
<module>nifi-security-socket-ssl</module>
<module>nifi-security-utils-api</module>
<module>nifi-security-utils</module>
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index a231d46..38e5765 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -2865,8 +2865,9 @@
|====
|*Property*|*Description*
-|`nifi.flowfile.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the content claims. There are currently two implementations -- `StaticKeyProvider` which reads a key directly from _nifi.properties_, and `FileBasedKeyProvider` which reads *n* many keys from an encrypted file. The interface is extensible, and HSM-backed or other providers are expected in the future.
+|`nifi.flowfile.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the content claims. There are currently three implementations: `StaticKeyProvider` which reads a key directly from _nifi.properties_, `FileBasedKeyProvider` which reads keys from an encrypted file, and `KeyStoreKeyProvider` which reads keys from a standard `java.security.KeyStore`.
|`nifi.flowfile.repository.encryption.key.provider.location`|The path to the key definition resource (empty for `StaticKeyProvider`, `./keys.nkp` or similar path for `FileBasedKeyProvider`). For future providers like an HSM, this may be a connection string or URL.
+|`nifi.flowfile.repository.encryption.key.provider.password`|The password used for decrypting the key definition resource, such as the keystore for `KeyStoreKeyProvider`.
|`nifi.flowfile.repository.encryption.key.id`|The active key ID to use for encryption (e.g. `Key1`).
|`nifi.flowfile.repository.encryption.key`|The key to use for `StaticKeyProvider`. The key format is hex-encoded (`0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210`) but can also be encrypted using the `./encrypt-config.sh` tool in NiFi Toolkit (see the <<toolkit-guide.adoc#encrypt_config_tool,Encrypt-Config Tool>> section in the link:toolkit-guide.html[NiFi Toolkit Guide] for more information).
|`nifi.flowfile.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.flowfile.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
@@ -3047,8 +3048,9 @@
|====
|*Property*|*Description*
- |`nifi.content.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the content claims. There are currently two implementations -- `StaticKeyProvider` which reads a key directly from _nifi.properties_, and `FileBasedKeyProvider` which reads *n* many keys from an encrypted file. The interface is extensible, and HSM-backed or other providers are expected in the future.
+ |`nifi.content.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the content claims. There are currently three implementations: `StaticKeyProvider` which reads a key directly from _nifi.properties_, `FileBasedKeyProvider` which reads keys from an encrypted file, and `KeyStoreKeyProvider` which reads keys from a standard `java.security.KeyStore`.
|`nifi.content.repository.encryption.key.provider.location`|The path to the key definition resource (empty for `StaticKeyProvider`, `./keys.nkp` or similar path for `FileBasedKeyProvider`). For future providers like an HSM, this may be a connection string or URL.
+ |`nifi.content.repository.encryption.key.provider.password`|The password used for decrypting the key definition resource, such as the keystore for `KeyStoreKeyProvider`.
|`nifi.content.repository.encryption.key.id`|The active key ID to use for encryption (e.g. `Key1`).
|`nifi.content.repository.encryption.key`|The key to use for `StaticKeyProvider`. The key format is hex-encoded (`0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210`) but can also be encrypted using the `./encrypt-config.sh` tool in NiFi Toolkit (see the <<toolkit-guide.adoc#encrypt_config_tool,Encrypt-Config Tool>> section in the link:toolkit-guide.html[NiFi Toolkit Guide] for more information).
|`nifi.content.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.content.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
@@ -3173,8 +3175,9 @@
|====
|*Property*|*Description*
- |`nifi.provenance.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the provenance events. There are currently two implementations -- `StaticKeyProvider` which reads a key directly from _nifi.properties_, and `FileBasedKeyProvider` which reads *n* many keys from an encrypted file. The interface is extensible, and HSM-backed or other providers are expected in the future.
+ |`nifi.provenance.repository.encryption.key.provider.implementation`|This is the fully-qualified class name of the **key provider**. A key provider is the datastore interface for accessing the encryption key to protect the provenance events. There are currently three implementations: `StaticKeyProvider` which reads a key directly from _nifi.properties_, `FileBasedKeyProvider` which reads keys from an encrypted file, and `KeyStoreKeyProvider` which reads keys from a standard `java.security.KeyStore`.
|`nifi.provenance.repository.encryption.key.provider.location`|The path to the key definition resource (empty for `StaticKeyProvider`, `./keys.nkp` or similar path for `FileBasedKeyProvider`). For future providers like an HSM, this may be a connection string or URL.
+ |`nifi.provenance.repository.encryption.key.provider.password`|The password used for decrypting the key definition resource, such as the keystore for `KeyStoreKeyProvider`.
|`nifi.provenance.repository.encryption.key.id`|The active key ID to use for encryption (e.g. `Key1`).
|`nifi.provenance.repository.encryption.key`|The key to use for `StaticKeyProvider`. The key format is hex-encoded (`0123456789ABCDEFFEDCBA98765432100123456789ABCDEFFEDCBA9876543210`) but can also be encrypted using the `./encrypt-config.sh` tool in NiFi Toolkit (see the <<toolkit-guide.adoc#encrypt_config_tool,Encrypt-Config Tool>> section in the link:toolkit-guide.html[NiFi Toolkit Guide] for more information).
|`nifi.provenance.repository.encryption.key.id.`*|Allows for additional keys to be specified for the `StaticKeyProvider`. For example, the line `nifi.provenance.repository.encryption.key.id.Key2=012...210` would provide an available key `Key2`.
diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc b/nifi-docs/src/main/asciidoc/user-guide.adoc
index feaa9ac..0316a0e 100644
--- a/nifi-docs/src/main/asciidoc/user-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/user-guide.adoc
@@ -2934,6 +2934,34 @@
Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
+===== KeyStoreKeyProvider
+The `KeyStoreKeyProvider` implementation reads from a standard `java.security.KeyStore` using the configured password to load AES Secret Key entries.
+
+The provider supports the following Keystore Types:
+
+* BCFKS
+* PKCS12
+
+The keystore filename extension must be either `.p12` indicating PKCS12 or `.bcfks` indicating BCFKS.
+
+The `keytool` command can be used to generate an AES-256 Secret Key stored in a PKCS12 file for repository encryption:
+
+...
+keytool -genseckey -alias primary-key -keyalg AES -keysize 256 -keystore repository.p12 -storetype PKCS12
+...
+
+Enter a keystore password when prompted. The same value must be used for both the keystore password and key password.
+The keystore password will be used in the provider configuration properties.
+
+The following configuration properties support using a PKCS12 keystore with a Secret Key:
+
+...
+nifi.provenance.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
+nifi.provenance.repository.encryption.key.provider.location=./conf/repository.p12
+nifi.provenance.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
+nifi.provenance.repository.encryption.key.id=primary-key
+...
+
[[provenance-repository-key-rotation]]
===== Key Rotation
Simply update _nifi.properties_ to reference a new key ID in `nifi.provenance.repository.encryption.key.id`. Previously-encrypted events can still be decrypted as long as that key is still available in the key definition file or `nifi.provenance.repository.encryption.key.id.<OldKeyID>` as the key ID is serialized alongside the encrypted record.
@@ -3013,6 +3041,34 @@
Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
+==== KeyStoreKeyProvider
+The `KeyStoreKeyProvider` implementation reads from a standard `java.security.KeyStore` using the configured password to load AES Secret Key entries.
+
+The provider supports the following Keystore Types:
+
+* BCFKS
+* PKCS12
+
+The keystore filename extension must be either `.p12` indicating PKCS12 or `.bcfks` indicating BCFKS.
+
+The `keytool` command can be used to generate an AES-256 Secret Key stored in a PKCS12 file for repository encryption:
+
+...
+keytool -genseckey -alias primary-key -keyalg AES -keysize 256 -keystore repository.p12 -storetype PKCS12
+...
+
+Enter a keystore password when prompted. The same value must be used for both the keystore password and key password.
+The keystore password will be used in the provider configuration properties.
+
+The following configuration properties support using a PKCS12 keystore with a Secret Key:
+
+...
+nifi.content.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
+nifi.content.repository.encryption.key.provider.location=./conf/repository.p12
+nifi.content.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
+nifi.content.repository.encryption.key.id=primary-key
+...
+
.Data Protection vs. Key Protection
****
Even though the flowfile content is encrypted using `AES/CTR` to handle streaming data, if using the _Config Encrypt
@@ -3099,6 +3155,34 @@
Each line defines a key ID and then the Base64-encoded cipher text of a 16 byte IV and wrapped AES-128, AES-192, or AES-256 key depending on the JCE policies available. The individual keys are wrapped by AES/GCM encryption using the **root key** defined by `nifi.bootstrap.sensitive.key` in _conf/bootstrap.conf_.
+==== KeyStoreKeyProvider
+The `KeyStoreKeyProvider` implementation reads from a standard `java.security.KeyStore` using the configured password to load AES Secret Key entries.
+
+The provider supports the following Keystore Types:
+
+* BCFKS
+* PKCS12
+
+The keystore filename extension must be either `.p12` indicating PKCS12 or `.bcfks` indicating BCFKS.
+
+The `keytool` command can be used to generate an AES-256 Secret Key stored in a PKCS12 file for repository encryption:
+
+...
+keytool -genseckey -alias primary-key -keyalg AES -keysize 256 -keystore repository.p12 -storetype PKCS12
+...
+
+Enter a keystore password when prompted. The same value must be used for both the keystore password and key password.
+The keystore password will be used in the provider configuration properties.
+
+The following configuration properties support using a PKCS12 keystore with a Secret Key:
+
+...
+nifi.flowfile.repository.encryption.key.provider.implementation=org.apache.nifi.security.kms.KeyStoreKeyProvider
+nifi.flowfile.repository.encryption.key.provider.location=./conf/repository.p12
+nifi.flowfile.repository.encryption.key.provider.password=KEYSTORE_PASSWORD
+nifi.flowfile.repository.encryption.key.id=primary-key
+...
+
[[flowfile-repository-key-rotation]]
==== Key Rotation
Simply update _nifi.properties_ to reference a new key ID in `nifi.flowfile.repository.encryption.key.id`. Previously-encrypted flowfile records can still be decrypted as long as that key is still available in the key definition file or `nifi.flowfile.repository.encryption.key.id.<OldKeyID>` as the key ID is serialized alongside the encrypted record.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy
index 9e0cf32..d6a5418 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedSchemaRepositoryRecordSerdeTest.groovy
@@ -276,7 +276,7 @@
// Configure the serde with multiple keys available
def multipleKeys = KEYS + [K2: "0F" * 32]
- FlowFileRepositoryEncryptionConfiguration multipleKeyFFREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, multipleKeys, REPO_IMPL)
+ FlowFileRepositoryEncryptionConfiguration multipleKeyFFREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, multipleKeys, REPO_IMPL, null)
esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, multipleKeyFFREC)
assert esrrs.getActiveKeyId() == "K1"
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
index 61d0b7a..f50a160 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/wali/EncryptedSequentialAccessWriteAheadLogTest.groovy
@@ -92,7 +92,7 @@
dataOutputStream = new DataOutputStream(byteArrayOutputStream)
wrappedSerDe = new SchemaRepositoryRecordSerde(claimManager, new NoOpFieldCache())
- flowFileREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, KEYS, REPO_IMPL)
+ flowFileREC = new FlowFileRepositoryEncryptionConfiguration(KPI, KPL, KEY_ID, KEYS, REPO_IMPL, null)
esrrs = new EncryptedSchemaRepositoryRecordSerde(wrappedSerDe, flowFileREC)
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
index 368ff03..ace5016 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
@@ -48,8 +48,16 @@
public static final String ADDITIONAL_SENSITIVE_PROPERTIES_KEY = "nifi.sensitive.props.additional.keys";
// Default list of "sensitive" property keys
- public static final List<String> DEFAULT_SENSITIVE_PROPERTIES = new ArrayList<>(asList(SECURITY_KEY_PASSWD,
- SECURITY_KEYSTORE_PASSWD, SECURITY_TRUSTSTORE_PASSWD, SENSITIVE_PROPS_KEY, PROVENANCE_REPO_ENCRYPTION_KEY));
+ public static final List<String> DEFAULT_SENSITIVE_PROPERTIES = new ArrayList<>(asList(
+ SECURITY_KEY_PASSWD,
+ SECURITY_KEYSTORE_PASSWD,
+ SECURITY_TRUSTSTORE_PASSWD,
+ SENSITIVE_PROPS_KEY,
+ PROVENANCE_REPO_ENCRYPTION_KEY,
+ PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD,
+ FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD,
+ CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD
+ ));
public ProtectedNiFiProperties() {
this(new NiFiProperties());
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
index f902dbc..6c49f6d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
@@ -41,7 +41,10 @@
"nifi.security.keystorePasswd",
"nifi.security.keyPasswd",
"nifi.security.truststorePasswd",
- "nifi.provenance.repository.encryption.key"
+ "nifi.provenance.repository.encryption.key",
+ "nifi.provenance.repository.encryption.key.provider.password",
+ "nifi.flowfile.repository.encryption.key.provider.password",
+ "nifi.content.repository.encryption.key.provider.password"
]
final def COMMON_ADDITIONAL_SENSITIVE_PROPERTIES = [
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index 772fe12..cf0c531 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -65,6 +65,7 @@
nifi.flowfile.repository.always.sync=${nifi.flowfile.repository.always.sync}
nifi.flowfile.repository.encryption.key.provider.implementation=${nifi.flowfile.repository.encryption.key.provider.implementation}
nifi.flowfile.repository.encryption.key.provider.location=${nifi.flowfile.repository.encryption.key.provider.location}
+nifi.flowfile.repository.encryption.key.provider.password=
nifi.flowfile.repository.encryption.key.id=${nifi.flowfile.repository.encryption.key.id}
nifi.flowfile.repository.encryption.key=${nifi.flowfile.repository.encryption.key}
nifi.flowfile.repository.retain.orphaned.flowfiles=${nifi.flowfile.repository.retain.orphaned.flowfiles}
@@ -83,6 +84,7 @@
nifi.content.viewer.url=${nifi.content.viewer.url}
nifi.content.repository.encryption.key.provider.implementation=${nifi.content.repository.encryption.key.provider.implementation}
nifi.content.repository.encryption.key.provider.location=${nifi.content.repository.encryption.key.provider.location}
+nifi.content.repository.encryption.key.provider.password=
nifi.content.repository.encryption.key.id=${nifi.content.repository.encryption.key.id}
nifi.content.repository.encryption.key=${nifi.content.repository.encryption.key}
@@ -90,6 +92,7 @@
nifi.provenance.repository.implementation=${nifi.provenance.repository.implementation}
nifi.provenance.repository.encryption.key.provider.implementation=${nifi.provenance.repository.encryption.key.provider.implementation}
nifi.provenance.repository.encryption.key.provider.location=${nifi.provenance.repository.encryption.key.provider.location}
+nifi.provenance.repository.encryption.key.provider.password=
nifi.provenance.repository.encryption.key.id=${nifi.provenance.repository.encryption.key.id}
nifi.provenance.repository.encryption.key=${nifi.provenance.repository.encryption.key}
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
index 3d83217..d8a8051 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/pom.xml
@@ -47,6 +47,21 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-utils</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-utils-api</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-kms</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
index 24d80d6..198f362 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java
@@ -25,14 +25,14 @@
import org.apache.nifi.provenance.toc.StandardTocWriter;
import org.apache.nifi.provenance.toc.TocUtil;
import org.apache.nifi.provenance.toc.TocWriter;
-import org.apache.nifi.security.kms.CryptoUtils;
import org.apache.nifi.security.kms.KeyProvider;
-import org.apache.nifi.security.kms.KeyProviderFactory;
+import org.apache.nifi.security.repository.RepositoryEncryptorUtils;
+import org.apache.nifi.security.repository.config.ProvenanceRepositoryEncryptionConfiguration;
+import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.crypto.SecretKey;
import java.io.IOException;
import java.security.KeyManagementException;
@@ -84,13 +84,7 @@
ProvenanceEventEncryptor provenanceEventEncryptor;
if (getConfig().supportsEncryption()) {
try {
- KeyProvider keyProvider;
- if (KeyProviderFactory.requiresRootKey(getConfig().getKeyProviderImplementation())) {
- SecretKey rootKey = CryptoUtils.getRootKey();
- keyProvider = buildKeyProvider(rootKey);
- } else {
- keyProvider = buildKeyProvider();
- }
+ final KeyProvider keyProvider = buildKeyProvider();
provenanceEventEncryptor = new AESProvenanceEventEncryptor();
provenanceEventEncryptor.initialize(keyProvider);
} catch (KeyManagementException e) {
@@ -130,22 +124,16 @@
super.init(recordWriterFactory, recordReaderFactory, eventReporter, authorizer, resourceFactory);
}
- private KeyProvider buildKeyProvider() throws KeyManagementException {
- return buildKeyProvider(null);
- }
-
- private KeyProvider buildKeyProvider(SecretKey rootKey) throws KeyManagementException {
- RepositoryConfiguration config = super.getConfig();
- if (config == null) {
- throw new KeyManagementException("The repository configuration is missing");
- }
-
- final String implementationClassName = config.getKeyProviderImplementation();
- if (implementationClassName == null) {
- throw new KeyManagementException("Cannot create Key Provider because the NiFi Properties is missing the following property: "
- + NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS);
- }
-
- return KeyProviderFactory.buildKeyProvider(implementationClassName, config.getKeyProviderLocation(), config.getKeyId(), config.getEncryptionKeys(), rootKey);
+ private KeyProvider buildKeyProvider() throws IOException {
+ final RepositoryConfiguration config = getConfig();
+ final RepositoryEncryptionConfiguration configuration = new ProvenanceRepositoryEncryptionConfiguration(
+ config.getKeyProviderImplementation(),
+ config.getKeyProviderLocation(),
+ config.getKeyId(),
+ config.getEncryptionKeys(),
+ getClass().getName(),
+ config.getKeyProviderPassword()
+ );
+ return RepositoryEncryptorUtils.validateAndBuildRepositoryKeyProvider(configuration);
}
}
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
index 53549aa..77c080a 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/RepositoryConfiguration.java
@@ -59,6 +59,7 @@
private String keyId;
private String keyProviderImplementation;
private String keyProviderLocation;
+ private String keyProviderPassword;
private List<SearchableField> searchableFields = new ArrayList<>();
private List<SearchableField> searchableAttributes = new ArrayList<>();
@@ -409,6 +410,13 @@
this.keyProviderLocation = keyProviderLocation;
}
+ public String getKeyProviderPassword() {
+ return keyProviderPassword;
+ }
+
+ public void setKeyProviderPassword(final String keyProviderPassword) {
+ this.keyProviderPassword = keyProviderPassword;
+ }
public int getDebugFrequency() {
return debugFrequency;
@@ -519,6 +527,7 @@
config.setKeyId(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_ID));
config.setKeyProviderImplementation(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS));
config.setKeyProviderLocation(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION));
+ config.setKeyProviderPassword(nifiProperties.getProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD));
}
return config;
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-provenance-repository-nar/pom.xml b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-provenance-repository-nar/pom.xml
index a2b7504..0e93b5a 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-provenance-repository-nar/pom.xml
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-provenance-repository-nar/pom.xml
@@ -34,5 +34,11 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-volatile-provenance-repository</artifactId>
</dependency>
+ <!-- Override provided scope to include in NAR -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-security-utils-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
</project>