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.hadoop.io.crypto; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.crypto.SecretKey; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; /** * <code>KeyStoreKeyProvider</code> provides a {@link KeyProvider} * implementation to provide the ability to retrieve keys from a "JCEKS" key * store. */ public class KeyStoreKeyProvider implements KeyProvider { public static final String PASSWORD_PROPERTY_SUFFIX = ".password"; public static final String KEY_STORE_PASSWORD_PROPERTY = "keystore" + PASSWORD_PROPERTY_SUFFIX; public static final String DEFAULT_KEY_STORE_TYPE = "JKS"; private Configuration conf; private KeyStoreConfig keyStoreConfig; private KeyStore keyStore; private Properties passwords; /** * Helper method to get parameter string for the * <code>KeyStoreKeyProvider</code>. The parameter string is in the format of * URL query string with UTF8 encoding. For example, * keyStoreUrl=abc.store&keyStoreType=JKS&passwordFile=passwords.properties */ public static String getKeyStoreParameterString(String keyStoreFile, String keyStoreType, String keyStorePassword, String keyStorePasswordFile, boolean sharedPassword) throws CryptoException { KeyStoreConfig keyStoreConfig = new KeyStoreConfig(keyStoreFile, keyStoreType, keyStorePassword, keyStorePasswordFile, sharedPassword); return keyStoreConfig.toParameterString(); } /** * Helper method to get parameter string use the default encryption algorithm * defined by <code>DEFAULT_KEY_STORE_TYPE</code> for the * <code>KeyStoreKeyProvider</code> */ public static String getKeyStoreParameterString(String keyStoreFile, String keyStorePassword, String keyStorePasswordFile, boolean sharedPassword) throws CryptoException { return getKeyStoreParameterString(keyStoreFile, DEFAULT_KEY_STORE_TYPE, keyStorePassword, keyStorePasswordFile, sharedPassword); } /** * Parses an URL query string and returns a map with the parameter values. The * URL query string is the part in the URL after the first '?' character up to * an optional '#' character. It has the format "name=value&name=value&...". * The map has the same structure as the one returned by * javax.servlet.ServletRequest.getParameterMap(). A parameter name may occur * multiple times within the query string. For each parameter name, the map * contains a string array with the parameter values. * * @param s an URL query string. * @return a map containing parameter names as keys and parameter values as * map values. */ public static Map<String, String[]> parseUrlQueryString(String s) { if (s == null) { return new HashMap<String, String[]>(0); } // In map1 we use strings and ArrayLists to collect the parameter values. HashMap<String, Object> map1 = new HashMap<String, Object>(); int p = 0; while (p < s.length()) { int p0 = p; while (p < s.length() && s.charAt(p) != '=' && s.charAt(p) != '&') p++; String name = urlDecode(s.substring(p0, p)); if (p < s.length() && s.charAt(p) == '=') p++; p0 = p; while (p < s.length() && s.charAt(p) != '&') p++; String value = urlDecode(s.substring(p0, p)); if (p < s.length() && s.charAt(p) == '&') p++; Object x = map1.get(name); if (x == null) { // The first value of each name is added directly as a string to the // map. map1.put(name, value); } else if (x instanceof String) { // For multiple values, we use an ArrayList. ArrayList<String> a = new ArrayList<String>(); a.add((String) x); a.add(value); map1.put(name, a); } else { @SuppressWarnings("unchecked") ArrayList<String> a = (ArrayList<String>) x; a.add(value); } } // Copy map1 to map2. Map2 uses string arrays to store the parameter values. HashMap<String, String[]> map2 = new HashMap<String, String[]>(map1.size()); for (Map.Entry<String, Object> e : map1.entrySet()) { String name = e.getKey(); Object x = e.getValue(); String[] v; if (x instanceof String) { v = new String[] { (String) x }; } else { @SuppressWarnings("unchecked") ArrayList<String> a = (ArrayList<String>) x; v = new String[a.size()]; v = a.toArray(v); } map2.put(name, v); } return map2; } public static String toUrlQueryString(Map<String, List<String>> parameterMap) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, List<String>> entry : parameterMap.entrySet()) { List<String> values = entry.getValue(); for (String value : values) { if (sb.length() > 0) { sb.append("&"); } // name sb.append(urlEncode(entry.getKey())); sb.append('='); sb.append(urlEncode(value)); } } return sb.toString(); } /** * Help method to get a single parameter value from parameter map */ public static String getParameter(Map<String, String[]> parameterMap, String name, String defaultValue) { String[] values = parameterMap.get(name); if (values == null || values.length <= 0) { return defaultValue; } return values[0]; } /** * Help method to add a single parameter value to parameter map */ public static void addParameter(Map<String, List<String>> parameterMap, String name, String value) { if (value == null) return; List<String> values = parameterMap.get(name); if (values == null) { values = new ArrayList<String>(); values.add(value); parameterMap.put(name, values); } else { values.add(value); } } private static String urlDecode(String s) { try { return URLDecoder.decode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Error in urlDecode.", e); } } private static String urlEncode(String s) { try { return URLEncoder.encode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new UnsupportedOperationException(e); } } @Override public void setConf(Configuration conf) { this.conf = conf; } @Override public Configuration getConf() { return conf; } @Override public void init(String keyProviderParameters) throws CryptoException { keyStoreConfig = KeyStoreConfig.fromParameterString(keyProviderParameters); passwords = getPasswords(conf, keyStoreConfig); keyStore = getKeyStore(conf, keyStoreConfig); } /** * Implementation of getting keys from the key store. */ @Override public Key[] getKeys(String[] keyNames) throws CryptoException { if (keyStore == null) throw new CryptoException("Key store is not intialized."); if (keyNames == null) return null; Key[] rawKeys = new Key[keyNames.length]; try { for (int i = 0; i < keyNames.length; i++) { String keyName = keyNames[i]; String password = getKeyPassword(keyName); char[] passphase = null; if (password != null) passphase = password.toCharArray(); Key.KeyType keyType = Key.KeyType.OPAQUE; String algorithm = null; String format = null; byte[] rawKey; java.security.Key key = keyStore.getKey(keyName, passphase); if (key != null) { // secret key or private key rawKey = key.getEncoded(); algorithm = key.getAlgorithm(); format = key.getFormat(); if (key instanceof SecretKey) { keyType = Key.KeyType.SYMMETRIC_KEY; } else if (key instanceof PrivateKey) { keyType = Key.KeyType.PRIVATE_KEY; } } else { // trusted certificate Certificate certificate = keyStore.getCertificate(keyName); if (certificate == null) throw new CryptoException("Key " + keyName + " not found"); keyType = Key.KeyType.CERTIFICATE; rawKey = certificate.getEncoded(); } rawKeys[i] = new Key(keyType, algorithm, 0, format, rawKey); } } catch (KeyStoreException e) { throw new CryptoException(e); } catch (UnrecoverableEntryException e) { throw new CryptoException(e); } catch (NoSuchAlgorithmException e) { throw new CryptoException(e); } catch (CertificateException e) { throw new CryptoException(e); } return rawKeys; } /** * Wraps the configuration parameters for <code>KeyStoreKeyProvider</code>. */ public static class KeyStoreConfig implements Writable { public static final String PARAM_KEYSTORE_URL = "keyStoreUrl"; public static final String PARAM_KEYSTORE_TYPE = "keyStoreType"; public static final String PARAM_PASSWORD = "password"; public static final String PARAM_PASSWORD_FILE = "passwordfile"; public static final String PARAM_SHARED_PASSWORD = "sharedPassword"; private String keyStoreUrl; private String keyStorePassword; private String keyStorePasswordFile; private String keyStoreType; private boolean sharedPassword = true; /** * A new configuration object. */ public KeyStoreConfig() { } /** * A new configuration object with specified attributes. */ public KeyStoreConfig(String keyStoreUrl, String keyStoreType, String keyStorePassword, String keyStorePasswordFile, boolean sharedPassword) { super(); this.keyStoreUrl = keyStoreUrl; if (StringUtils.isNotEmpty(keyStoreType)) { this.keyStoreType = keyStoreType; } else { this.keyStoreType = DEFAULT_KEY_STORE_TYPE; } this.keyStorePassword = keyStorePassword; this.keyStorePasswordFile = keyStorePasswordFile; this.sharedPassword = sharedPassword; } /** * Returns the key store URL. */ public String getKeyStoreUrl() { return keyStoreUrl; } /** * Returns the key store password for accessing the key store. */ public String getKeyStorePassword() { return keyStorePassword; } /** * Returns the key store password file if the password for the key store are * stored in a properties file, or a per key password is needed and the per * key password are stored in a properties file. The property name of the * password should follow the rules below: 1. keystore.password is the * property name of the key store password 2. {keyname}.password is the * property name of the per key password replacing the {keyname} with the * actual key name. */ public String getKeyStorePasswordFile() { return keyStorePasswordFile; } /** * Specify whether the key share the same password with the key store. */ public boolean isSharedPassword() { return sharedPassword; } /** * Specify the encryption algorithm be used to encrypt */ public String getKeyStoreType() { return keyStoreType; } /** * Check whether the configuration object is valid. */ public void validate() throws CryptoException { if (keyStoreUrl == null || keyStoreUrl.isEmpty()) throw new CryptoException("The key store YRL is not specified."); } @Override public void write(DataOutput out) throws IOException { Text.writeString(out, keyStoreUrl); if (keyStorePassword != null) { out.writeBoolean(true); Text.writeString(out, keyStorePassword); } else { out.writeBoolean(false); } if (keyStorePasswordFile != null) { out.writeBoolean(true); Text.writeString(out, keyStorePasswordFile); } else { out.writeBoolean(false); } Text.writeString(out, keyStoreType); out.writeBoolean(sharedPassword); } @Override public void readFields(DataInput in) throws IOException { keyStoreUrl = Text.readString(in); boolean hasKeyStorePassword = in.readBoolean(); if (hasKeyStorePassword) keyStorePassword = Text.readString(in); else keyStorePassword = null; boolean hasKeyStorePasswordFile = in.readBoolean(); if (hasKeyStorePasswordFile) keyStorePasswordFile = Text.readString(in); else keyStorePasswordFile = null; keyStoreType = Text.readString(in); sharedPassword = in.readBoolean(); } /** * Serialize the object to a byte array. */ public byte[] toBytes() throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(outputStream); write(out); return outputStream.toByteArray(); } /** * Deserialize to a object from the byte array. */ public static KeyStoreConfig fromBytes(byte[] bytes) throws IOException { if (bytes == null || bytes.length <= 0) throw new IOException("Invalid data for key store config."); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); DataInputStream in = new DataInputStream(inputStream); KeyStoreConfig config = new KeyStoreConfig(); config.readFields(in); return config; } public String toParameterString() throws CryptoException { validate(); Map<String, List<String>> parameterMap = new HashMap<String, List<String>>(); addParameter(parameterMap, PARAM_KEYSTORE_URL, keyStoreUrl); addParameter(parameterMap, PARAM_KEYSTORE_TYPE, keyStoreType); addParameter(parameterMap, PARAM_PASSWORD, keyStorePassword); addParameter(parameterMap, PARAM_PASSWORD_FILE, keyStorePasswordFile); addParameter(parameterMap, PARAM_SHARED_PASSWORD, Boolean.toString(sharedPassword)); return toUrlQueryString(parameterMap); } public static KeyStoreConfig fromParameterString(String parameters) throws CryptoException { Map<String, String[]> parameterMap = parseUrlQueryString(parameters); String keyStoreUrl = getParameter(parameterMap, PARAM_KEYSTORE_URL, null); String keyStoreType = getParameter(parameterMap, PARAM_KEYSTORE_TYPE, null); String keyStorePassword = getParameter(parameterMap, PARAM_PASSWORD, null); String keyStorePasswordFile = getParameter(parameterMap, PARAM_PASSWORD_FILE, null); String sharedPassword = getParameter(parameterMap, PARAM_SHARED_PASSWORD, "true"); KeyStoreConfig keyStoreConfig = new KeyStoreConfig(keyStoreUrl, keyStoreType, keyStorePassword, keyStorePasswordFile, Boolean.parseBoolean(sharedPassword)); keyStoreConfig.validate(); return keyStoreConfig; } } protected KeyStore getKeyStore(Configuration conf, KeyStoreConfig keyStoreConfig) throws CryptoException { String keyStoreFile = keyStoreConfig.getKeyStoreUrl(); Path keyStorePath = new Path(keyStoreFile); try { FileSystem fs = keyStorePath.getFileSystem(conf); if (!fs.exists(keyStorePath)) throw new CryptoException("Key store file doesn't exist. File: " + keyStoreFile); // read the key specified by key name from key store KeyStore ks = KeyStore.getInstance(keyStoreConfig.getKeyStoreType()); // get the key store password String password = getKeyStorePassword(); if (password == null || password.isEmpty()) throw new CryptoException("Key store password is not specified in any forms."); char[] passphase = password.toCharArray(); InputStream in = new BufferedInputStream(fs.open(keyStorePath)); try { ks.load(in, passphase); return ks; } finally { in.close(); } } catch (IOException e) { throw new CryptoException(e); } catch (KeyStoreException e) { throw new CryptoException(e); } catch (Exception e) { throw new CryptoException(e); } } protected Properties getPasswords(Configuration conf, KeyStoreConfig keyStoreConfig) throws CryptoException { String keyStorePasswordFile = keyStoreConfig.getKeyStorePasswordFile(); if (keyStorePasswordFile == null || keyStorePasswordFile.isEmpty()) return null; Path keyStorePasswordPath = new Path(keyStorePasswordFile); try { FileSystem fs = keyStorePasswordPath.getFileSystem(conf); if (!fs.exists(keyStorePasswordPath)) throw new CryptoException("Key store password file doesn't exist. File: " + keyStorePasswordFile); InputStream in = new BufferedInputStream(fs.open(keyStorePasswordPath)); try { Properties passwords = new Properties(); passwords.load(in); return passwords; } finally { in.close(); } } catch (IOException e) { throw new CryptoException(e); } catch (Exception e) { throw new CryptoException(e); } } protected String getKeyStorePassword() throws CryptoException { String password = keyStoreConfig.getKeyStorePassword(); if (password == null) { if (passwords != null) password = passwords.getProperty(KEY_STORE_PASSWORD_PROPERTY); } return password; } protected String getKeyPassword(String keyName) throws CryptoException { String password = null; if (keyStoreConfig.isSharedPassword()) { password = getKeyStorePassword(); } else { if (passwords != null) password = passwords.getProperty(keyName + PASSWORD_PROPERTY_SUFFIX); } return password; } }