NIFI-30 NIFI-374 EncryptContent support of PGP

This commit is contained in:
Mike Moser 2015-04-20 21:26:24 -04:00
parent 53b6ac3333
commit 156d9409ff
11 changed files with 1170 additions and 329 deletions

View File

@ -46,7 +46,10 @@ public enum EncryptionMethod {
SHA_256AES("PBEWITHSHAAND256BITAES-CBC-BC", "BC", true), SHA_256AES("PBEWITHSHAAND256BITAES-CBC-BC", "BC", true),
SHA_3KEYTRIPLEDES("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "BC", true), SHA_3KEYTRIPLEDES("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", "BC", true),
SHA_TWOFISH("PBEWITHSHAANDTWOFISH-CBC", "BC", true), SHA_TWOFISH("PBEWITHSHAANDTWOFISH-CBC", "BC", true),
SHA_128RC4("PBEWITHSHAAND128BITRC4", "BC", true); SHA_128RC4("PBEWITHSHAAND128BITRC4", "BC", true),
PGP("PGP", "BC", false),
PGP_ASCII_ARMOR("PGP-ASCII-ARMOR", "BC", false);
private final String algorithm; private final String algorithm;
private final String provider; private final String provider;
private final boolean unlimitedStrength; private final boolean unlimitedStrength;

View File

@ -80,6 +80,10 @@
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId> <artifactId>bcprov-jdk16</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk16</artifactId>
</dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
@ -186,6 +190,8 @@
<exclude>src/test/resources/ScanAttribute/dictionary-with-empty-new-lines</exclude> <exclude>src/test/resources/ScanAttribute/dictionary-with-empty-new-lines</exclude>
<exclude>src/test/resources/ScanAttribute/dictionary-with-extra-info</exclude> <exclude>src/test/resources/ScanAttribute/dictionary-with-extra-info</exclude>
<exclude>src/test/resources/ScanAttribute/dictionary1</exclude> <exclude>src/test/resources/ScanAttribute/dictionary1</exclude>
<exclude>src/test/resources/TestEncryptContent/text.txt</exclude>
<exclude>src/test/resources/TestEncryptContent/text.txt.asc</exclude>
<exclude>src/test/resources/TestIdentifyMimeType/1.txt</exclude> <exclude>src/test/resources/TestIdentifyMimeType/1.txt</exclude>
<exclude>src/test/resources/TestJson/json-sample.json</exclude> <exclude>src/test/resources/TestJson/json-sample.json</exclude>
<exclude>src/test/resources/TestMergeContent/demarcate</exclude> <exclude>src/test/resources/TestMergeContent/demarcate</exclude>

View File

@ -1,263 +1,306 @@
/* /*
* Licensed to the Apache Software Foundation (ASF) under one or more * Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with * contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. * this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0 * 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 not use this file except in compliance with
* the License. You may obtain a copy of the License at * the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.processors.standard; package org.apache.nifi.processors.standard;
import org.apache.nifi.processor.ProcessContext; import java.security.Security;
import org.apache.nifi.processor.AbstractProcessor; import java.text.Normalizer;
import org.apache.nifi.processor.ProcessorInitializationContext; import java.util.ArrayList;
import org.apache.nifi.processor.ProcessSession; import java.util.Collection;
import org.apache.nifi.processor.Relationship; import java.util.Collections;
import org.apache.nifi.components.PropertyDescriptor; import java.util.HashSet;
import org.apache.nifi.flowfile.FlowFile; import java.util.List;
import org.apache.nifi.stream.io.StreamUtils; import java.util.Set;
import org.apache.nifi.logging.ProcessorLog; import java.util.concurrent.TimeUnit;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.behavior.EventDriven; import org.apache.nifi.annotation.behavior.EventDriven;
import org.apache.nifi.annotation.behavior.SideEffectFree; import org.apache.nifi.annotation.behavior.SideEffectFree;
import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.behavior.SupportsBatching;
import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.processor.io.StreamCallback; import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.security.util.EncryptionMethod; import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.util.StopWatch; import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.apache.nifi.logging.ProcessorLog;
import org.apache.nifi.processor.AbstractProcessor;
import javax.crypto.*; import org.apache.nifi.processor.ProcessContext;
import javax.crypto.spec.PBEKeySpec; import org.apache.nifi.processor.ProcessSession;
import javax.crypto.spec.PBEParameterSpec; import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.Relationship;
import java.io.IOException; import org.apache.nifi.processor.exception.ProcessException;
import java.io.InputStream; import org.apache.nifi.processor.io.StreamCallback;
import java.io.OutputStream; import org.apache.nifi.processor.util.StandardValidators;
import java.security.InvalidAlgorithmParameterException; import org.apache.nifi.processors.standard.util.OpenPGPKeyBasedEncryptor;
import java.security.InvalidKeyException; import org.apache.nifi.processors.standard.util.OpenPGPPasswordBasedEncryptor;
import java.security.SecureRandom; import org.apache.nifi.processors.standard.util.PasswordBasedEncryptor;
import java.security.Security; import org.apache.nifi.security.util.EncryptionMethod;
import java.text.Normalizer; import org.apache.nifi.util.StopWatch;
import java.util.*; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.util.concurrent.TimeUnit;
@EventDriven
@EventDriven @SideEffectFree
@SideEffectFree @SupportsBatching
@SupportsBatching @Tags({"encryption", "decryption", "password", "JCE", "OpenPGP", "PGP", "GPG"})
@Tags({"encryption", "decryption", "password", "JCE"}) @CapabilityDescription("Encrypts or Decrypts a FlowFile using either symmetric encryption with a password and randomly generated salt, or asymmetric encryption using a public and secret key.")
@CapabilityDescription("Encrypts or Decrypts a FlowFile using a randomly generated salt") public class EncryptContent extends AbstractProcessor {
public class EncryptContent extends AbstractProcessor {
public static final String ENCRYPT_MODE = "Encrypt";
public static final String ENCRYPT_MODE = "Encrypt"; public static final String DECRYPT_MODE = "Decrypt";
public static final String DECRYPT_MODE = "Decrypt";
public static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG"; public static final PropertyDescriptor MODE = new PropertyDescriptor.Builder()
public static final int DEFAULT_SALT_SIZE = 8; .name("Mode")
.description("Specifies whether the content should be encrypted or decrypted")
public static final PropertyDescriptor MODE = new PropertyDescriptor.Builder() .required(true)
.name("Mode") .allowableValues(ENCRYPT_MODE, DECRYPT_MODE)
.description("Specifies whether the content should be encrypted or decrypted") .defaultValue(ENCRYPT_MODE)
.required(true) .build();
.allowableValues(ENCRYPT_MODE, DECRYPT_MODE) public static final PropertyDescriptor ENCRYPTION_ALGORITHM = new PropertyDescriptor.Builder()
.defaultValue(ENCRYPT_MODE) .name("Encryption Algorithm")
.build(); .description("The Encryption Algorithm to use")
public static final PropertyDescriptor ENCRYPTION_ALGORITHM = new PropertyDescriptor.Builder() .required(true)
.name("Encryption Algorithm") .allowableValues(EncryptionMethod.values())
.description("The Encryption Algorithm to use") .defaultValue(EncryptionMethod.MD5_256AES.name())
.required(true) .build();
.allowableValues(EncryptionMethod.values()) public static final PropertyDescriptor PASSWORD = new PropertyDescriptor.Builder()
.defaultValue(EncryptionMethod.MD5_256AES.name()) .name("Password")
.build(); .description("The Password to use for encrypting or decrypting the data")
public static final PropertyDescriptor PASSWORD = new PropertyDescriptor.Builder() .required(false)
.name("Password") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.description("The Password to use for encrypting or decrypting the data") .sensitive(true)
.required(true) .build();
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) public static final PropertyDescriptor PUBLIC_KEYRING = new PropertyDescriptor.Builder()
.sensitive(true) .name("public-keyring-file")
.build(); .displayName("Public Keyring File")
.description("In a PGP encrypt mode, this keyring contains the public key of the recipient")
public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description("Any FlowFile that is successfully encrypted or decrypted will be routed to success").build(); .required(false)
public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure").description("Any FlowFile that cannot be encrypted or decrypted will be routed to failure").build(); .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
private List<PropertyDescriptor> properties; public static final PropertyDescriptor PUBLIC_KEY_USERID = new PropertyDescriptor.Builder()
private Set<Relationship> relationships; .name("public-key-user-id")
.displayName("Public Key User Id")
static { .description("In a PGP encrypt mode, this user id of the recipient")
// add BouncyCastle encryption providers .required(false)
Security.addProvider(new BouncyCastleProvider()); .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
} .build();
public static final PropertyDescriptor PRIVATE_KEYRING = new PropertyDescriptor.Builder()
@Override .name("private-keyring-file")
protected void init(final ProcessorInitializationContext context) { .displayName("Private Keyring File")
final List<PropertyDescriptor> properties = new ArrayList<>(); .description("In a PGP decrypt mode, this keyring contains the private key of the recipient")
properties.add(MODE); .required(false)
properties.add(ENCRYPTION_ALGORITHM); .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
properties.add(PASSWORD); .build();
this.properties = Collections.unmodifiableList(properties); public static final PropertyDescriptor PRIVATE_KEYRING_PASSPHRASE = new PropertyDescriptor.Builder()
.name("private-keyring-passphrase")
final Set<Relationship> relationships = new HashSet<>(); .displayName("Private Keyring Passphrase")
relationships.add(REL_SUCCESS); .description("In a PGP decrypt mode, this is the private keyring passphrase")
relationships.add(REL_FAILURE); .required(false)
this.relationships = Collections.unmodifiableSet(relationships); .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
} .sensitive(true)
.build();
@Override
public Set<Relationship> getRelationships() { public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success")
return relationships; .description("Any FlowFile that is successfully encrypted or decrypted will be routed to success").build();
} public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure")
.description("Any FlowFile that cannot be encrypted or decrypted will be routed to failure").build();
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { private List<PropertyDescriptor> properties;
return properties; private Set<Relationship> relationships;
}
static {
@Override // add BouncyCastle encryption providers
public void onTrigger(final ProcessContext context, final ProcessSession session) { Security.addProvider(new BouncyCastleProvider());
FlowFile flowFile = session.get(); }
if (flowFile == null) {
return; @Override
} protected void init(final ProcessorInitializationContext context) {
final List<PropertyDescriptor> properties = new ArrayList<>();
final ProcessorLog logger = getLogger(); properties.add(MODE);
final String method = context.getProperty(ENCRYPTION_ALGORITHM).getValue(); properties.add(ENCRYPTION_ALGORITHM);
final EncryptionMethod encryptionMethod = EncryptionMethod.valueOf(method); properties.add(PASSWORD);
final String providerName = encryptionMethod.getProvider(); properties.add(PUBLIC_KEYRING);
final String algorithm = encryptionMethod.getAlgorithm(); properties.add(PUBLIC_KEY_USERID);
properties.add(PRIVATE_KEYRING);
final String password = context.getProperty(PASSWORD).getValue(); properties.add(PRIVATE_KEYRING_PASSPHRASE);
final char[] normalizedPassword = Normalizer.normalize(password, Normalizer.Form.NFC).toCharArray(); this.properties = Collections.unmodifiableList(properties);
final PBEKeySpec pbeKeySpec = new PBEKeySpec(normalizedPassword);
final Set<Relationship> relationships = new HashSet<>();
final SecureRandom secureRandom; relationships.add(REL_SUCCESS);
final SecretKeyFactory factory; relationships.add(REL_FAILURE);
final SecretKey secretKey; this.relationships = Collections.unmodifiableSet(relationships);
final Cipher cipher; }
try {
secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM); @Override
secureRandom.setSeed(System.currentTimeMillis()); public Set<Relationship> getRelationships() {
factory = SecretKeyFactory.getInstance(algorithm, providerName); return relationships;
secretKey = factory.generateSecret(pbeKeySpec); }
cipher = Cipher.getInstance(algorithm, providerName);
} catch (final Exception e) { @Override
logger.error("failed to initialize Encryption/Decryption algorithm due to {}", new Object[]{e}); protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
session.transfer(flowFile, REL_FAILURE); return properties;
return; }
}
public static boolean isPGPAlgorithm(String algorithm) {
final int algorithmBlockSize = cipher.getBlockSize(); return algorithm.startsWith("PGP");
final int saltSize = (algorithmBlockSize > 0) ? algorithmBlockSize : DEFAULT_SALT_SIZE; }
final StopWatch stopWatch = new StopWatch(true); public static boolean isPGPArmoredAlgorithm(String algorithm) {
if (context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE)) { return isPGPAlgorithm(algorithm) && algorithm.endsWith("ASCII-ARMOR");
final byte[] salt = new byte[saltSize]; }
secureRandom.nextBytes(salt);
@Override
final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 1000); protected Collection<ValidationResult> customValidate(ValidationContext context) {
try { List<ValidationResult> validationResults = new ArrayList<>(super.customValidate(context));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); final String method = context.getProperty(ENCRYPTION_ALGORITHM).getValue();
} catch (final InvalidKeyException | InvalidAlgorithmParameterException e) { final String algorithm = EncryptionMethod.valueOf(method).getAlgorithm();
logger.error("unable to encrypt {} due to {}", new Object[]{flowFile, e}); final String password = context.getProperty(PASSWORD).getValue();
session.transfer(flowFile, REL_FAILURE); if (isPGPAlgorithm(algorithm)) {
return; if (password == null) {
} boolean encrypt = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE);
if (encrypt) {
flowFile = session.write(flowFile, new EncryptCallback(cipher, salt)); // need both public-keyring-file and public-key-user-id set
logger.info("Successfully encrypted {}", new Object[]{flowFile}); String publicKeyring = context.getProperty(PUBLIC_KEYRING).getValue();
} else { String publicUserId = context.getProperty(PUBLIC_KEY_USERID).getValue();
if (flowFile.getSize() <= saltSize) { if (publicKeyring == null || publicUserId == null) {
logger.error("Cannot decrypt {} because its file size is not greater than the salt size", new Object[]{flowFile}); validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
session.transfer(flowFile, REL_FAILURE); .explanation(algorithm + " encryption without a " + PASSWORD.getDisplayName() + " requires both "
return; + PUBLIC_KEYRING.getDisplayName() + " and " + PUBLIC_KEY_USERID.getDisplayName())
} .build());
} else {
flowFile = session.write(flowFile, new DecryptCallback(cipher, secretKey, saltSize)); // verify the public keyring contains the user id
logger.info("successfully decrypted {}", new Object[]{flowFile}); try {
} if (OpenPGPKeyBasedEncryptor.getPublicKey(publicUserId, publicKeyring) == null) {
validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
session.getProvenanceReporter().modifyContent(flowFile, stopWatch.getElapsed(TimeUnit.MILLISECONDS)); .explanation(PUBLIC_KEYRING.getDisplayName() + " " + publicKeyring
session.transfer(flowFile, REL_SUCCESS); + " does not contain user id " + publicUserId)
} .build());
}
private static class DecryptCallback implements StreamCallback { } catch (Exception e) {
validationResults.add(new ValidationResult.Builder().subject(PUBLIC_KEYRING.getDisplayName())
private final Cipher cipher; .explanation("Invalid " + PUBLIC_KEYRING.getDisplayName() + " " + publicKeyring
private final SecretKey secretKey; + " because " + e.toString())
private final int saltSize; .build());
}
public DecryptCallback(final Cipher cipher, final SecretKey secretKey, final int saltSize) { }
this.cipher = cipher; } else {
this.secretKey = secretKey; // need both private-keyring-file and private-keyring-passphrase set
this.saltSize = saltSize; String privateKeyring = context.getProperty(PRIVATE_KEYRING).getValue();
} String keyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).getValue();
if (privateKeyring == null || keyringPassphrase == null) {
@Override validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getName())
public void process(final InputStream in, final OutputStream out) throws IOException { .explanation(algorithm + " decryption without a " + PASSWORD.getDisplayName() + " requires both "
final byte[] salt = new byte[saltSize]; + PRIVATE_KEYRING.getDisplayName() + " and " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
StreamUtils.fillBuffer(in, salt); .build());
} else {
final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 1000); final String providerName = EncryptionMethod.valueOf(method).getProvider();
try { // verify the passphrase works on the private keyring
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); try {
} catch (final Exception e) { if (!OpenPGPKeyBasedEncryptor.validateKeyring(providerName, privateKeyring, keyringPassphrase.toCharArray())) {
throw new ProcessException(e); validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
} .explanation(PRIVATE_KEYRING.getDisplayName() + " " + privateKeyring
+ " could not be opened with the provided " + PRIVATE_KEYRING_PASSPHRASE.getDisplayName())
final byte[] buffer = new byte[65536]; .build());
int len; }
while ((len = in.read(buffer)) > 0) { } catch (Exception e) {
final byte[] decryptedBytes = cipher.update(buffer, 0, len); validationResults.add(new ValidationResult.Builder().subject(PRIVATE_KEYRING.getDisplayName())
if (decryptedBytes != null) { .explanation("Invalid " + PRIVATE_KEYRING.getDisplayName() + " " + privateKeyring
out.write(decryptedBytes); + " because " + e.toString())
} .build());
} }
}
try { }
out.write(cipher.doFinal()); }
} catch (final Exception e) { } else {
throw new ProcessException(e); if (password == null) {
} validationResults.add(new ValidationResult.Builder().subject(PASSWORD.getName())
} .explanation(PASSWORD.getDisplayName() + " is required when using algorithm " + algorithm).build());
} }
}
private static class EncryptCallback implements StreamCallback { return validationResults;
}
private final Cipher cipher;
private final byte[] salt; @Override
public void onTrigger(final ProcessContext context, final ProcessSession session) {
public EncryptCallback(final Cipher cipher, final byte[] salt) { FlowFile flowFile = session.get();
this.cipher = cipher; if (flowFile == null) {
this.salt = salt; return;
} }
@Override final ProcessorLog logger = getLogger();
public void process(final InputStream in, final OutputStream out) throws IOException { final String method = context.getProperty(ENCRYPTION_ALGORITHM).getValue();
out.write(salt); final EncryptionMethod encryptionMethod = EncryptionMethod.valueOf(method);
final String providerName = encryptionMethod.getProvider();
final byte[] buffer = new byte[65536]; final String algorithm = encryptionMethod.getAlgorithm();
int len; final String password = context.getProperty(PASSWORD).getValue();
while ((len = in.read(buffer)) > 0) { boolean encrypt = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCRYPT_MODE);
final byte[] encryptedBytes = cipher.update(buffer, 0, len);
if (encryptedBytes != null) { Encryptor encryptor;
out.write(encryptedBytes); StreamCallback callback;
} try {
} if (isPGPAlgorithm(algorithm)) {
String filename = flowFile.getAttribute(CoreAttributes.FILENAME.key());
try { String publicKeyring = context.getProperty(PUBLIC_KEYRING).getValue();
out.write(cipher.doFinal()); String privateKeyring = context.getProperty(PRIVATE_KEYRING).getValue();
} catch (final IllegalBlockSizeException | BadPaddingException e) { if (encrypt && publicKeyring != null) {
throw new ProcessException(e); String publicUserId = context.getProperty(PUBLIC_KEY_USERID).getValue();
} encryptor = new OpenPGPKeyBasedEncryptor(algorithm, providerName, publicKeyring, publicUserId, null, filename);
} } else if (!encrypt && privateKeyring != null) {
} char[] keyringPassphrase = context.getProperty(PRIVATE_KEYRING_PASSPHRASE).getValue().toCharArray();
} encryptor = new OpenPGPKeyBasedEncryptor(algorithm, providerName, privateKeyring, null, keyringPassphrase,
filename);
} else {
char[] passphrase = Normalizer.normalize(password, Normalizer.Form.NFC).toCharArray();
encryptor = new OpenPGPPasswordBasedEncryptor(algorithm, providerName, passphrase, filename);
}
} else {
char[] passphrase = Normalizer.normalize(password, Normalizer.Form.NFC).toCharArray();
encryptor = new PasswordBasedEncryptor(algorithm, providerName, passphrase);
}
if (encrypt) {
callback = encryptor.getEncryptionCallback();
} else {
callback = encryptor.getDecryptionCallback();
}
} catch (Exception e) {
logger.error("Failed to initialize {}cryption algorithm because - ", new Object[] { encrypt ? "en" : "de", e });
session.rollback();
context.yield();
return;
}
try {
final StopWatch stopWatch = new StopWatch(true);
flowFile = session.write(flowFile, callback);
logger.info("successfully {}crypted {}", new Object[] { encrypt ? "en" : "de", flowFile });
session.getProvenanceReporter().modifyContent(flowFile, stopWatch.getElapsed(TimeUnit.MILLISECONDS));
session.transfer(flowFile, REL_SUCCESS);
} catch (ProcessException e) {
logger.error("Cannot {}crypt {} - ", new Object[] { encrypt ? "en" : "de", flowFile, e });
session.transfer(flowFile, REL_FAILURE);
return;
}
}
public static interface Encryptor {
public StreamCallback getEncryptionCallback() throws Exception;
public StreamCallback getDecryptionCallback() throws Exception;
}
}

View File

@ -0,0 +1,277 @@
/*
* 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.processors.standard.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Iterator;
import java.util.zip.Deflater;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processors.standard.EncryptContent;
import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
public class OpenPGPKeyBasedEncryptor implements Encryptor {
private String algorithm;
private String provider;
private String keyring;
private String userId;
private char[] passphrase;
private String filename;
public static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
public OpenPGPKeyBasedEncryptor(final String algorithm, final String provider, final String keyring, final String userId,
final char[] passphrase, final String filename) {
this.algorithm = algorithm;
this.provider = provider;
this.keyring = keyring;
this.userId = userId;
this.passphrase = passphrase;
this.filename = filename;
}
@Override
public StreamCallback getEncryptionCallback() throws Exception {
return new OpenPGPEncryptCallback(algorithm, provider, keyring, userId, filename);
}
@Override
public StreamCallback getDecryptionCallback() throws Exception {
return new OpenPGPDecryptCallback(provider, keyring, passphrase);
}
/*
* Validate secret keyring passphrase
*/
public static boolean validateKeyring(String provider, String secretKeyringFile, char[] passphrase) throws IOException,
PGPException, NoSuchProviderException {
PGPSecretKeyRingCollection pgpsec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(Files.newInputStream(Paths
.get(secretKeyringFile))));
Iterator ringit = pgpsec.getKeyRings();
while (ringit.hasNext()) {
PGPSecretKeyRing secretkeyring = (PGPSecretKeyRing) ringit.next();
PGPSecretKey secretkey = secretkeyring.getSecretKey();
secretkey.extractPrivateKey(passphrase, provider);
return true;
}
return false;
}
/*
* Get the public key for a specific user id from a keyring.
*/
@SuppressWarnings("rawtypes")
public static PGPPublicKey getPublicKey(String userId, String publicKeyring) throws IOException, PGPException {
PGPPublicKey pubkey = null;
PGPPublicKeyRingCollection pgppub = new
PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(Files.newInputStream(Paths.get(publicKeyring))));
Iterator ringit = pgppub.getKeyRings();
while (ringit.hasNext()) {
PGPPublicKeyRing kring = (PGPPublicKeyRing) ringit.next();
Iterator keyit = kring.getPublicKeys();
while (keyit.hasNext()) {
pubkey = (PGPPublicKey) keyit.next();
boolean userIdMatch = false;
Iterator userit = pubkey.getUserIDs();
while (userit.hasNext()) {
String id = userit.next().toString();
if (id.contains(userId)) {
userIdMatch = true;
break;
}
}
if (pubkey.isEncryptionKey() && userIdMatch) {
return pubkey;
}
}
}
return null;
}
private class OpenPGPDecryptCallback implements StreamCallback {
private String provider;
private String secretKeyring;
private char[] passphrase;
OpenPGPDecryptCallback(final String provider, final String keyring, final char[] passphrase) {
this.provider = provider;
this.secretKeyring = keyring;
this.passphrase = passphrase;
}
@Override
public void process(InputStream in, OutputStream out) throws IOException {
InputStream pgpin = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpFactory = new PGPObjectFactory(pgpin);
Object obj = pgpFactory.nextObject();
if (!(obj instanceof PGPEncryptedDataList)) {
obj = pgpFactory.nextObject();
if (!(obj instanceof PGPEncryptedDataList)) {
throw new ProcessException("Invalid OpenPGP data");
}
}
PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
PGPSecretKeyRingCollection pgpSecretKeyring;
try {
// open secret keyring file
pgpSecretKeyring = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(Files
.newInputStream(Paths.get(secretKeyring))));
} catch (Exception e) {
throw new ProcessException("Invalid secret keyring - " + e.getMessage());
}
try {
PGPPrivateKey privateKey = null;
PGPPublicKeyEncryptedData encData = null;
// find the secret key in the encrypted data
Iterator it = encList.getEncryptedDataObjects();
while (privateKey == null && it.hasNext()) {
obj = it.next();
if (!(obj instanceof PGPPublicKeyEncryptedData)) {
throw new ProcessException("Invalid OpenPGP data");
}
encData = (PGPPublicKeyEncryptedData) obj;
PGPSecretKey secretkey = pgpSecretKeyring.getSecretKey(encData.getKeyID());
if (secretkey != null) {
privateKey = secretkey.extractPrivateKey(passphrase, provider);
}
}
if (privateKey == null) {
throw new ProcessException("Secret keyring does not contain the key required to decrypt");
}
InputStream clearData = encData.getDataStream(privateKey, provider);
PGPObjectFactory clearFactory = new PGPObjectFactory(clearData);
obj = clearFactory.nextObject();
if (obj instanceof PGPCompressedData) {
PGPCompressedData compData = (PGPCompressedData) obj;
clearFactory = new PGPObjectFactory(compData.getDataStream());
obj = clearFactory.nextObject();
}
PGPLiteralData literal = (PGPLiteralData) obj;
InputStream lis = literal.getInputStream();
final byte[] buffer = new byte[4096];
int len;
while ((len = lis.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
} catch (Exception e) {
throw new ProcessException(e.getMessage());
}
}
}
private class OpenPGPEncryptCallback implements StreamCallback {
private String algorithm;
private String provider;
private String publicKeyring;
private String userId;
private String filename;
OpenPGPEncryptCallback(final String algorithm, final String provider, final String keyring, final String userId,
final String filename) {
this.algorithm = algorithm;
this.provider = provider;
this.publicKeyring = keyring;
this.userId = userId;
this.filename = filename;
}
@Override
public void process(InputStream in, OutputStream out) throws IOException {
PGPPublicKey publicKey;
try {
publicKey = getPublicKey(userId, publicKeyring);
} catch (Exception e) {
throw new ProcessException("Invalid public keyring - " + e.getMessage());
}
try {
SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
OutputStream output = out;
if (EncryptContent.isPGPArmoredAlgorithm(algorithm)) {
output = new ArmoredOutputStream(out);
}
PGPEncryptedDataGenerator encGenerator = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, false,
secureRandom, provider);
encGenerator.addMethod(publicKey);
OutputStream encOut = encGenerator.open(output, new byte[65536]);
PGPCompressedDataGenerator compData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP, Deflater.BEST_SPEED);
OutputStream compOut = compData.open(encOut, new byte[65536]);
PGPLiteralDataGenerator literal = new PGPLiteralDataGenerator();
OutputStream literalOut = literal.open(compOut, PGPLiteralData.BINARY, filename, new Date(), new byte[65536]);
final byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) >= 0) {
literalOut.write(buffer, 0, len);
}
literalOut.close();
compOut.close();
encOut.close();
output.close();
} catch (Exception e) {
throw new ProcessException(e.getMessage());
}
}
}
}

