All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.crypto.key.KeyProvider Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/**
 * 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.hadoop.crypto.key;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;

import javax.crypto.KeyGenerator;

import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_CRYPTO_JCEKS_KEY_SERIALFILTER;

/**
 * A provider of secret key material for Hadoop applications. Provides an
 * abstraction to separate key storage from users of encryption. It
 * is intended to support getting or storing keys in a variety of ways,
 * including third party bindings.
 * 

* KeyProvider implementations must be thread safe. */ @InterfaceAudience.Public @InterfaceStability.Unstable public abstract class KeyProvider implements Closeable { public static final String DEFAULT_CIPHER_NAME = CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_DEFAULT_CIPHER_KEY; public static final String DEFAULT_CIPHER = CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_DEFAULT_CIPHER_DEFAULT; public static final String DEFAULT_BITLENGTH_NAME = CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_DEFAULT_BITLENGTH_KEY; public static final int DEFAULT_BITLENGTH = CommonConfigurationKeysPublic. HADOOP_SECURITY_KEY_DEFAULT_BITLENGTH_DEFAULT; public static final String JCEKS_KEY_SERIALFILTER_DEFAULT = "java.lang.Enum;" + "java.security.KeyRep;" + "java.security.KeyRep$Type;" + "javax.crypto.spec.SecretKeySpec;" + "org.apache.hadoop.crypto.key.JavaKeyStoreProvider$KeyMetadata;" + "!*"; public static final String JCEKS_KEY_SERIAL_FILTER = "jceks.key.serialFilter"; private final Configuration conf; /** * The combination of both the key version name and the key material. */ public static class KeyVersion { private final String name; private final String versionName; private final byte[] material; protected KeyVersion(String name, String versionName, byte[] material) { this.name = name == null ? null : name.intern(); this.versionName = versionName == null ? null : versionName.intern(); this.material = material; } public String getName() { return name; } public String getVersionName() { return versionName; } public byte[] getMaterial() { return material; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("key("); buf.append(versionName); buf.append(")="); if (material == null) { buf.append("null"); } else { for(byte b: material) { buf.append(' '); int right = b & 0xff; if (right < 0x10) { buf.append('0'); } buf.append(Integer.toHexString(right)); } } return buf.toString(); } @Override public boolean equals(Object rhs) { if (this == rhs) { return true; } if (rhs == null || getClass() != rhs.getClass()) { return false; } final KeyVersion kv = (KeyVersion) rhs; return new EqualsBuilder(). append(name, kv.name). append(versionName, kv.versionName). append(material, kv.material). isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(). append(name). append(versionName). append(material). toHashCode(); } } /** * Key metadata that is associated with the key. */ public static class Metadata { private final static String CIPHER_FIELD = "cipher"; private final static String BIT_LENGTH_FIELD = "bitLength"; private final static String CREATED_FIELD = "created"; private final static String DESCRIPTION_FIELD = "description"; private final static String VERSIONS_FIELD = "versions"; private final static String ATTRIBUTES_FIELD = "attributes"; private final String cipher; private final int bitLength; private final String description; private final Date created; private int versions; private Map attributes; protected Metadata(String cipher, int bitLength, String description, Map attributes, Date created, int versions) { this.cipher = cipher; this.bitLength = bitLength; this.description = description; this.attributes = (attributes == null || attributes.isEmpty()) ? null : attributes; this.created = created; this.versions = versions; } public String toString() { final StringBuilder metaSB = new StringBuilder(); metaSB.append("cipher: ").append(cipher).append(", "); metaSB.append("length: ").append(bitLength).append(", "); metaSB.append("description: ").append(description).append(", "); metaSB.append("created: ").append(created).append(", "); metaSB.append("version: ").append(versions).append(", "); metaSB.append("attributes: "); if ((attributes != null) && !attributes.isEmpty()) { for (Map.Entry attribute : attributes.entrySet()) { metaSB.append("["); metaSB.append(attribute.getKey()); metaSB.append("="); metaSB.append(attribute.getValue()); metaSB.append("], "); } metaSB.deleteCharAt(metaSB.length() - 2); // remove last ', ' } else { metaSB.append("null"); } return metaSB.toString(); } public String getDescription() { return description; } public Date getCreated() { return created; } public String getCipher() { return cipher; } public Map getAttributes() { return (attributes == null) ? Collections.emptyMap() : attributes; } /** * Get the algorithm from the cipher. * @return the algorithm name */ public String getAlgorithm() { int slash = cipher.indexOf('/'); if (slash == - 1) { return cipher; } else { return cipher.substring(0, slash); } } public int getBitLength() { return bitLength; } public int getVersions() { return versions; } protected int addVersion() { return versions++; } /** * Serialize the metadata to a set of bytes. * @return the serialized bytes * @throws IOException */ protected byte[] serialize() throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); JsonWriter writer = new JsonWriter( new OutputStreamWriter(buffer, StandardCharsets.UTF_8)); try { writer.beginObject(); if (cipher != null) { writer.name(CIPHER_FIELD).value(cipher); } if (bitLength != 0) { writer.name(BIT_LENGTH_FIELD).value(bitLength); } if (created != null) { writer.name(CREATED_FIELD).value(created.getTime()); } if (description != null) { writer.name(DESCRIPTION_FIELD).value(description); } if (attributes != null && attributes.size() > 0) { writer.name(ATTRIBUTES_FIELD).beginObject(); for (Map.Entry attribute : attributes.entrySet()) { writer.name(attribute.getKey()).value(attribute.getValue()); } writer.endObject(); } writer.name(VERSIONS_FIELD).value(versions); writer.endObject(); writer.flush(); } finally { writer.close(); } return buffer.toByteArray(); } /** * Deserialize a new metadata object from a set of bytes. * @param bytes the serialized metadata * @throws IOException */ protected Metadata(byte[] bytes) throws IOException { String cipher = null; int bitLength = 0; Date created = null; int versions = 0; String description = null; Map attributes = null; JsonReader reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)); try { reader.beginObject(); while (reader.hasNext()) { String field = reader.nextName(); if (CIPHER_FIELD.equals(field)) { cipher = reader.nextString(); } else if (BIT_LENGTH_FIELD.equals(field)) { bitLength = reader.nextInt(); } else if (CREATED_FIELD.equals(field)) { created = new Date(reader.nextLong()); } else if (VERSIONS_FIELD.equals(field)) { versions = reader.nextInt(); } else if (DESCRIPTION_FIELD.equals(field)) { description = reader.nextString(); } else if (ATTRIBUTES_FIELD.equalsIgnoreCase(field)) { reader.beginObject(); attributes = new HashMap(); while (reader.hasNext()) { attributes.put(reader.nextName(), reader.nextString()); } reader.endObject(); } } reader.endObject(); } finally { reader.close(); } this.cipher = cipher; this.bitLength = bitLength; this.created = created; this.description = description; this.attributes = attributes; this.versions = versions; } } /** * Options when creating key objects. */ public static class Options { private String cipher; private int bitLength; private String description; private Map attributes; public Options(Configuration conf) { cipher = conf.get(DEFAULT_CIPHER_NAME, DEFAULT_CIPHER); bitLength = conf.getInt(DEFAULT_BITLENGTH_NAME, DEFAULT_BITLENGTH); } public Options setCipher(String cipher) { this.cipher = cipher; return this; } public Options setBitLength(int bitLength) { this.bitLength = bitLength; return this; } public Options setDescription(String description) { this.description = description; return this; } public Options setAttributes(Map attributes) { if (attributes != null) { if (attributes.containsKey(null)) { throw new IllegalArgumentException("attributes cannot have a NULL key"); } this.attributes = new HashMap(attributes); } return this; } public String getCipher() { return cipher; } public int getBitLength() { return bitLength; } public String getDescription() { return description; } public Map getAttributes() { return (attributes == null) ? Collections.emptyMap() : attributes; } @Override public String toString() { return "Options{" + "cipher='" + cipher + '\'' + ", bitLength=" + bitLength + ", description='" + description + '\'' + ", attributes=" + attributes + '}'; } } /** * Constructor. * * @param conf configuration for the provider */ public KeyProvider(Configuration conf) { this.conf = new Configuration(conf); // Added for HADOOP-15473. Configured serialFilter property fixes // java.security.UnrecoverableKeyException in JDK 8u171. if(System.getProperty(JCEKS_KEY_SERIAL_FILTER) == null) { String serialFilter = conf.get(HADOOP_SECURITY_CRYPTO_JCEKS_KEY_SERIALFILTER, JCEKS_KEY_SERIALFILTER_DEFAULT); System.setProperty(JCEKS_KEY_SERIAL_FILTER, serialFilter); } } /** * Return the provider configuration. * * @return the provider configuration */ public Configuration getConf() { return conf; } /** * A helper function to create an options object. * @param conf the configuration to use * @return a new options object */ public static Options options(Configuration conf) { return new Options(conf); } /** * Indicates whether this provider represents a store * that is intended for transient use - such as the UserProvider * is. These providers are generally used to provide access to * keying material rather than for long term storage. * @return true if transient, false otherwise */ public boolean isTransient() { return false; } /** * Get the key material for a specific version of the key. This method is used * when decrypting data. * @param versionName the name of a specific version of the key * @return the key material * @throws IOException */ public abstract KeyVersion getKeyVersion(String versionName ) throws IOException; /** * Get the key names for all keys. * @return the list of key names * @throws IOException */ public abstract List getKeys() throws IOException; /** * Get key metadata in bulk. * @param names the names of the keys to get * @throws IOException */ public Metadata[] getKeysMetadata(String... names) throws IOException { Metadata[] result = new Metadata[names.length]; for (int i=0; i < names.length; ++i) { result[i] = getMetadata(names[i]); } return result; } /** * Get the key material for all versions of a specific key name. * @return the list of key material * @throws IOException */ public abstract List getKeyVersions(String name) throws IOException; /** * Get the current version of the key, which should be used for encrypting new * data. * @param name the base name of the key * @return the version name of the current version of the key or null if the * key version doesn't exist * @throws IOException */ public KeyVersion getCurrentKey(String name) throws IOException { Metadata meta = getMetadata(name); if (meta == null) { return null; } return getKeyVersion(buildVersionName(name, meta.getVersions() - 1)); } /** * Get metadata about the key. * @param name the basename of the key * @return the key's metadata or null if the key doesn't exist * @throws IOException */ public abstract Metadata getMetadata(String name) throws IOException; /** * Create a new key. The given key must not already exist. * @param name the base name of the key * @param material the key material for the first version of the key. * @param options the options for the new key. * @return the version name of the first version of the key. * @throws IOException */ public abstract KeyVersion createKey(String name, byte[] material, Options options) throws IOException; /** * Get the algorithm from the cipher. * * @return the algorithm name */ private String getAlgorithm(String cipher) { int slash = cipher.indexOf('/'); if (slash == -1) { return cipher; } else { return cipher.substring(0, slash); } } /** * Generates a key material. * * @param size length of the key. * @param algorithm algorithm to use for generating the key. * @return the generated key. * @throws NoSuchAlgorithmException */ protected byte[] generateKey(int size, String algorithm) throws NoSuchAlgorithmException { algorithm = getAlgorithm(algorithm); KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); keyGenerator.init(size); byte[] key = keyGenerator.generateKey().getEncoded(); return key; } /** * Create a new key generating the material for it. * The given key must not already exist. *

* This implementation generates the key material and calls the * {@link #createKey(String, byte[], Options)} method. * * @param name the base name of the key * @param options the options for the new key. * @return the version name of the first version of the key. * @throws IOException * @throws NoSuchAlgorithmException */ public KeyVersion createKey(String name, Options options) throws NoSuchAlgorithmException, IOException { byte[] material = generateKey(options.getBitLength(), options.getCipher()); return createKey(name, material, options); } /** * Delete the given key. * @param name the name of the key to delete * @throws IOException */ public abstract void deleteKey(String name) throws IOException; /** * Roll a new version of the given key. * @param name the basename of the key * @param material the new key material * @return the name of the new version of the key * @throws IOException */ public abstract KeyVersion rollNewVersion(String name, byte[] material ) throws IOException; /** * Can be used by implementing classes to close any resources * that require closing */ public void close() throws IOException { // NOP } /** * Roll a new version of the given key generating the material for it. *

* This implementation generates the key material and calls the * {@link #rollNewVersion(String, byte[])} method. * * @param name the basename of the key * @return the name of the new version of the key * @throws IOException */ public KeyVersion rollNewVersion(String name) throws NoSuchAlgorithmException, IOException { Metadata meta = getMetadata(name); if (meta == null) { throw new IOException("Can't find Metadata for key " + name); } byte[] material = generateKey(meta.getBitLength(), meta.getCipher()); return rollNewVersion(name, material); } /** * Can be used by implementing classes to invalidate the caches. This could be * used after rollNewVersion to provide a strong guarantee to return the new * version of the given key. * * @param name the basename of the key * @throws IOException */ public void invalidateCache(String name) throws IOException { // NOP } /** * Ensures that any changes to the keys are written to persistent store. * @throws IOException */ public abstract void flush() throws IOException; /** * Split the versionName in to a base name. Converts "/aaa/bbb/3" to * "/aaa/bbb". * @param versionName the version name to split * @return the base name of the key * @throws IOException */ public static String getBaseName(String versionName) throws IOException { int div = versionName.lastIndexOf('@'); if (div == -1) { throw new IOException("No version in key path " + versionName); } return versionName.substring(0, div); } /** * Build a version string from a basename and version number. Converts * "/aaa/bbb" and 3 to "/aaa/bbb@3". * @param name the basename of the key * @param version the version of the key * @return the versionName of the key. */ protected static String buildVersionName(String name, int version) { return name + "@" + version; } /** * Find the provider with the given key. * @param providerList the list of providers * @param keyName the key name we are looking for * @return the KeyProvider that has the key */ public static KeyProvider findProvider(List providerList, String keyName) throws IOException { for(KeyProvider provider: providerList) { if (provider.getMetadata(keyName) != null) { return provider; } } throw new IOException("Can't find KeyProvider for key " + keyName); } /** * Does this provider require a password? This means that a password is * required for normal operation, and it has not been found through normal * means. If true, the password should be provided by the caller using * setPassword(). * @return Whether or not the provider requires a password * @throws IOException */ public boolean needsPassword() throws IOException { return false; } /** * If a password for the provider is needed, but is not provided, this will * return a warning and instructions for supplying said password to the * provider. * @return A warning and instructions for supplying the password */ public String noPasswordWarning() { return null; } /** * If a password for the provider is needed, but is not provided, this will * return an error message and instructions for supplying said password to * the provider. * @return An error message and instructions for supplying the password */ public String noPasswordError() { return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy