diff --git a/common/src/java/org/apache/hadoop/hive/common/crypto/key/JavaKeyStoreProvider.java b/common/src/java/org/apache/hadoop/hive/common/crypto/key/JavaKeyStoreProvider.java new file mode 100644 index 0000000..4f792bc --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/crypto/key/JavaKeyStoreProvider.java @@ -0,0 +1,243 @@ +/* + * 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.hive.common.crypto.key; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Properties; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javax.crypto.spec.SecretKeySpec; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * A basic KeyProvider that can resolve keys from a protected KeyStore file + * on the local filesystem. It is configured with a URI passed in as a String + * to init(). The URI should have the form: + *

+ *

    scheme://path?option1=value1&option2=value2
+ *

+ * scheme can be either "jks" or "jceks", specifying the file based + * providers shipped with every JRE. The latter is the certificate store for + * the SunJCE cryptography extension, or PKCS #12, and is capable of storing + * SecretKeys. + *

+ * path is the location of the keystore in the filesystem namespace. + *

+ * Options can be specified as query parameters. + *

+ * If the store was created with a password, the password can be specified + * using the option 'password'. + *

+ * For example: + *

+ *

    jceks:///var/tmp/example.ks?password=foobar
+ *

+ * It is assumed that all keys in the store are protected with the same + * password. + *

+ * Alternatively, a properties file can be specified containing passwords for + * keys in the keystore. + *

    jceks:///var/tmp/example.ks?passwordFile=/var/tmp/example.pw
+ *

+ * Subclasses for supporting KeyStores that are not file based can extend the + * protected methods of this class to specify the appropriate + * LoadStoreParameters. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class JavaKeyStoreProvider implements KeyProvider { + + protected KeyStore store; + protected char[] password; // can be null if no password + protected Properties passwordFile; // can be null if no file provided + private Lock readLock; + private Lock writeLock; + + protected void processParameter(String name, String value) throws IOException { + if (name.equalsIgnoreCase(KeyProvider.PASSWORD)) { + password = value.toCharArray(); + } + if (name.equalsIgnoreCase(KeyProvider.PASSWORDFILE)) { + Properties p = new Properties(); + InputStream in = new BufferedInputStream(new FileInputStream(new File(value))); + try { + p.load(in); + passwordFile = p; + } finally { + in.close(); + } + } + } + + protected void processParameters(URI uri) throws IOException { + String params = uri.getQuery(); + if (params == null || params.isEmpty()) { + return; + } + do { + int nameStart = 0; + int nameEnd = params.indexOf('='); + if (nameEnd == -1) { + throw new RuntimeException("Invalid parameters: '" + params + "'"); + } + int valueStart = nameEnd + 1; + int valueEnd = params.indexOf('&'); + if (valueEnd == -1) { + valueEnd = params.length(); + } + String name = URLDecoder.decode(params.substring(nameStart, nameEnd), "UTF-8"); + String value = URLDecoder.decode(params.substring(valueStart, valueEnd), "UTF-8"); + processParameter(name, value); + params = params.substring(valueEnd, params.length()); + } while (!params.isEmpty()); + } + + protected void load(URI uri) throws IOException { + String path = uri.getPath(); + if (path == null || path.isEmpty()) { + throw new RuntimeException("KeyProvider parameters should specify a path"); + } + InputStream is = new FileInputStream(new File(path)); + try { + store.load(is, password); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (CertificateException e) { + throw new RuntimeException(e); + } finally { + is.close(); + } + } + + @Override + public void init(String params) { + try { + URI uri = new URI(params); + String storeType = uri.getScheme(); + if (storeType == null || storeType.isEmpty()) { + throw new RuntimeException("KeyProvider scheme should specify KeyStore type"); + } + // KeyStore expects instance type specifications in uppercase + store = KeyStore.getInstance(storeType.toUpperCase()); + processParameters(uri); + load(uri); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } catch (KeyStoreException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + ReadWriteLock lock = new ReentrantReadWriteLock(true); + readLock = lock.readLock(); + writeLock = lock.writeLock(); + } + + protected char[] getAliasPassword(String alias) { + if (password != null) { + return password; + } + if (passwordFile != null) { + String p = passwordFile.getProperty(alias); + if (p != null) { + return p.toCharArray(); + } + } + return null; + } + + @Override + public Key getKey(String name) throws IOException { + try { + readLock.lock(); + java.security.Key javaKey = store.getKey(name, getAliasPassword(name)); + Key key = new Key(name, javaKey.getEncoded()); + return key; + } catch (UnrecoverableKeyException e) { + throw new IOException("Can not recover key " + name); + } catch (KeyStoreException e) { + throw new IOException("Can not get key " + name); + } catch (NoSuchAlgorithmException e) { + throw new IOException("Can not get algorithm for key " + name); + } finally { + readLock.unlock(); + } + } + + @Override + public Key[] getKeys(String[] aliases) throws IOException { + Key[] result = new Key[aliases.length]; + for (int i = 0; i < aliases.length; i++) { + result[i] = getKey(aliases[i]); + } + return result; + } + + @Override + public Key createKey(String name, + byte[] material, String cipher) throws IOException { + writeLock.lock(); + try { + try { + if (store.containsAlias(name)) { + throw new IOException("Key " + name + " already exists in " + this); + } + } catch (KeyStoreException e) { + throw new IOException("Problem looking up key " + name + " in " + this, e); + } + try { + store.setKeyEntry(name, new SecretKeySpec(material, cipher), + password, null); + } catch (KeyStoreException e) { + throw new IOException("Can't store key " + name + " in " + this, e); + } + return new Key(name, material); + } finally { + writeLock.unlock(); + } + } + + @Override + public void deleteKey(String name) throws IOException { + try { + writeLock.lock(); + if (store.containsAlias(name)) { + store.deleteEntry(name); + } + } catch (KeyStoreException e) { + throw new IOException("Problem removing " + name + " from " + this, e); + } finally { + writeLock.unlock(); + } + } +} diff --git a/common/src/java/org/apache/hadoop/hive/common/crypto/key/KeyProvider.java b/common/src/java/org/apache/hadoop/hive/common/crypto/key/KeyProvider.java new file mode 100644 index 0000000..db569ae --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/crypto/key/KeyProvider.java @@ -0,0 +1,78 @@ +/* + * 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.hive.common.crypto.key; + +import java.io.IOException; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * KeyProvider is a interface to management secret + * {@link org.apache.hadoop.hive.common.crypto.key.Key} + * from key storage such as Java key store. + * + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public interface KeyProvider { + + public static final String PASSWORD = "password"; + public static final String PASSWORDFILE = "passwordfile"; + + /** + * Initialize the key provider + * @param params + */ + public void init(String params); + + /** + * Get the key for a given key name. + * @param name, key name + * @return the specific key corresponding to the supplied key name, + * or null if the key is not found + * @throws IOException + */ + public Key getKey(String name) throws IOException; + + /** + * Get keys for a given set of key names. + * @param names, an array of key names + * @return an array of keys corresponding to the supplied key names, + * or null if none is found + * @throws IOException + */ + public Key[] getKeys(String[] names) throws IOException; + + /** + * Create a new key. The given key must not already exist. + * @param name, key name + * @param material, the key material for the key. + * @param cipher, the key cipher name. + * @return the new key. + * @throws IOException + */ + public Key createKey(String name, byte[] material, String cipher) throws IOException; + + /** + * Delete a key for a given key name. + * @param name, key name + * @throws IOException + */ + public void deleteKey(String name) throws IOException; + +}