View File

@ -0,0 +1,175 @@
/*
* 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.processors.standard.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Date;
import java.util.zip.Deflater;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processors.standard.EncryptContent;
import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
import org.bouncycastle.openpgp.PGPUtil;
public class OpenPGPPasswordBasedEncryptor implements Encryptor {
private String algorithm;
private String provider;
private char[] password;
private String filename;
public static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
public OpenPGPPasswordBasedEncryptor(final String algorithm, final String provider, final char[] passphrase, final String filename) {
this.algorithm = algorithm;
this.provider = provider;
this.password = passphrase;
this.filename = filename;
}
@Override
public StreamCallback getEncryptionCallback() throws Exception {
return new OpenPGPEncryptCallback(algorithm, provider, password, filename);
}
@Override
public StreamCallback getDecryptionCallback() throws Exception {
return new OpenPGPDecryptCallback(provider, password);
}
private class OpenPGPDecryptCallback implements StreamCallback {
private String provider;
private char[] password;
OpenPGPDecryptCallback(final String provider, final char[] password) {
this.provider = provider;
this.password = password;
}
@Override
public void process(InputStream in, OutputStream out) throws IOException {
InputStream pgpin = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpFactory = new PGPObjectFactory(pgpin);
Object obj = pgpFactory.nextObject();
if (!(obj instanceof PGPEncryptedDataList)) {
obj = pgpFactory.nextObject();
if (!(obj instanceof PGPEncryptedDataList)) {
throw new ProcessException("Invalid OpenPGP data");
}
}
PGPEncryptedDataList encList = (PGPEncryptedDataList) obj;
obj = encList.get(0);
if (!(obj instanceof PGPPBEEncryptedData)) {
throw new ProcessException("Invalid OpenPGP data");
}
PGPPBEEncryptedData encData = (PGPPBEEncryptedData) obj;
try {
InputStream clearData = encData.getDataStream(password, provider);
PGPObjectFactory clearFactory = new PGPObjectFactory(clearData);
obj = clearFactory.nextObject();
if (obj instanceof PGPCompressedData) {
PGPCompressedData compData = (PGPCompressedData) obj;
clearFactory = new PGPObjectFactory(compData.getDataStream());
obj = clearFactory.nextObject();
}
PGPLiteralData literal = (PGPLiteralData) obj;
InputStream lis = literal.getInputStream();
final byte[] buffer = new byte[4096];
int len;
while ((len = lis.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
} catch (Exception e) {
throw new ProcessException(e.getMessage());
}
}
}
private class OpenPGPEncryptCallback implements StreamCallback {
private String algorithm;
private String provider;
private char[] password;
private String filename;
OpenPGPEncryptCallback(final String algorithm, final String provider, final char[] password, final String filename) {
this.algorithm = algorithm;
this.provider = provider;
this.password = password;
this.filename = filename;
}
@Override
public void process(InputStream in, OutputStream out) throws IOException {
try {
SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
OutputStream output = out;
if (EncryptContent.isPGPArmoredAlgorithm(algorithm)) {
output = new ArmoredOutputStream(out);
}
PGPEncryptedDataGenerator encGenerator = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, false,
secureRandom, provider);
encGenerator.addMethod(password);
OutputStream encOut = encGenerator.open(output, new byte[65536]);
PGPCompressedDataGenerator compData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP, Deflater.BEST_SPEED);
OutputStream compOut = compData.open(encOut, new byte[65536]);
PGPLiteralDataGenerator literal = new PGPLiteralDataGenerator();
OutputStream literalOut = literal.open(compOut, PGPLiteralData.BINARY, filename, new Date(), new byte[65536]);
final byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) >= 0) {
literalOut.write(buffer, 0, len);
}
literalOut.close();
compOut.close();
encOut.close();
output.close();
} catch (Exception e) {
throw new ProcessException(e.getMessage());
}
}
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.processors.standard.util;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.StreamCallback;
import org.apache.nifi.processors.standard.EncryptContent.Encryptor;
import org.apache.nifi.stream.io.StreamUtils;
public class PasswordBasedEncryptor implements Encryptor {
private Cipher cipher;
private int saltSize;
private SecretKey secretKey;
public static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
public static final int DEFAULT_SALT_SIZE = 8;
public PasswordBasedEncryptor(final String algorithm, final String providerName, final char[] password) {
super();
try {
// initialize cipher
this.cipher = Cipher.getInstance(algorithm, providerName);
int algorithmBlockSize = cipher.getBlockSize();
this.saltSize = (algorithmBlockSize > 0) ? algorithmBlockSize : DEFAULT_SALT_SIZE;
// initialize SecretKey from password
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm, providerName);
this.secretKey = factory.generateSecret(pbeKeySpec);
} catch (Exception e) {
throw new ProcessException(e);
}
}
@Override
public StreamCallback getEncryptionCallback() throws ProcessException {
try {
byte[] salt = new byte[saltSize];
SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
secureRandom.setSeed(System.currentTimeMillis());
secureRandom.nextBytes(salt);
return new EncryptCallback(salt);
} catch (Exception e) {
throw new ProcessException(e);
}
}
@Override
public StreamCallback getDecryptionCallback() throws ProcessException {
return new DecryptCallback();
}
private class DecryptCallback implements StreamCallback {
public DecryptCallback() {
}
@Override
public void process(final InputStream in, final OutputStream out) throws IOException {
final byte[] salt = new byte[saltSize];
try {
StreamUtils.fillBuffer(in, salt);
} catch (final EOFException e) {
throw new ProcessException("Cannot decrypt because file size is smaller than salt size", e);
}
final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 1000);
try {
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
} catch (final Exception e) {
throw new ProcessException(e);
}
final byte[] buffer = new byte[65536];
int len;
while ((len = in.read(buffer)) > 0) {
final byte[] decryptedBytes = cipher.update(buffer, 0, len);
if (decryptedBytes != null) {
out.write(decryptedBytes);
}
}
try {
out.write(cipher.doFinal());
} catch (final Exception e) {
throw new ProcessException(e);
}
}
}
private class EncryptCallback implements StreamCallback {
private final byte[] salt;
public EncryptCallback(final byte[] salt) {
this.salt = salt;
}
@Override
public void process(final InputStream in, final OutputStream out) throws IOException {
final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 1000);
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
} catch (final Exception e) {
throw new ProcessException(e);
}
out.write(salt);
final byte[] buffer = new byte[65536];
int len;
while ((len = in.read(buffer)) > 0) {
final byte[] encryptedBytes = cipher.update(buffer, 0, len);
if (encryptedBytes != null) {
out.write(encryptedBytes);
}
}
try {
out.write(cipher.doFinal());
} catch (final IllegalBlockSizeException | BadPaddingException e) {
throw new ProcessException(e);
}
}
}
}

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<!--
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.
-->
<head>
<meta charset="utf-8"/>
<title>EvaluateJsonPath</title>
<link rel="stylesheet" href="../../css/component-usage.css" type="text/css"/>
</head>
<body>
<!-- Processor Documentation ================================================== -->
<p>
<strong>Note:</strong> This processor supports OpenPGP algorithms that are compatible with third party programs.
</p>
</body>
</html>

View File

@ -1,65 +1,163 @@
/* /*
* Licensed to the Apache Software Foundation (ASF) under one or more * Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with * contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. * this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0 * 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 not use this file except in compliance with
* the License. You may obtain a copy of the License at * the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.processors.standard; package org.apache.nifi.processors.standard;
import org.apache.nifi.processors.standard.EncryptContent; import java.io.File;
import java.io.File; import java.io.IOException;
import java.io.IOException; import java.nio.file.Paths;
import java.nio.file.Paths; import java.util.Collection;
import java.util.HashSet;
import org.apache.nifi.security.util.EncryptionMethod;
import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.util.TestRunner; import org.apache.nifi.security.util.EncryptionMethod;
import org.apache.nifi.util.TestRunners; import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.MockProcessContext;
import org.junit.Test; import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
public class TestEncryptContent { import org.junit.Assert;
import org.junit.Test;
@Test
public void testRoundTrip() throws IOException { public class TestEncryptContent {
final TestRunner testRunner = TestRunners.newTestRunner(new EncryptContent());
testRunner.setProperty(EncryptContent.PASSWORD, "Hello, World!"); @Test
public void testRoundTrip() throws IOException {
for (final EncryptionMethod method : EncryptionMethod.values()) { final TestRunner testRunner = TestRunners.newTestRunner(new EncryptContent());
if (method.isUnlimitedStrength()) { testRunner.setProperty(EncryptContent.PASSWORD, "Hello, World!");
continue; // cannot test unlimited strength in unit tests because it's not enabled by the JVM by default.
} for (final EncryptionMethod method : EncryptionMethod.values()) {
testRunner.setProperty(EncryptContent.ENCRYPTION_ALGORITHM, method.name()); if (method.isUnlimitedStrength()) {
testRunner.setProperty(EncryptContent.MODE, EncryptContent.ENCRYPT_MODE); continue; // cannot test unlimited strength in unit tests because it's not enabled by the JVM by default.
}
testRunner.enqueue(Paths.get("src/test/resources/hello.txt")); testRunner.setProperty(EncryptContent.ENCRYPTION_ALGORITHM, method.name());
testRunner.clearTransferState(); testRunner.setProperty(EncryptContent.MODE, EncryptContent.ENCRYPT_MODE);
testRunner.run();
testRunner.enqueue(Paths.get("src/test/resources/hello.txt"));
testRunner.assertAllFlowFilesTransferred(EncryptContent.REL_SUCCESS, 1); testRunner.clearTransferState();
testRunner.run();
MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(EncryptContent.REL_SUCCESS).get(0);
testRunner.assertQueueEmpty(); testRunner.assertAllFlowFilesTransferred(EncryptContent.REL_SUCCESS, 1);
testRunner.setProperty(EncryptContent.MODE, EncryptContent.DECRYPT_MODE); MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(EncryptContent.REL_SUCCESS).get(0);
testRunner.enqueue(flowFile); testRunner.assertQueueEmpty();
testRunner.clearTransferState();
testRunner.run(); testRunner.setProperty(EncryptContent.MODE, EncryptContent.DECRYPT_MODE);
testRunner.assertAllFlowFilesTransferred(EncryptContent.REL_SUCCESS, 1); testRunner.enqueue(flowFile);
testRunner.clearTransferState();
flowFile = testRunner.getFlowFilesForRelationship(EncryptContent.REL_SUCCESS).get(0); testRunner.run();
flowFile.assertContentEquals(new File("src/test/resources/hello.txt")); testRunner.assertAllFlowFilesTransferred(EncryptContent.REL_SUCCESS, 1);
}
} flowFile = testRunner.getFlowFilesForRelationship(EncryptContent.REL_SUCCESS).get(0);
flowFile.assertContentEquals(new File("src/test/resources/hello.txt"));
} }
}
@Test
public void testDecryptSmallerThanSaltSize() {
TestRunner runner = TestRunners.newTestRunner(EncryptContent.class);
runner.setProperty(EncryptContent.PASSWORD, "Hello, World!");
runner.setProperty(EncryptContent.MODE, EncryptContent.DECRYPT_MODE);
runner.enqueue(new byte[4]);
runner.run();
runner.assertAllFlowFilesTransferred(EncryptContent.REL_FAILURE, 1);
}
@Test
public void testPGPDecrypt() throws IOException {
final TestRunner runner = TestRunners.newTestRunner(EncryptContent.class);
runner.setProperty(EncryptContent.MODE, EncryptContent.DECRYPT_MODE);
runner.setProperty(EncryptContent.ENCRYPTION_ALGORITHM, EncryptionMethod.PGP_ASCII_ARMOR.name());
runner.setProperty(EncryptContent.PASSWORD, "Hello, World!");
runner.enqueue(Paths.get("src/test/resources/TestEncryptContent/text.txt.asc"));
runner.run();
runner.assertAllFlowFilesTransferred(EncryptContent.REL_SUCCESS, 1);
MockFlowFile flowFile = runner.getFlowFilesForRelationship(EncryptContent.REL_SUCCESS).get(0);
flowFile.assertContentEquals(Paths.get("src/test/resources/TestEncryptContent/text.txt"));
}
@Test
public void testValidation() {
final TestRunner runner = TestRunners.newTestRunner(EncryptContent.class);
Collection<ValidationResult> results;
MockProcessContext pc;
results = new HashSet<>();
runner.enqueue(new byte[0]);
pc = (MockProcessContext) runner.getProcessContext();
results = pc.validate();
Assert.assertEquals(1, results.size());
for (ValidationResult vr : results) {
Assert.assertTrue(vr.toString()
.contains(EncryptContent.PASSWORD.getDisplayName() + " is required when using algorithm"));
}
results = new HashSet<>();
runner.setProperty(EncryptContent.ENCRYPTION_ALGORITHM, EncryptionMethod.PGP.name());
runner.setProperty(EncryptContent.PUBLIC_KEYRING, "src/test/resources/TestEncryptContent/text.txt");
runner.enqueue(new byte[0]);
pc = (MockProcessContext) runner.getProcessContext();
results = pc.validate();
Assert.assertEquals(1, results.size());
for (ValidationResult vr : results) {
Assert.assertTrue(vr.toString().contains(
" encryption without a " + EncryptContent.PASSWORD.getDisplayName() + " requires both "
+ EncryptContent.PUBLIC_KEYRING.getDisplayName() + " and "
+ EncryptContent.PUBLIC_KEY_USERID.getDisplayName()));
}
results = new HashSet<>();
runner.setProperty(EncryptContent.PUBLIC_KEY_USERID, "USERID");
runner.enqueue(new byte[0]);
pc = (MockProcessContext) runner.getProcessContext();
results = pc.validate();
Assert.assertEquals(1, results.size());
for (ValidationResult vr : results) {
Assert.assertTrue(vr.toString().contains("does not contain user id USERID"));
}
runner.removeProperty(EncryptContent.PUBLIC_KEYRING);
runner.removeProperty(EncryptContent.PUBLIC_KEY_USERID);
results = new HashSet<>();
runner.setProperty(EncryptContent.MODE, EncryptContent.DECRYPT_MODE);
runner.setProperty(EncryptContent.PRIVATE_KEYRING, "src/test/resources/TestEncryptContent/text.txt");
runner.enqueue(new byte[0]);
pc = (MockProcessContext) runner.getProcessContext();
results = pc.validate();
Assert.assertEquals(1, results.size());
for (ValidationResult vr : results) {
Assert.assertTrue(vr.toString().contains(
" decryption without a " + EncryptContent.PASSWORD.getDisplayName() + " requires both "
+ EncryptContent.PRIVATE_KEYRING.getDisplayName() + " and "
+ EncryptContent.PRIVATE_KEYRING_PASSPHRASE.getDisplayName()));
}
results = new HashSet<>();
runner.setProperty(EncryptContent.PRIVATE_KEYRING_PASSPHRASE, "PASSWORD");
runner.enqueue(new byte[0]);
pc = (MockProcessContext) runner.getProcessContext();
results = pc.validate();
Assert.assertEquals(1, results.size());
for (ValidationResult vr : results) {
Assert.assertTrue(vr.toString().contains(
" could not be opened with the provided " + EncryptContent.PRIVATE_KEYRING_PASSPHRASE.getDisplayName()));
}
}
}

View File

@ -0,0 +1,17 @@
/*
* 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.
*/
This is some clear text.

