blob: d5464ae884bd48a054688922d7d2ae386987e381 [file] [log] [blame]
/*
* 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);
}
}
}