001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.jbi.security.keystore.impl;
018    
019    import java.io.BufferedInputStream;
020    import java.io.File;
021    import java.io.FileInputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.security.Key;
025    import java.security.KeyStore;
026    import java.security.KeyStoreException;
027    import java.security.NoSuchAlgorithmException;
028    import java.security.PrivateKey;
029    import java.security.UnrecoverableKeyException;
030    import java.security.cert.Certificate;
031    import java.security.cert.CertificateException;
032    import java.util.ArrayList;
033    import java.util.Enumeration;
034    import java.util.HashMap;
035    import java.util.List;
036    import java.util.Map;
037    
038    import javax.net.ssl.KeyManager;
039    import javax.net.ssl.KeyManagerFactory;
040    import javax.net.ssl.TrustManager;
041    import javax.net.ssl.TrustManagerFactory;
042    
043    import org.apache.commons.logging.Log;
044    import org.apache.commons.logging.LogFactory;
045    import org.apache.servicemix.jbi.security.keystore.KeystoreInstance;
046    import org.apache.servicemix.jbi.security.keystore.KeystoreIsLocked;
047    import org.springframework.core.io.Resource;
048    
049    /**
050     * 
051     * @org.apache.xbean.XBean element="keystore"
052     */
053    public class FileKeystoreInstance implements KeystoreInstance {
054    
055        private static final Log LOG = LogFactory.getLog(FileKeystoreInstance.class);
056        private static final String JKS = "JKS";
057        
058        private Resource path;
059        private String name;
060        private String keystorePassword;
061        private Map keyPasswords = new HashMap();
062        private File keystoreFile; // Only valid after startup
063        
064        // The following variables are the state of the keystore, which should be chucked if the file on disk changes
065        private List privateKeys = new ArrayList();
066        private List trustCerts = new ArrayList();
067        private KeyStore keystore;
068        private long keystoreReadDate = Long.MIN_VALUE;
069        
070        /**
071         * @param keyPasswords the keyPasswords to set
072         */
073        public void setKeyPasswords(String keyPasswords) {
074            if (keyPasswords != null) {
075                String[] keys = keyPasswords.split("\\]\\!\\[");
076                for (int i = 0; i < keys.length; i++) {
077                    String key = keys[i];
078                    int pos = key.indexOf('=');
079                    this.keyPasswords.put(key.substring(0, pos), key.substring(pos + 1).toCharArray());
080                }
081            }
082        }
083    
084        /**
085         * @return the keystoreName
086         */
087        public String getName() {
088            return name;
089        }
090    
091        /**
092         * @param keystoreName the keystoreName to set
093         */
094        public void setName(String keystoreName) {
095            this.name = keystoreName;
096        }
097    
098        /**
099         * @param keystorePassword the keystorePassword to set
100         */
101        public void setKeystorePassword(String keystorePassword) {
102            this.keystorePassword = keystorePassword;
103        }
104    
105        /**
106         * @return the keystorePath
107         */
108        public Resource getPath() {
109            return path;
110        }
111    
112        /**
113         * @param keystorePath the keystorePath to set
114         */
115        public void setPath(Resource keystorePath) throws IOException {
116            this.path = keystorePath;
117            this.keystoreFile = keystorePath.getFile();
118        }
119    
120        public Certificate getCertificate(String alias) {
121            if (!loadKeystoreData()) {
122                return null;
123            }
124            try {
125                return keystore.getCertificate(alias);
126            } catch (KeyStoreException e) {
127                LOG.error("Unable to read certificate from keystore", e);
128            }
129            return null;
130        }
131    
132        public String getCertificateAlias(Certificate cert) {
133            if (!loadKeystoreData()) {
134                return null;
135            }
136            try {
137                return keystore.getCertificateAlias(cert);
138            } catch (KeyStoreException e) {
139                LOG.error("Unable to read retrieve alias for given certificate from keystore", e);
140            }
141            return null;
142        }
143    
144        public Certificate[] getCertificateChain(String alias) {
145            if (!loadKeystoreData()) {
146                return null;
147            }
148            try {
149                return keystore.getCertificateChain(alias);
150            } catch (KeyStoreException e) {
151                LOG.error("Unable to read certificate chain from keystore", e);
152            }
153            return null;
154        }
155    
156        public KeyManager[] getKeyManager(String algorithm, String keyAlias) throws KeystoreIsLocked, 
157                                        NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
158            if (isKeystoreLocked()) {
159                throw new KeystoreIsLocked("Keystore '" + name + "' is locked; please unlock it in the console.");
160            }
161            if (keystore == null || keystoreReadDate < keystoreFile.lastModified()) {
162                loadKeystoreData();
163            }
164            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(algorithm);
165            keyFactory.init(keystore, (char[]) keyPasswords.get(keyAlias));
166            return keyFactory.getKeyManagers();
167        }
168    
169        public PrivateKey getPrivateKey(String alias) {
170            if (!loadKeystoreData()) {
171                return null;
172            }
173            try {
174                if (isKeyLocked(alias)) {
175                    return null;
176                }
177                Key key = keystore.getKey(alias, (char[]) keyPasswords.get(alias));
178                if (key instanceof PrivateKey) {
179                    return (PrivateKey) key;
180                }
181            } catch (KeyStoreException e) {
182                LOG.error("Unable to read private key from keystore", e);
183            } catch (NoSuchAlgorithmException e) {
184                LOG.error("Unable to read private key from keystore", e);
185            } catch (UnrecoverableKeyException e) {
186                LOG.error("Unable to read private key from keystore", e);
187            }
188            return null;
189        }
190    
191        public TrustManager[] getTrustManager(String algorithm) throws KeyStoreException, 
192                                                NoSuchAlgorithmException, KeystoreIsLocked {
193            if (isKeystoreLocked()) {
194                throw new KeystoreIsLocked("Keystore '" + name + "' is locked; please unlock it in the console.");
195            }
196            if (!loadKeystoreData()) {
197                return null;
198            }
199            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(algorithm);
200            trustFactory.init(keystore);
201            return trustFactory.getTrustManagers();
202        }
203    
204        public boolean isKeyLocked(String keyAlias) {
205            return keyPasswords.get(keyAlias) == null;
206        }
207    
208        public boolean isKeystoreLocked() {
209            return keystorePassword == null;
210        }
211    
212        public String[] listPrivateKeys() {
213            if (!loadKeystoreData()) {
214                return null;
215            }
216            return (String[]) privateKeys.toArray(new String[privateKeys.size()]);
217        }
218    
219        public String[] listTrustCertificates() {
220            if (!loadKeystoreData()) {
221                return null;
222            }
223            return (String[]) trustCerts.toArray(new String[trustCerts.size()]);
224        }
225    
226        // ==================== Internals =====================
227    
228        private boolean loadKeystoreData() {
229            if (keystoreFile == null) {
230                throw new IllegalArgumentException("keystorePath not set");
231            }
232            if (keystoreReadDate >= keystoreFile.lastModified()) {
233                return true;
234            }
235            if (!keystoreFile.exists() || !keystoreFile.canRead()) {
236                throw new IllegalArgumentException("Invalid keystore file (" + path + " = " + keystoreFile.getAbsolutePath() + ")");
237            }
238            try {
239                keystoreReadDate = System.currentTimeMillis();
240                privateKeys.clear();
241                trustCerts.clear();
242                if (keystore == null) {
243                    keystore = KeyStore.getInstance(JKS);
244                }
245                InputStream in = new BufferedInputStream(new FileInputStream(keystoreFile));
246                keystore.load(in, keystorePassword == null ? new char[0] : keystorePassword.toCharArray());
247                in.close();
248                Enumeration aliases = keystore.aliases();
249                while (aliases.hasMoreElements()) {
250                    String alias = (String) aliases.nextElement();
251                    if (keystore.isKeyEntry(alias)) {
252                        privateKeys.add(alias);
253                    } else if (keystore.isCertificateEntry(alias)) {
254                        trustCerts.add(alias);
255                    }
256                }
257                return true;
258            } catch (KeyStoreException e) {
259                LOG.error("Unable to open keystore with provided password", e);
260            } catch (IOException e) {
261                LOG.error("Unable to open keystore with provided password", e);
262            } catch (NoSuchAlgorithmException e) {
263                LOG.error("Unable to open keystore with provided password", e);
264            } catch (CertificateException e) {
265                LOG.error("Unable to open keystore with provided password", e);
266            }
267            return false;
268        }
269    
270    }