001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (C) 2012 Paul Ferraro 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Lesser General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package net.sf.hajdbc.codec.crypto; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.security.GeneralSecurityException; 025import java.security.KeyStore; 026import java.sql.SQLException; 027import java.text.MessageFormat; 028 029import net.sf.hajdbc.codec.Codec; 030import net.sf.hajdbc.codec.CodecFactory; 031import net.sf.hajdbc.util.Resources; 032import net.sf.hajdbc.util.Strings; 033import net.sf.hajdbc.util.SystemProperties; 034 035/** 036 * Used to decrypt configuration file passwords using a symmetric key stored in a keystore. 037 * Use the following command to generate Base-64 encoded encrypted passwords for use in your config file:<br/> 038 * <p><code>java -classpath ha-jdbc.jar net.sf.hajdbc.codec.crypto.CipherCodecFactory [password]</code></p> 039 * The following system properties can be used to customize the properties of the key and/or keystore: 040 * <table> 041 * <tr> 042 * <th>Property</th> 043 * <th>Default</th> 044 * </tr> 045 * <tr> 046 * <td>ha-jdbc.keystore.file</td> 047 * <td>~/.keystore</td> 048 * </tr> 049 * <tr> 050 * <td>ha-jdbc.keystore.type</td> 051 * <td>jceks</td> 052 * </tr> 053 * <tr> 054 * <td>ha-jdbc.keystore.password</td> 055 * <td><em>none</em></td> 056 * </tr> 057 * <tr> 058 * <td>ha-jdbc.key.alias</td> 059 * <td>ha-jdbc</td> 060 * </tr> 061 * <tr> 062 * <td>ha-jdbc.key.password</td> 063 * <td><em>required</em><td> 064 * </tr> 065 * </table> 066 * @author Paul Ferraro 067 */ 068public class CipherCodecFactory implements CodecFactory 069{ 070 private static final long serialVersionUID = -4409167180573651279L; 071 072 public static final String DEFAULT_KEYSTORE_FILE = String.format("%s/.keystore", SystemProperties.getSystemProperty("user.home")); 073 public static final String DEFAULT_KEY_ALIAS = "ha-jdbc"; 074 075 enum Property 076 { 077 KEYSTORE_FILE("keystore.file", DEFAULT_KEYSTORE_FILE), 078 KEYSTORE_TYPE("keystore.type", "jceks"), 079 KEYSTORE_PASSWORD("keystore.password", null), 080 KEY_ALIAS("key.alias", DEFAULT_KEY_ALIAS), 081 KEY_PASSWORD("key.password", null); 082 083 final String nameFormat; 084 final String name; 085 final String defaultValue; 086 087 private Property(String name, String defaultValue) 088 { 089 this.nameFormat = "ha-jdbc.{0}." + name; 090 this.name = "ha-jdbc." + name; 091 this.defaultValue = defaultValue; 092 } 093 } 094 095 private static String getProperty(String id, Property property) 096 { 097 String value = SystemProperties.getSystemProperty(MessageFormat.format(property.nameFormat, id)); 098 099 if (value != null) return value; 100 101 String pattern = SystemProperties.getSystemProperty(property.name, property.defaultValue); 102 103 if (pattern == null) return null; 104 105 return MessageFormat.format(pattern, id); 106 } 107 108 @Override 109 public String getId() 110 { 111 return "?"; 112 } 113 114 /** 115 * {@inheritDoc} 116 * @see net.sf.hajdbc.codec.CodecFactory#createCodec(java.lang.String) 117 */ 118 @Override 119 public Codec createCodec(String clusterId) throws SQLException 120 { 121 String type = getProperty(clusterId, Property.KEYSTORE_TYPE); 122 File file = new File(getProperty(clusterId, Property.KEYSTORE_FILE)); 123 String password = getProperty(clusterId, Property.KEYSTORE_PASSWORD); 124 125 String keyAlias = getProperty(clusterId, Property.KEY_ALIAS); 126 String keyPassword = getProperty(clusterId, Property.KEY_PASSWORD); 127 128 try 129 { 130 KeyStore store = KeyStore.getInstance(type); 131 132 InputStream input = new FileInputStream(file); 133 try 134 { 135 store.load(input, (password != null) ? password.toCharArray() : null); 136 } 137 finally 138 { 139 Resources.close(input); 140 } 141 return new CipherCodec(store.getKey(keyAlias, (keyPassword != null) ? keyPassword.toCharArray() : null)); 142 } 143 catch (GeneralSecurityException e) 144 { 145 throw new SQLException(e); 146 } 147 catch (IOException e) 148 { 149 throw new SQLException(e); 150 } 151 } 152 153 public static void main(String... args) 154 { 155 if (args.length != 2) 156 { 157 System.err.println(String.format("Usage:%s\tjava %s <cluster-id> <password-to-encrypt>", Strings.NEW_LINE, CipherCodecFactory.class.getName())); 158 System.exit(1); 159 return; 160 } 161 162 String clusterId = args[0]; 163 String value = args[1]; 164 165 try 166 { 167 Codec codec = new CipherCodecFactory().createCodec(clusterId); 168 169 System.out.println(codec.encode(value)); 170 } 171 catch (SQLException e) 172 { 173 e.printStackTrace(System.err); 174 } 175 } 176}