View File

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-----BEGIN PGP MESSAGE-----
Version: GnuPG v2
jA0EAwMC54x6GeEEmLy3ycE/pY5T6vawFMS9/Io8chEQGD8UNAbZFzqXcVEvvy5f
wJQggyuF6Lo1RZGnhxdGoz0do1DZzR0lVaTL8dR0/jtz/kRZ1omz+OxICuo9BaRX
M+fT2mdna5lhDGJmE7nCctDaGwXqjglEXPqOdi8j/tL225HViTKP1VmlKuu8AjiH
lMGuC65bqILSWCaE2jewCbsmjHPgLGmH9NN6EAo2kiFEMOrA+UYo35PuShkgQsZp
gA0m2A31JjLW28SUsNd1vLk5bWBZaIFA1UvhR7u0pagv3qtu5f8qls19nHnAT/bz
Kh2KrnIm0peWWVEPGQkFoK3Lt9vJTjmHdHPUXQHyg+SMN1PIGA2sxwiSrkQlAyon
uNg/I24ctydWU+qndz+ycDWR6zBziA09KHw7uKo5CDtTm+Zo3K9U9uf9y8iZ+AKd
vgF/4Nw2lJTqQfNtkmK+N+cKEyZNmJa4r+uDzJF/dCv8R5jGj2dBYRTLxj5tgllU
4GiwuJR6w09hK0S0oe9XTdcNciWigb12H9z6U6JOse+1S/fYoUa0CZRJg9Bgeqym
uuT/mNSKwRVcWN27vOGy+zGf0tqw6A5idLrK+8FZzd1sgxtKsgkYz8FcZgo9rq1f
PHR9KF4JhaGpqNJmEu/GucYIAgq3aeo9GoV/RpDZtoHAVBuqPwcDTzGHeiAoZ49U
6Q==
=H5nf
-----END PGP MESSAGE-----

View File

@ -191,6 +191,11 @@
<artifactId>bcprov-jdk16</artifactId> <artifactId>bcprov-jdk16</artifactId>
<version>1.46</version> <version>1.46</version>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency> <dependency>
<groupId>com.jcraft</groupId> <groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId> <artifactId>jsch</artifactId>