blob: e13b225c7980c2dcf7cfb9a1e490c875e646a85e [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.repository;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.Security;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.security.kms.EncryptionException;
import org.apache.nifi.security.kms.KeyProvider;
import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractAESEncryptor implements RepositoryObjectEncryptor {
private static final Logger logger = LoggerFactory.getLogger(AbstractAESEncryptor.class);
private static final byte[] EM_START_SENTINEL = new byte[]{0x00, 0x00};
private static final byte[] EM_END_SENTINEL = new byte[]{(byte) 0xFF, (byte) 0xFF};
private static String ALGORITHM = "AES/CTR/NoPadding";
protected static final int IV_LENGTH = 16;
protected 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);
protected KeyProvider keyProvider;
protected AESKeyedCipherProvider aesKeyedCipherProvider = new AESKeyedCipherProvider();
/**
* Initializes the encryptor with a {@link KeyProvider}.
*
* @param keyProvider the key provider which will be responsible for accessing keys
* @throws KeyManagementException if there is an issue configuring the key provider
*/
@Override
public void initialize(KeyProvider keyProvider) throws KeyManagementException {
this.keyProvider = keyProvider;
if (this.aesKeyedCipherProvider == null) {
this.aesKeyedCipherProvider = new AESKeyedCipherProvider();
}
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* Available for dependency injection to override the default {@link AESKeyedCipherProvider} if necessary.
*
* @param cipherProvider the AES cipher provider to use
*/
void setCipherProvider(AESKeyedCipherProvider cipherProvider) {
this.aesKeyedCipherProvider = cipherProvider;
}
/**
* Utility method which extracts the {@link RepositoryObjectEncryptionMetadata} object from the {@code byte[]} or
* {@link InputStream} provided and verifies common validation across both streaming and block decryption. Returns
* the extracted metadata object.
*
* @param ciphertextSource the encrypted source -- can be {@code byte[]} or {@code InputStream}
* @param identifier the unique identifier for this source
* @param descriptor the generic name for this source type for logging/error messages
* @param supportedVersions the list of supported versions for the particular encryptor calling this method (see
* {@link org.apache.nifi.security.repository.stream.aes.RepositoryObjectAESCTREncryptor} and
* {@link org.apache.nifi.security.repository.block.aes.RepositoryObjectAESGCMEncryptor} for
* {@code SUPPORTED_VERSIONS})
* @return the extracted {@link RepositoryObjectEncryptionMetadata} object
* @throws EncryptionException if there is an exception parsing or validating the source
*/
public static RepositoryObjectEncryptionMetadata prepareObjectForDecryption(Object ciphertextSource,
String identifier, String descriptor, List<String> supportedVersions) throws EncryptionException {
if (ciphertextSource == null) {
throw new EncryptionException("The encrypted " + descriptor + " cannot be missing");
}
RepositoryObjectEncryptionMetadata metadata;
try {
if (ciphertextSource instanceof InputStream) {
logger.debug("Detected encrypted input stream for {} with ID {}", descriptor, identifier);
InputStream ciphertextStream = (InputStream) ciphertextSource;
metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(ciphertextStream);
} else if (ciphertextSource instanceof byte[]) {
logger.debug("Detected byte[] for {} with ID {}", descriptor, identifier);
byte[] ciphertextBytes = (byte[]) ciphertextSource;
metadata = RepositoryEncryptorUtils.extractEncryptionMetadata(ciphertextBytes);
} else {
String errorMsg = "The " + descriptor + " with ID " + identifier + " was detected as " + ciphertextSource.getClass().getSimpleName() + "; this is not a supported source of ciphertext";
logger.error(errorMsg);
throw new EncryptionException(errorMsg);
}
} catch (IOException | ClassNotFoundException e) {
final String msg = "Encountered an error reading the encryption metadata: ";
logger.error(msg, e);
throw new EncryptionException(msg, e);
}
if (!supportedVersions.contains(metadata.version)) {
throw new EncryptionException("The " + descriptor + " with ID " + identifier
+ " was encrypted with version " + metadata.version
+ " which is not in the list of supported versions " + StringUtils.join(supportedVersions, ","));
}
return metadata;
}
}