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}