View Javadoc

1   /*
2    * The MIT License
3    *
4    * Original work sponsored and donated by National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
5    *
6    * Copyright (C) 2011 National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   *
26   * $HeadURL: https://svn.softwareborsen.dk/sosi/trunk/modules/seal/src/main/java/dk/sosi/seal/vault/GenericCredentialVault.java $
27   * $Id: GenericCredentialVault.java 8697 2011-09-02 10:33:55Z chg@lakeside.dk $
28   */
29  package dk.sosi.seal.vault;
30  
31  import dk.sosi.seal.SOSIFactory;
32  import dk.sosi.seal.model.SignatureUtil;
33  import dk.sosi.seal.pki.AuditEventHandler;
34  
35  import java.io.*;
36  import java.security.*;
37  import java.security.cert.Certificate;
38  import java.security.cert.CertificateException;
39  import java.security.cert.CertificateFactory;
40  import java.security.cert.X509Certificate;
41  import java.util.Enumeration;
42  import java.util.Properties;
43  
44  /**
45   * CredentialVault implementation, which stores the internal KeyStore in memory.
46   *
47   * @author kkj
48   * @author $LastChangedBy: chg@lakeside.dk $
49   * @since 1.0
50   */
51  public class GenericCredentialVault implements CredentialVault {
52  
53  	protected KeyStore keyStore;
54  	protected String keyStorePassword;
55  
56  	protected Properties properties;
57  
58  	private void log(){
59  		SOSIFactory.getAuditEventHandler(properties).onInformationalAuditingEvent(
60  				AuditEventHandler.EVENT_TYPE_INFO_CREDENTIAL_VAULT_INITIALIZED,
61  				new Object[]{this}
62  				);
63  	}
64  
65  	public GenericCredentialVault(Properties properties, KeyStore keyStore, String keyStorePassword) throws CredentialVaultException {
66  		this.properties = properties;
67  		if (keyStore == null) {
68  			throw new CredentialVaultException("Keystore cannot be null!");
69  		}
70  		this.keyStore = keyStore;
71  
72  		if (keyStorePassword == null) {
73  			throw new CredentialVaultException("Keystore password cannot be null!");
74  		}
75  		this.keyStorePassword = keyStorePassword;
76  
77  		log();
78  	}
79  
80  	/**
81  	 * Create a generic credential vault based off a JKS java keystore in memory
82  	 *
83  	 * @throws CredentialVaultException
84  	 *             If the keystore could not be created
85  	 */
86  	protected GenericCredentialVault(Properties properties) throws CredentialVaultException {
87  		this.properties = properties;
88  		try {
89  			keyStore = KeyStore.getInstance("JKS");
90  		} catch (KeyStoreException e) {
91  			throw new CredentialVaultException("Unable to create Java Keystore", e);
92  		}
93  	}
94  
95  	/**
96  	 * Create an in-memory credential vault, secured by the supplied password
97  	 *
98  	 * @param keyStorePassword
99  	 *            Credential to access vault
100 	 * @throws CredentialVaultException
101 	 *             If the underlying keystore had issues.
102 	 */
103 	public GenericCredentialVault(Properties properties, String keyStorePassword) throws CredentialVaultException {
104 		this(properties);
105 		setKeyStorePassword(keyStorePassword);
106 		try {
107 			keyStore.load(null, keyStorePassword.toCharArray());
108 		} catch (IOException e) {
109 			throw new CredentialVaultException("Unable to create Java Keystore", e);
110 		} catch (NoSuchAlgorithmException e) {
111 			throw new CredentialVaultException("Unable to create Java Keystore", e);
112 		} catch (CertificateException e) {
113 			throw new CredentialVaultException("Unable to create Java Keystore", e);
114 		}
115 		log();
116 	}
117 
118 	public boolean isTrustedCertificate(X509Certificate certificate) throws CredentialVaultException {
119 		try {
120 			return keyStore.getCertificateAlias(certificate) != null;
121 		} catch (KeyStoreException e) {
122 			throw new CredentialVaultException("Unable to query underlying keystore", e);
123 		}
124 	}
125 
126 	/**
127 	 * @return The current system credential pair (certificate + private key) or
128 	 *         <code>null</code> if none has been associated with this
129 	 *         credential vault.
130 	 */
131 	public CredentialPair getSystemCredentialPair() throws CredentialVaultException {
132 		return getCredentialPairByAlias(ALIAS_SYSTEM);
133 	}
134 
135 	protected CredentialPair getCredentialPairByAlias(String alias) throws CredentialVaultException {
136 		try {
137 			if (!keyStore.isKeyEntry(alias))
138 				return null; // NOPMD
139 		} catch (KeyStoreException e) {
140 			throw new CredentialVaultException("Unable to query underlying keystore", e);
141 		}
142 
143 		try {
144 			return new CredentialPair(
145 					(X509Certificate) keyStore.getCertificate(alias),
146 					(PrivateKey) keyStore.getKey(alias, keyStorePassword.toCharArray())
147 			);
148 		} catch (NoSuchAlgorithmException e) {
149 			throw new CredentialVaultException("Problem accessing system certificate", e);
150 		} catch (UnrecoverableKeyException e) {
151 			throw new CredentialVaultException("Problem accessing system certificate", e);
152 		} catch (KeyStoreException e) {
153 			throw new CredentialVaultException("Problem accessing system certificate", e);
154 		}
155 
156 	}
157 
158 	/**
159 	 * Install the system certificate + private key pair
160 	 *
161 	 * @param credentialPair
162 	 *            Certificate and corresponding private key
163 	 * @throws CredentialVaultException
164 	 *             If an internal keystore error occurs
165 	 */
166 	public void setSystemCredentialPair(CredentialPair credentialPair) throws CredentialVaultException {
167 		setCredentialPairByAlias(credentialPair, ALIAS_SYSTEM);
168 	}
169 
170 	protected void setCredentialPairByAlias(CredentialPair credentialPair, String alias) throws CredentialVaultException {
171 		try {
172 			keyStore.setKeyEntry(alias, credentialPair.getPrivateKey(), keyStorePassword.toCharArray(), new Certificate[] { credentialPair.getCertificate() });
173 		} catch (KeyStoreException e) {
174 			throw new CredentialVaultException("Unable to install System certificate", e);
175 		}
176 	}
177 
178 	/**
179 	 * Install the system certificate + private key pair from a pkcs12 file
180 	 *
181 	 * @param pkcs12file
182 	 *            Certificate and corresponding private key
183 	 * @param password
184 	 *            Used to unlock the private key
185 	 * @throws CredentialVaultException
186 	 *             If an internal keystore error occurs
187 	 */
188 	public void setSystemCredentialPair(InputStream pkcs12file, String password) throws CredentialVaultException {
189 		CredentialPair systemCredentialPair = loadKeyPairFromPKCS12(pkcs12file, password);
190 		setSystemCredentialPair(systemCredentialPair);
191 	}
192 
193 	/**
194 	 * Add the supplied certificate under the supplied alias
195 	 *
196 	 * @param certificate
197 	 *            The certificate to add
198 	 * @param alias
199 	 *            The alias under which to add the certificate
200 	 * @throws CredentialVaultException
201 	 *             If an internal keystore error occurs
202 	 */
203 	public void addTrustedCertificate(X509Certificate certificate, String alias) throws CredentialVaultException {
204 		try {
205 			keyStore.setCertificateEntry(alias, certificate);
206 		} catch (KeyStoreException e) {
207 			throw new CredentialVaultException("Unable to install trusted certificate", e);
208 		}
209 	}
210 
211 	/**
212 	 * Remove the certificate specified by alias
213 	 *
214 	 * @param alias
215 	 * @throws CredentialVaultException
216 	 *             If alias is not a certificate, does not exist or an internal
217 	 *             keystore error occurs
218 	 */
219 	public void removeTrustedCertificate(String alias) throws CredentialVaultException {
220 		try {
221 			if (!keyStore.isCertificateEntry(alias)) {
222 				throw new CredentialVaultException("The supplied alias '" + alias + "' is not a certificate entry");
223 			}
224 		} catch (KeyStoreException e) {
225 			throw new CredentialVaultException("Internal error accessing keystore", e);
226 		}
227 
228 		try {
229 			keyStore.deleteEntry(alias);
230 		} catch (KeyStoreException e) {
231 			throw new CredentialVaultException("Unable to install certificate", e);
232 		}
233 	}
234 
235 	/**
236 	 * Get the trusted certificate specified by the alias
237 	 *
238 	 * @param alias
239 	 *            Alias that points to certificate
240 	 * @return X509Certificate
241 	 * @throws CredentialVaultException
242 	 *             If no such certificate exists or an internal error occured.
243 	 */
244 	public X509Certificate getTrustedCertificate(String alias) throws CredentialVaultException {
245 		try {
246 			return (X509Certificate) keyStore.getCertificate(alias);
247 		} catch (KeyStoreException e) {
248 			throw new CredentialVaultException("Unable to access certificate under alias '" + alias + "'");
249 		}
250 	}
251 
252 	/**
253 	 * @return the underlying keystore
254 	 */
255 	public KeyStore getKeyStore() {
256 
257 		return keyStore;
258 	}
259 
260 	/**
261 	 * Import system certificate and private key from a PKCS12 keystore.
262 	 *
263 	 * @param pkcs12file
264 	 *            The PKCS12 (.pfx, .p12) file to import
265 	 * @param password
266 	 *            Password to access PKCS12 file
267 	 * @throws CredentialVaultException
268 	 *             If import failed.
269 	 */
270 	public void setSystemCredentialPair(File pkcs12file, String password) throws CredentialVaultException {
271 
272 		CredentialPair systemCredentialPair = loadKeyPairFromPKCS12(pkcs12file, password);
273 		setSystemCredentialPair(systemCredentialPair);
274 	}
275 
276 	/**
277 	 * Adds a trusted certificate based on an Alias and a X509 encoded file.
278 	 *
279 	 * @param x509file
280 	 * @param alias
281 	 * @throws CredentialVaultException
282 	 */
283 	public void addTrustedCertificate(File x509file, String alias) throws CredentialVaultException {
284 
285 		X509Certificate certificate = (X509Certificate) loadCertificateFromX509(x509file);
286 		addTrustedCertificate(certificate, alias);
287 	}
288 
289 	/** --- Private parts --- */
290 
291 	protected void setKeyStorePassword(String keyStorePassword) {
292 
293 		this.keyStorePassword = keyStorePassword;
294 	}
295 
296 	protected CredentialPair loadKeyPairFromPKCS12(InputStream pkcs12instream, String password) throws CredentialVaultException {
297 		KeyStore pkcs12KeyStore;
298 		CredentialPair credentialPair = null;
299 
300 		try {
301 			String provider = SignatureUtil.getCryptoProvider(properties,SOSIFactory.PROPERTYNAME_SOSI_CRYPTOPROVIDER_PKCS12);
302 			pkcs12KeyStore = KeyStore.getInstance("PKCS12",provider);
303 
304 			pkcs12KeyStore.load(pkcs12instream, password.toCharArray());
305 			pkcs12instream.close();
306 		} catch (KeyStoreException e) {
307 			throw new CredentialVaultException("Unable to load PKCS12 file " + pkcs12instream, e);
308 		} catch (IOException e) {
309 			throw new CredentialVaultException("Unable to load PKCS12 file " + pkcs12instream, e);
310 		} catch (NoSuchAlgorithmException e) {
311 			throw new CredentialVaultException("Unable to load PKCS12 file " + pkcs12instream, e);
312 		} catch (CertificateException e) {
313 			throw new CredentialVaultException("Unable to load PKCS12 file " + pkcs12instream, e);
314 		} catch (NoSuchProviderException e) {
315 			throw new CredentialVaultException("No Such Provider", e);
316 		}
317 
318 		// The BouncyCastle provider does not allow for testing the alias type
319 		// via the isXXX methods
320 		// of the KeyStore, but uses the same alias for Certificate and
321 		// PrivateKey when type is PKCS12
322 		try {
323 			Enumeration<String> aliases = pkcs12KeyStore.aliases();
324 			while (aliases.hasMoreElements()) {
325 				String alias = aliases.nextElement();
326 				if (pkcs12KeyStore.isKeyEntry(alias)) {
327 					X509Certificate cert = (X509Certificate) pkcs12KeyStore.getCertificate(alias);
328 					PrivateKey key = (PrivateKey) pkcs12KeyStore.getKey(alias, password.toCharArray());
329 					credentialPair = new CredentialPair(cert, key);
330 				}
331 			}
332 		} catch (KeyStoreException e) {
333 			throw new CredentialVaultException("Unable to get private key or certificate from PKCS12 keystore", e);
334 		} catch (NoSuchAlgorithmException e) {
335 			throw new CredentialVaultException("Unable to get private key or certificate from PKCS12 keystore", e);
336 		} catch (UnrecoverableKeyException e) {
337 			throw new CredentialVaultException("Unable to get private key or certificate from PKCS12 keystore", e);
338 		}
339 
340 		return credentialPair;
341 	}
342 
343 	/**
344 	 * Read certificate and private key from a PKCS12 keystore. This method will
345 	 * expect a keystore with a single certificate and a single private key.
346 	 * Trusted certificate chains will be ignored.
347 	 *
348 	 * @param pkcs12file
349 	 *            The PKCS12 (.pfx, .p12) file to import
350 	 * @param password
351 	 *            Password to access PKCS12 file
352 	 * @return CredentialPair containing Certificate and Private Key
353 	 * @throws CredentialVaultException
354 	 *             If import failed.
355 	 */
356 	protected CredentialPair loadKeyPairFromPKCS12(File pkcs12file, String password) throws CredentialVaultException {
357 		FileInputStream fileInputStream;
358 		try {
359 			fileInputStream = new FileInputStream(pkcs12file);
360 		} catch (FileNotFoundException e) {
361 			throw new CredentialVaultException("Unable to load pkcs12 keystore from file " + pkcs12file, e);
362 		}
363 		return loadKeyPairFromPKCS12(fileInputStream, password);
364 	}
365 
366 	/**
367 	 * Load an x509 certificate from file (DER encoded binary or Base 64)
368 	 *
369 	 * @param x509file
370 	 *            The x509 certificate to load
371 	 * @return A Java Certificate
372 	 * @throws CredentialVaultException
373 	 *             If certificate could not be loaded
374 	 */
375 	protected Certificate loadCertificateFromX509(File x509file) throws CredentialVaultException {
376 
377 		Certificate cert;
378 		try {
379 			FileInputStream fileInputStream = new FileInputStream(x509file);
380 			CertificateFactory cf = CertificateFactory.getInstance("X.509");
381 			cert = cf.generateCertificate(fileInputStream);
382 		} catch (FileNotFoundException e) {
383 			throw new CredentialVaultException("Unable to load certificate file", e);
384 		} catch (CertificateException e) {
385 			throw new CredentialVaultException("Unable to load certificate file", e);
386 		}
387 		return cert;
388 	}
389 }