001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.crypto; 019 020import java.io.BufferedInputStream; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.URI; 026import java.net.URISyntaxException; 027import java.net.URLDecoder; 028import java.security.Key; 029import java.security.KeyStore; 030import java.security.KeyStoreException; 031import java.security.NoSuchAlgorithmException; 032import java.security.UnrecoverableKeyException; 033import java.security.cert.CertificateException; 034import java.util.Locale; 035import java.util.Properties; 036import org.apache.yetus.audience.InterfaceAudience; 037 038/** 039 * A basic KeyProvider that can resolve keys from a protected KeyStore file on the local filesystem. 040 * It is configured with a URI passed in as a String to init(). The URI should have the form: 041 * <p> 042 * 043 * <pre> 044 * scheme://path?option1=value1&option2=value2 045 * </pre> 046 * <p> 047 * <i>scheme</i> can be either "jks" or "jceks", specifying the file based providers shipped with 048 * every JRE. The latter is the certificate store for the SunJCE cryptography extension, or PKCS 049 * #12, and is capable of storing SecretKeys. 050 * <p> 051 * <i>path</i> is the location of the keystore in the filesystem namespace. 052 * <p> 053 * Options can be specified as query parameters. 054 * <p> 055 * If the store was created with a password, the password can be specified using the option 056 * 'password'. 057 * <p> 058 * For example: 059 * <p> 060 * 061 * <pre> 062 * jceks:///var/tmp/example.ks?password=foobar 063 * </pre> 064 * <p> 065 * It is assumed that all keys in the store are protected with the same password. 066 * <p> 067 * Alternatively, a properties file can be specified containing passwords for keys in the keystore. 068 * 069 * <pre> 070 * jceks:///var/tmp/example.ks?passwordFile=/var/tmp/example.pw 071 * </pre> 072 * <p> 073 * Subclasses for supporting KeyStores that are not file based can extend the protected methods of 074 * this class to specify the appropriate LoadStoreParameters. 075 */ 076@InterfaceAudience.Public 077public class KeyStoreKeyProvider implements KeyProvider { 078 079 protected KeyStore store; 080 protected char[] password; // can be null if no password 081 protected Properties passwordFile; // can be null if no file provided 082 083 protected void processParameter(String name, String value) throws IOException { 084 if (name.equalsIgnoreCase(KeyProvider.PASSWORD)) { 085 password = value.toCharArray(); 086 } 087 if (name.equalsIgnoreCase(KeyProvider.PASSWORDFILE)) { 088 Properties p = new Properties(); 089 InputStream in = new BufferedInputStream(new FileInputStream(new File(value))); 090 try { 091 p.load(in); 092 passwordFile = p; 093 } finally { 094 in.close(); 095 } 096 } 097 } 098 099 protected void processParameters(URI uri) throws IOException { 100 String params = uri.getQuery(); 101 if (params == null || params.isEmpty()) { 102 return; 103 } 104 do { 105 int nameStart = 0; 106 int nameEnd = params.indexOf('='); 107 if (nameEnd == -1) { 108 throw new RuntimeException("Invalid parameters: '" + params + "'"); 109 } 110 int valueStart = nameEnd + 1; 111 int valueEnd = params.indexOf('&'); 112 if (valueEnd == -1) { 113 valueEnd = params.length(); 114 } 115 String name = URLDecoder.decode(params.substring(nameStart, nameEnd), "UTF-8"); 116 String value = URLDecoder.decode(params.substring(valueStart, valueEnd), "UTF-8"); 117 processParameter(name, value); 118 params = params.substring(valueEnd, params.length()); 119 } while (!params.isEmpty()); 120 } 121 122 protected void load(URI uri) throws IOException { 123 String path = uri.getPath(); 124 if (path == null || path.isEmpty()) { 125 throw new RuntimeException("KeyProvider parameters should specify a path"); 126 } 127 InputStream is = new FileInputStream(new File(path)); 128 try { 129 store.load(is, password); 130 } catch (NoSuchAlgorithmException e) { 131 throw new RuntimeException(e); 132 } catch (CertificateException e) { 133 throw new RuntimeException(e); 134 } finally { 135 is.close(); 136 } 137 } 138 139 @Override 140 public void init(String params) { 141 try { 142 URI uri = new URI(params); 143 String storeType = uri.getScheme(); 144 if (storeType == null || storeType.isEmpty()) { 145 throw new RuntimeException("KeyProvider scheme should specify KeyStore type"); 146 } 147 // KeyStore expects instance type specifications in uppercase 148 store = KeyStore.getInstance(storeType.toUpperCase(Locale.ROOT)); 149 processParameters(uri); 150 load(uri); 151 } catch (URISyntaxException e) { 152 throw new RuntimeException(e); 153 } catch (KeyStoreException e) { 154 throw new RuntimeException(e); 155 } catch (IOException e) { 156 throw new RuntimeException(e); 157 } 158 } 159 160 protected char[] getAliasPassword(String alias) { 161 if (password != null) { 162 return password; 163 } 164 if (passwordFile != null) { 165 String p = passwordFile.getProperty(alias); 166 if (p != null) { 167 return p.toCharArray(); 168 } 169 } 170 return null; 171 } 172 173 @Override 174 public Key getKey(String alias) { 175 try { 176 return store.getKey(alias, getAliasPassword(alias)); 177 } catch (UnrecoverableKeyException e) { 178 throw new RuntimeException(e); 179 } catch (KeyStoreException e) { 180 throw new RuntimeException(e); 181 } catch (NoSuchAlgorithmException e) { 182 throw new RuntimeException(e); 183 } 184 } 185 186 @Override 187 public Key[] getKeys(String[] aliases) { 188 Key[] result = new Key[aliases.length]; 189 for (int i = 0; i < aliases.length; i++) { 190 result[i] = getKey(aliases[i]); 191 } 192 return result; 193 } 194 195}