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.Properties;
34  
35  import org.apache.hadoop.hbase.classification.InterfaceAudience;
36  import org.apache.hadoop.hbase.classification.InterfaceStability;
37  
38  /**
39   * A basic KeyProvider that can resolve keys from a protected KeyStore file
40   * on the local filesystem. It is configured with a URI passed in as a String
41   * to init(). The URI should have the form:
42   * <p>
43   * <pre>    scheme://path?option1=value1&amp;option2=value2</pre>
44   * <p>
45   * <i>scheme</i> can be either "jks" or "jceks", specifying the file based
46   * providers shipped with every JRE. The latter is the certificate store for
47   * the SunJCE cryptography extension, or PKCS #12, and is capable of storing
48   * SecretKeys.
49   * <p>
50   * <i>path</i> is the location of the keystore in the filesystem namespace.
51   * <p>
52   * Options can be specified as query parameters.
53   * <p>
54   * If the store was created with a password, the password can be specified
55   * using the option 'password'.
56   * <p>
57   * For example:
58   * <p>
59   * <pre>    jceks:///var/tmp/example.ks?password=foobar</pre>
60   * <p>
61   * It is assumed that all keys in the store are protected with the same
62   * password.
63   * <p>
64   * Alternatively, a properties file can be specified containing passwords for
65   * keys in the keystore.
66   * <pre>    jceks:///var/tmp/example.ks?passwordFile=/var/tmp/example.pw</pre>
67   * <p>
68   * Subclasses for supporting KeyStores that are not file based can extend the
69   * protected methods of this class to specify the appropriate
70   * LoadStoreParameters.
71   */
72  @InterfaceAudience.Public
73  @InterfaceStability.Evolving
74  public class KeyStoreKeyProvider implements KeyProvider {
75  
76    protected KeyStore store;
77    protected char[] password;         // can be null if no password
78    protected Properties passwordFile; // can be null if no file provided
79  
80    protected void processParameter(String name, String value) throws IOException {
81      if (name.equalsIgnoreCase(KeyProvider.PASSWORD)) {
82        password = value.toCharArray();
83      }
84      if (name.equalsIgnoreCase(KeyProvider.PASSWORDFILE)) {
85        Properties p = new Properties();
86        InputStream in = new BufferedInputStream(new FileInputStream(new File(value)));
87        try {
88          p.load(in);
89          passwordFile = p;
90        } finally {
91          in.close();
92        }
93      }
94    }
95  
96    protected void processParameters(URI uri) throws IOException {
97      String params = uri.getQuery();
98      if (params == null || params.isEmpty()) {
99        return;
100     }
101     do {
102       int nameStart = 0;
103       int nameEnd = params.indexOf('=');
104       if (nameEnd == -1) {
105         throw new RuntimeException("Invalid parameters: '" + params + "'");
106       }
107       int valueStart = nameEnd + 1;
108       int valueEnd = params.indexOf('&');
109       if (valueEnd == -1) {
110         valueEnd = params.length();
111       }
112       String name = URLDecoder.decode(params.substring(nameStart, nameEnd), "UTF-8");
113       String value = URLDecoder.decode(params.substring(valueStart, valueEnd), "UTF-8");
114       processParameter(name, value);
115       params = params.substring(valueEnd, params.length());
116     } while (!params.isEmpty());
117   }
118 
119   protected void load(URI uri) throws IOException {
120     String path = uri.getPath();
121     if (path == null || path.isEmpty()) {
122       throw new RuntimeException("KeyProvider parameters should specify a path");
123     }
124     InputStream is = new FileInputStream(new File(path));
125     try {
126       store.load(is, password);
127     } catch (NoSuchAlgorithmException e) {
128       throw new RuntimeException(e);
129     } catch (CertificateException e) {
130       throw new RuntimeException(e);
131     } finally {
132       is.close();
133     }
134   }
135 
136   @Override
137   public void init(String params) {
138     try {
139       URI uri = new URI(params);
140       String storeType = uri.getScheme();
141       if (storeType == null || storeType.isEmpty()) {
142         throw new RuntimeException("KeyProvider scheme should specify KeyStore type");
143       }
144       // KeyStore expects instance type specifications in uppercase
145       store = KeyStore.getInstance(storeType.toUpperCase());
146       processParameters(uri);
147       load(uri);
148     } catch (URISyntaxException e) {
149       throw new RuntimeException(e);
150     } catch (KeyStoreException e) {
151       throw new RuntimeException(e);
152     } catch (IOException e) {
153       throw new RuntimeException(e);
154     }
155   }
156 
157   protected char[] getAliasPassword(String alias) {
158     if (password != null) {
159       return password;
160     }
161     if (passwordFile != null) {
162       String p = passwordFile.getProperty(alias);
163       if (p != null) {
164         return p.toCharArray();
165       }
166     }
167     return null;
168   }
169 
170   @Override
171   public Key getKey(String alias) {
172     try {
173       return store.getKey(alias, getAliasPassword(alias));
174     } catch (UnrecoverableKeyException e) {
175       throw new RuntimeException(e);
176     } catch (KeyStoreException e) {
177       throw new RuntimeException(e);
178     } catch (NoSuchAlgorithmException e) {
179       throw new RuntimeException(e);
180     }
181   }
182 
183   @Override
184   public Key[] getKeys(String[] aliases) {
185     Key[] result = new Key[aliases.length];
186     for (int i = 0; i < aliases.length; i++) {
187       result[i] = getKey(aliases[i]);
188     }
189     return result;
190   }
191 
192 }