View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package org.apache.hadoop.hbase.io.crypto;
18  
19  import java.io.BufferedInputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.InputStream;
23  import java.io.IOException;
24  import java.net.URI;
25  import java.net.URISyntaxException;
26  import java.net.URLDecoder;
27  import java.security.Key;
28  import java.security.KeyStore;
29  import java.security.KeyStoreException;
30  import java.security.NoSuchAlgorithmException;
31  import java.security.UnrecoverableKeyException;
32  import java.security.cert.CertificateException;
33  import java.util.Locale;
34  import java.util.Properties;
35  
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.hbase.classification.InterfaceStability;
38  
39  /**
40   * A basic KeyProvider that can resolve keys from a protected KeyStore file
41   * on the local filesystem. It is configured with a URI passed in as a String
42   * to init(). The URI should have the form:
43   * <p>
44   * <pre>    scheme://path?option1=value1&amp;option2=value2</pre>
45   * <p>
46   * <i>scheme</i> can be either "jks" or "jceks", specifying the file based
47   * providers shipped with every JRE. The latter is the certificate store for
48   * the SunJCE cryptography extension, or PKCS #12, and is capable of storing
49   * SecretKeys.
50   * <p>
51   * <i>path</i> is the location of the keystore in the filesystem namespace.
52   * <p>
53   * Options can be specified as query parameters.
54   * <p>
55   * If the store was created with a password, the password can be specified
56   * using the option 'password'.
57   * <p>
58   * For example:
59   * <p>
60   * <pre>    jceks:///var/tmp/example.ks?password=foobar</pre>
61   * <p>
62   * It is assumed that all keys in the store are protected with the same
63   * password.
64   * <p>
65   * Alternatively, a properties file can be specified containing passwords for
66   * keys in the keystore.
67   * <pre>    jceks:///var/tmp/example.ks?passwordFile=/var/tmp/example.pw</pre>
68   * <p>
69   * Subclasses for supporting KeyStores that are not file based can extend the
70   * protected methods of this class to specify the appropriate
71   * LoadStoreParameters.
72   */
73  @InterfaceAudience.Public
74  @InterfaceStability.Evolving
75  public class KeyStoreKeyProvider implements KeyProvider {
76  
77    protected KeyStore store;
78    protected char[] password;         // can be null if no password
79    protected Properties passwordFile; // can be null if no file provided
80  
81    protected void processParameter(String name, String value) throws IOException {
82      if (name.equalsIgnoreCase(KeyProvider.PASSWORD)) {
83        password = value.toCharArray();
84      }
85      if (name.equalsIgnoreCase(KeyProvider.PASSWORDFILE)) {
86        Properties p = new Properties();
87        InputStream in = new BufferedInputStream(new FileInputStream(new File(value)));
88        try {
89          p.load(in);
90          passwordFile = p;
91        } finally {
92          in.close();
93        }
94      }
95    }
96  
97    protected void processParameters(URI uri) throws IOException {
98      String params = uri.getQuery();
99      if (params == null || params.isEmpty()) {
100       return;
101     }
102     do {
103       int nameStart = 0;
104       int nameEnd = params.indexOf('=');
105       if (nameEnd == -1) {
106         throw new RuntimeException("Invalid parameters: '" + params + "'");
107       }
108       int valueStart = nameEnd + 1;
109       int valueEnd = params.indexOf('&');
110       if (valueEnd == -1) {
111         valueEnd = params.length();
112       }
113       String name = URLDecoder.decode(params.substring(nameStart, nameEnd), "UTF-8");
114       String value = URLDecoder.decode(params.substring(valueStart, valueEnd), "UTF-8");
115       processParameter(name, value);
116       params = params.substring(valueEnd, params.length());
117     } while (!params.isEmpty());
118   }
119 
120   protected void load(URI uri) throws IOException {
121     String path = uri.getPath();
122     if (path == null || path.isEmpty()) {
123       throw new RuntimeException("KeyProvider parameters should specify a path");
124     }
125     InputStream is = new FileInputStream(new File(path));
126     try {
127       store.load(is, password);
128     } catch (NoSuchAlgorithmException e) {
129       throw new RuntimeException(e);
130     } catch (CertificateException e) {
131       throw new RuntimeException(e);
132     } finally {
133       is.close();
134     }
135   }
136 
137   @Override
138   public void init(String params) {
139     try {
140       URI uri = new URI(params);
141       String storeType = uri.getScheme();
142       if (storeType == null || storeType.isEmpty()) {
143         throw new RuntimeException("KeyProvider scheme should specify KeyStore type");
144       }
145       // KeyStore expects instance type specifications in uppercase
146       store = KeyStore.getInstance(storeType.toUpperCase(Locale.ROOT));
147       processParameters(uri);
148       load(uri);
149     } catch (URISyntaxException e) {
150       throw new RuntimeException(e);
151     } catch (KeyStoreException e) {
152       throw new RuntimeException(e);
153     } catch (IOException e) {
154       throw new RuntimeException(e);
155     }
156   }
157 
158   protected char[] getAliasPassword(String alias) {
159     if (password != null) {
160       return password;
161     }
162     if (passwordFile != null) {
163       String p = passwordFile.getProperty(alias);
164       if (p != null) {
165         return p.toCharArray();
166       }
167     }
168     return null;
169   }
170 
171   @Override
172   public Key getKey(String alias) {
173     try {
174       return store.getKey(alias, getAliasPassword(alias));
175     } catch (UnrecoverableKeyException e) {
176       throw new RuntimeException(e);
177     } catch (KeyStoreException e) {
178       throw new RuntimeException(e);
179     } catch (NoSuchAlgorithmException e) {
180       throw new RuntimeException(e);
181     }
182   }
183 
184   @Override
185   public Key[] getKeys(String[] aliases) {
186     Key[] result = new Key[aliases.length];
187     for (int i = 0; i < aliases.length; i++) {
188       result[i] = getKey(aliases[i]);
189     }
190     return result;
191   }
192 
193 }