Java tutorial
/* * 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.aws.s3.encryption.service; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.AmazonS3EncryptionClient; import com.amazonaws.services.s3.model.CryptoConfiguration; import com.amazonaws.services.s3.model.CryptoMode; import com.amazonaws.services.s3.model.CryptoStorageMode; import com.amazonaws.services.s3.model.EncryptionMaterials; import com.amazonaws.services.s3.model.KMSEncryptionMaterials; import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnEnabled; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.reporting.InitializationException; import org.apache.nifi.util.StringUtils; import javax.crypto.spec.SecretKeySpec; import java.security.KeyFactory; import java.security.KeyPair; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.List; @Tags({ "aws", "s3", "encryption", "client", "kms", "key" }) @CapabilityDescription("Provides the ability to configure S3 Client Side Encryption once and reuse " + "that configuration throughout the application") public class StandardS3ClientSideEncryptionService extends AbstractControllerService implements S3ClientSideEncryptionService { public static final String METHOD_CSE_MK = "Client Side Master Key"; public static final String METHOD_CSE_KMS = "KMS-Managed Customer Master Key"; public static final String ALGORITHM_AES128 = "AES_128"; public static final String ALGORITHM_AES256 = "AES_256"; public static final String ALGORITHM_DES = "DES"; public static final String ALGORITHM_RSA = "RSA"; public static final PropertyDescriptor ENCRYPTION_METHOD = new PropertyDescriptor.Builder() .name("encryption-method").displayName("Encryption Method").required(true) .allowableValues(METHOD_CSE_MK, METHOD_CSE_KMS).defaultValue(METHOD_CSE_MK) .description("Method by which the S3 object will be encrypted client-side.").build(); public static final PropertyDescriptor KMS_CMK_ID = new PropertyDescriptor.Builder().name("kms-cmk-id") .displayName("KMS Customer Master Key Id").required(false).expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .description("Identifier belonging to the custom master key managed by the KMS.").build(); public static final PropertyDescriptor KMS_REGION = new PropertyDescriptor.Builder().name("kms-region") .displayName("KMS Region").required(false).allowableValues(getAvailableRegions()) .description("AWS region that contains the KMS.").build(); public static final PropertyDescriptor SECRET_KEY = new PropertyDescriptor.Builder().name("secret-key") .displayName("Secret Key").required(false).expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .description("Secret key used when performing symmetric client side encryption.").build(); public static final PropertyDescriptor SECRET_KEY_ALGORITHM = new PropertyDescriptor.Builder() .name("secret-key-algorithm").displayName("Secret Key Algorithm").required(false) .description("Secret key algorithm used when performing symmetric client side encryption.").build(); public static final PropertyDescriptor PRIVATE_KEY = new PropertyDescriptor.Builder().name("private-key") .displayName("Private Key").required(false).expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .description("Private key used when performing asymmetric client side encryption.").build(); public static final PropertyDescriptor PRIVATE_KEY_ALGORITHM = new PropertyDescriptor.Builder() .name("private-key-algorithm").displayName("Private Key Algorithm").required(false) .description("Private key algorithm used when performing asymmetric client side encryption.").build(); public static final PropertyDescriptor PUBLIC_KEY = new PropertyDescriptor.Builder().name("public-key") .displayName("Public Key").required(false).expressionLanguageSupported(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .description("Public key used when performing asymmetric client side encryption.").build(); public static final PropertyDescriptor PUBLIC_KEY_ALGORITHM = new PropertyDescriptor.Builder() .name("public-key-algorithm").displayName("Public Key Algorithm").required(false) .description("Public key algorithm used when performing asymmetric client side encryption.").build(); public static final PropertyDescriptor CRYPTO_MODE = new PropertyDescriptor.Builder().name("crypto-mode") .displayName("Crypto Mode").required(false) .allowableValues( new AllowableValue(CryptoMode.StrictAuthenticatedEncryption.toString(), "Strict Authenticated Encryption"), new AllowableValue(CryptoMode.AuthenticatedEncryption.toString(), "Authenticated Encryption"), new AllowableValue(CryptoMode.EncryptionOnly.toString(), "Encryption Only")) .description("Cryptographic mode used to secure an S3 object.").build(); public static final PropertyDescriptor CRYPTO_STORAGE_MODE = new PropertyDescriptor.Builder() .name("storage-mode").displayName("Crypto Storage Mode").required(false) .allowableValues(new AllowableValue(CryptoStorageMode.InstructionFile.toString(), "Instruction File"), new AllowableValue(CryptoStorageMode.ObjectMetadata.toString(), "Object Metadata")) .description("Storage mode used to store encryption information when encrypting an S3 object.").build(); public static final PropertyDescriptor IGNORE_MISSING_INSTRUCTION_FILE = new PropertyDescriptor.Builder() .name("ignore-missing-instruction-file").displayName("Ignore Missing Instruction File").required(false) .addValidator(StandardValidators.BOOLEAN_VALIDATOR).allowableValues("true", "false") .defaultValue("false").description("Whether to ignore missing instruction files during decryption.") .build(); private static final List<PropertyDescriptor> properties; private String encryptionMethod; private String kmsCmkId; private String kmsRegion; private String secretKey; private String secretKeyAlgorithm; private String privateKey; private String privateKeyAlgorithm; private String publicKey; private String publicKeyAlgorithm; private String cryptoMode; private String cryptoStorageMode; private Boolean ignoreMissingInstructionFile; static { final List<PropertyDescriptor> props = new ArrayList<>(); props.add(ENCRYPTION_METHOD); props.add(KMS_CMK_ID); props.add(KMS_REGION); props.add(SECRET_KEY); props.add(SECRET_KEY_ALGORITHM); props.add(PRIVATE_KEY); props.add(PRIVATE_KEY_ALGORITHM); props.add(PUBLIC_KEY); props.add(PUBLIC_KEY_ALGORITHM); props.add(CRYPTO_MODE); props.add(CRYPTO_STORAGE_MODE); props.add(IGNORE_MISSING_INSTRUCTION_FILE); properties = Collections.unmodifiableList(props); } @Override protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { return properties; } @OnEnabled public void onConfigured(final ConfigurationContext context) throws InitializationException { encryptionMethod = context.getProperty(ENCRYPTION_METHOD).getValue(); kmsCmkId = context.getProperty(KMS_CMK_ID).getValue(); kmsRegion = context.getProperty(KMS_REGION).getValue(); secretKey = context.getProperty(SECRET_KEY).getValue(); secretKeyAlgorithm = context.getProperty(SECRET_KEY_ALGORITHM).getValue(); privateKey = context.getProperty(PRIVATE_KEY).getValue(); privateKeyAlgorithm = context.getProperty(PRIVATE_KEY_ALGORITHM).getValue(); publicKey = context.getProperty(PUBLIC_KEY).getValue(); publicKeyAlgorithm = context.getProperty(PUBLIC_KEY_ALGORITHM).getValue(); cryptoMode = context.getProperty(CRYPTO_MODE).getValue(); cryptoStorageMode = context.getProperty(CRYPTO_STORAGE_MODE).getValue(); ignoreMissingInstructionFile = context.getProperty(IGNORE_MISSING_INSTRUCTION_FILE).asBoolean(); } public boolean needsEncryptedClient() { return encryptionMethod != null && (encryptionMethod.equals(METHOD_CSE_KMS) || encryptionMethod.equals(METHOD_CSE_MK)); } public AmazonS3Client encryptedClient(AWSCredentialsProvider credentialsProvider, ClientConfiguration config) { return new AmazonS3EncryptionClient(credentialsProvider, new StaticEncryptionMaterialsProvider(encryptionMaterials()), config, cryptoConfiguration()); } public AmazonS3Client encryptedClient(AWSCredentials credentials, ClientConfiguration config) { return new AmazonS3EncryptionClient(credentials, encryptionMaterials(), config, cryptoConfiguration()); } private CryptoConfiguration cryptoConfiguration() { CryptoConfiguration config = new CryptoConfiguration(); if (!StringUtils.isBlank(cryptoMode)) { config.setCryptoMode(CryptoMode.valueOf(cryptoMode)); } if (!StringUtils.isBlank(cryptoStorageMode)) { config.setStorageMode(CryptoStorageMode.valueOf(cryptoStorageMode)); } if (!StringUtils.isBlank(kmsRegion)) { config.setAwsKmsRegion(Region.getRegion(Regions.fromName(kmsRegion))); } config.setIgnoreMissingInstructionFile(ignoreMissingInstructionFile); return config; } private EncryptionMaterials encryptionMaterials() { if (!StringUtils.isBlank(kmsCmkId)) { return new KMSEncryptionMaterials(kmsCmkId); } if (!StringUtils.isBlank(secretKey)) { return new EncryptionMaterials(new SecretKeySpec(secretKey.getBytes(), secretKeyAlgorithm != null ? publicKeyAlgorithm : "RSA")); } if (!StringUtils.isBlank(publicKey) && !StringUtils.isBlank(privateKey)) { try { KeyFactory publicKeyFactory = KeyFactory .getInstance(publicKeyAlgorithm != null ? publicKeyAlgorithm : "RSA"); KeyFactory privateKeyFactory = KeyFactory .getInstance(privateKeyAlgorithm != null ? privateKeyAlgorithm : "RSA"); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey.getBytes()); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey.getBytes()); KeyPair keyPair = new KeyPair(publicKeyFactory.generatePublic(publicKeySpec), privateKeyFactory.generatePrivate(privateKeySpec)); return new EncryptionMaterials(keyPair); } catch (Exception e) { getLogger().info("Failed to create key pair based encryption materials: reason={}", new Object[] { e.getMessage() }); return null; } } return null; } private static AllowableValue[] getAvailableRegions() { final List<AllowableValue> values = new ArrayList<>(); for (final Regions regions : Regions.values()) { values.add(new AllowableValue(regions.getName(), regions.getName(), regions.getName())); } return values.toArray(new AllowableValue[values.size()]); } @Override public String toString() { return "StandardS3ClientSideEncryptionService[id=" + getIdentifier() + "]"; } }