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/tool/SealCommands.java $
27   * $Id: SealCommands.java 9462 2011-12-07 12:59:18Z chg@lakeside.dk $
28   */
29  package dk.sosi.seal.tool;
30  
31  import dk.sosi.seal.ssl.HttpsConnector;
32  import dk.sosi.seal.vault.*;
33  import dk.sosi.seal.vault.renewal.CredentialPairRenewer;
34  
35  import java.io.*;
36  import java.security.KeyStore;
37  import java.security.KeyStoreException;
38  import java.security.NoSuchAlgorithmException;
39  import java.security.cert.CertificateException;
40  import java.security.cert.X509Certificate;
41  import java.util.Enumeration;
42  import java.util.Properties;
43  import java.util.jar.*;
44  
45  
46  /**
47   * The core operations of the Seal tool. This is where the actual grunt work is
48   * done, importing certificates, handling .jar content, etc.
49   * 
50   * @author kkj
51   * @author $LastChangedBy: chg@lakeside.dk $
52   * @since 1.0
53   */
54  public class SealCommands { // NOPMD
55  	private static final String KEYSTORE_FILENAME = "SealKeystore.jks";
56  
57  	private CredentialPairRenewer credentialPairRenewer;
58  	private HttpsConnector httpsConnector;
59  	private Properties properties;
60  	
61  	public SealCommands(Properties properties) {
62  		this.properties = properties;
63  	}
64  
65  	/**
66  	 * Import the supplied certificate (.cer) into keystore inside .jar file.
67  	 * 
68  	 * @param vaultPath
69  	 *            The path to the vault.jar file into which certificate will be
70  	 *            imported
71  	 * @param certPath
72  	 *            The path to the certificate file to import
73  	 * @param keystorePassword
74  	 *            The password to the embedded keystore
75  	 * @param alias
76  	 *            The alias under which to store the certificate
77  	 * @param createVault
78  	 *            If true, a new vault will be created if vaultPath does not
79  	 *            exist
80  	 */
81  	public void importCertificate(File vaultPath, File certPath, String keystorePassword, String alias, boolean createVault) throws SealToolException {
82  
83  		if (!vaultPath.exists()) {
84  			if (createVault) {
85  				System.out.println("The supplied vault file " + vaultPath + " does not exist. Attempts to create it");
86  				initKeystore(vaultPath, keystorePassword);
87  			} else {
88  				throw new SealToolException("The supplied vault file " + vaultPath + " does not exist.");
89  			}
90  		}
91  
92  		KeyStore keyStore = getKeyStore(vaultPath, keystorePassword);
93  		GenericCredentialVault genericCredentialVault = new GenericCredentialVault(properties, keyStore, keystorePassword);
94  		genericCredentialVault.addTrustedCertificate(certPath, alias);
95  		saveKeystore(keyStore, vaultPath, keystorePassword);
96  	}
97  
98  	/**
99  	 * Import pkcs12 keystore pcks12Path secured by pkcs12Password into the
100 	 * vault.jar specified by vaultPath and secured by keystorePassword
101 	 * 
102 	 * @param vaultPath
103 	 *            The full path to the vault.jar
104 	 * @param keystorePassword
105 	 *            The password that protects the keystore inside vaultPath
106 	 * @param pkcs12Path
107 	 *            The full path to the pkcs12 keystore
108 	 * @param pkcs12Password
109 	 *            The password to the pkcs12 keystore
110 	 */
111 	public void importPkcs12Keystore(File vaultPath, String keystorePassword, File pkcs12Path, String pkcs12Password) {
112 
113 		if (!vaultPath.exists()) {
114 			System.out.println("The supplied vault file " + vaultPath + " does not exist. Attempts to create it");
115 			initKeystore(vaultPath, keystorePassword);
116 		}
117 
118 		KeyStore keyStore = getKeyStore(vaultPath, keystorePassword);
119 		GenericCredentialVault genericCredentialVault = new GenericCredentialVault(properties, keyStore, keystorePassword);
120 		genericCredentialVault.setSystemCredentialPair(pkcs12Path, pkcs12Password);
121 		saveKeystore(keyStore, vaultPath, keystorePassword);
122 	}
123 
124 	/**
125 	 * Issue a set of credentials based on TDC installation code and reference number, and store certificate and
126 	 * private key into vault.path
127 	 * 
128 	 * @param vaultPath
129 	 *            The full path to the vault.jar
130 	 * @param keystorePassword
131 	 *            The password that protects the keystore inside vaultPath
132 	 * @param referenceNumber
133 	 * 			  The referencenumber from TDC
134 	 * @param installationCode
135 	 * 			  The installationCode from TDC
136 	 * @param issueTestCertificates
137 	 * 			  Pass <code>true</code> to issue a TDC test certificate. 
138 	 *            Pass <code>false</code> to issue a production certificate.
139      * @deprecated
140      *            Works only for OCES1 certificates, will be removed from Seal when OCES1 reaches end-of-life
141 	 */
142 	@Deprecated
143     public void issueToVault(File vaultPath, String keystorePassword, String referenceNumber, String installationCode, boolean issueTestCertificates) {
144 		if (!vaultPath.exists()) {
145 			System.out.println("The supplied vault file " + vaultPath + " does not exist. Attempts to create it");
146 			initKeystore(vaultPath, keystorePassword);
147 		}
148 		KeyStore keyStore = getKeyStore(vaultPath, keystorePassword);
149 		GenericCredentialVault genericCredentialVault = new GenericCredentialVault(properties, keyStore, keystorePassword);
150 
151 		TDCCredentialPairIssuer issuer = new TDCCredentialPairIssuer(properties);
152 		if(this.httpsConnector != null) {
153 			issuer.setHttpsConnector(httpsConnector);
154 		}
155 		CredentialPair pair = issuer.issue(referenceNumber, installationCode, issueTestCertificates);
156 
157 		genericCredentialVault.setSystemCredentialPair(pair);
158 		saveKeystore(keyStore, vaultPath, keystorePassword);
159 	}
160 	
161 	/**
162 	 * Remove the alias from the vault in vaultPath.
163 	 * 
164 	 * @param vaultPath
165 	 *            The full path to the vault.jar
166 	 * @param keystorePassword
167 	 *            The password that protects the vault
168 	 * @param alias
169 	 *            The alias to remove
170 	 * @throws SealToolException
171 	 *             If the alias does not exist or vault could not be loaded
172 	 */
173 	public void removeAlias(File vaultPath, String keystorePassword, String alias) throws SealToolException {
174 
175 		if (!vaultPath.exists()) {
176 			System.out.println("The supplied vault file " + vaultPath + " does not exist. Attempts to create it");
177 			initKeystore(vaultPath, keystorePassword);
178 		}
179 		KeyStore keyStore = getKeyStore(vaultPath, keystorePassword);
180 		try {
181 			if (!keyStore.containsAlias(alias)) {
182 				throw new SealToolException("No such alias in keystore '" + alias + "'");
183 			}
184 			keyStore.deleteEntry(alias);
185 		} catch (KeyStoreException e) {
186 			throw new SealToolException("Unable to delete alias " + alias, e);
187 		}
188 		saveKeystore(keyStore, vaultPath, keystorePassword);
189 	}
190 
191 	/**
192 	 * List the content of the vault in vaultPath secured by password
193 	 * 
194 	 * @param vaultPath
195 	 *            The full path to the vault.jar
196 	 * @param password
197 	 *            The password that protects the keystore inside
198 	 * @throws SealToolException
199 	 *             If listing content failed.
200 	 */
201 	public void list(File vaultPath, String password) throws SealToolException {
202 		KeyStore keyStore = getKeyStore(vaultPath, password);
203 		list(keyStore);
204 	}
205 
206 	/**
207 	 * List the content of the keystore in keyStorePath secured by password
208 	 * @param keyStorePath
209 	 * 		  	The full path to the keystore.jks
210 	 * @param password
211 	 *          The password that protects the keystore inside
212 	 * @throws SealToolException
213 	 *          If listing content failed.
214 	 */
215 	public void list(String keyStorePath, String password) throws SealToolException {
216 		KeyStore keyStore = getKeyStore(keyStorePath, password);
217 		list(keyStore);
218 	}
219 
220 	
221 	public void setCredentialPairRenewer(CredentialPairRenewer renewer) {
222 		this.credentialPairRenewer = renewer;
223 	}
224 	
225 	public void setHttpsConnector(HttpsConnector httpsConnector) {
226 		this.httpsConnector = httpsConnector;
227 	}
228 
229 	/**
230 	 * Renew the system credentials stored in a Java keystore.
231 	 * 
232 	 * @param keyStoreFile
233 	 * 				Full path to the Java keystore.
234 	 * @param keyStorePassword
235 	 * 				Password protecting the keystore and private keys
236      * @deprecated
237      *            Works only for OCES1 certificates, will be removed from Seal when OCES1 reaches end-of-life
238 	 */
239 	@Deprecated
240     public void renewSystemCredentials(File keyStoreFile, String keyStorePassword) throws SealToolException {
241 		RenewableFileBasedCredentialVault vault = new RenewableFileBasedCredentialVault(properties, keyStoreFile, keyStorePassword);
242 		if(this.credentialPairRenewer != null) {
243 			vault.setCredentialPairRenewer(credentialPairRenewer);
244 		}
245 		
246 		if(!vault.isRenewalChargeable() || userAcceptsChargeableRenewal()) {
247 			vault.renewSystemCredentials();
248 		}
249 	}
250 
251 
252 	/**
253 	 * Renew the system credentials stored in vault.jar.
254 	 * 
255 	 * @param vaultPath
256 	 *            The full path to the vault.jar
257 	 * @param password
258 	 *            The password that protects the keystore inside
259      * @deprecated
260      *            Works only for OCES1 certificates, will be removed from Seal when OCES1 reaches end-of-life
261 	 */
262 	@Deprecated
263     public void renewVaultedSystemCredentials(File vaultPath, String password) throws SealToolException {
264 		
265 		if (!vaultPath.exists()) {
266 			throw new SealToolException("The supplied vault file " + vaultPath + " does not exist.");
267 		}
268 		KeyStore keyStore = getKeyStore(vaultPath, password);
269 		ArchivableCredentialVault vault = new ArchivableCredentialVault(properties, keyStore, password);
270 		
271 		CredentialPairRenewer renewer = getRenewer(vault.getSystemCredentialPair().getCertificate());
272 
273 		if(!renewer.isRenewalChargeable(vault.getSystemCredentialPair().getCertificate()) || userAcceptsChargeableRenewal()) {
274 			CredentialPair renewedPair = renewer.renew(vault.getSystemCredentialPair());
275 			vault.archiveSystemCredentials(renewedPair);
276 			keyStore = vault.getKeyStore();
277 			saveKeystore(keyStore, vaultPath, password);
278 		}
279 	}
280 	
281 	
282 
283 	// -- Private parts
284 
285 	private CredentialPairRenewer getRenewer(X509Certificate certificate) {
286 		if(this.credentialPairRenewer != null) {
287 			return this.credentialPairRenewer; //NOPMD
288 		} else {
289 			return CredentialPairRenewer.createInstance(certificate, properties);
290 		}
291 	}
292 
293 	private boolean userAcceptsChargeableRenewal() throws SealToolException {
294 		System.out.println("Renewal of the system certificate will not be free of charge.\n\n" +
295 				"Do you wish to renew the system certificate? (y/n)");
296 		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
297 		try {
298 			String line = reader.readLine();
299 			while(!(line.equals("y") || line.equals("n"))) {
300 				System.out.println("You must reply by typing either \"y\" or \"n\":");
301 				line = reader.readLine();
302 			}
303 			return line.equals("y");
304 		} catch (IOException e) {
305 			throw new SealToolException("Failed to read user input", e);
306 		}
307 	}
308 
309 	
310 	private void list(KeyStore keyStore) throws SealToolException {
311 
312 		Enumeration<String> aliases;
313 
314 		try {
315 			aliases = keyStore.aliases();
316 		} catch (KeyStoreException e) {
317 			throw new SealToolException("Unable to list content of keystore", e);
318 		}
319 
320 		System.out.println("Listing contents:\n");
321 
322 		int count = 1;
323 		while (aliases.hasMoreElements()) {
324 			String alias = aliases.nextElement();
325 			String type;
326 
327 			try {
328 				if (keyStore.isCertificateEntry(alias)) {
329 					type = "trusted certificate";
330 				} else if (keyStore.isKeyEntry(alias)) {
331 					int days = getDaysToExpiry((X509Certificate) keyStore.getCertificate(alias));
332 					type = "private key, " + days + " days to expiry";
333 				} else {
334 					type = "unknown";
335 				}
336 			} catch (KeyStoreException e) {
337 				throw new SealToolException("Unable to read keystore entry " + alias, e);
338 			}
339 
340 			System.out.println(count++ + " : " + alias + " (" + type + ")");
341 		}
342 	}
343 
344 	private int getDaysToExpiry(X509Certificate cert) {
345 		return (int) ((cert.getNotAfter().getTime() - System.currentTimeMillis()) / (24*60*60*1000));
346 		
347 	}
348 	
349 	private KeyStore getKeyStore(File vaultPath, String password) throws SealToolException {
350 
351 		KeyStore keyStore;
352 		try {
353 			keyStore = KeyStore.getInstance("JKS");
354 		} catch (KeyStoreException e) {
355 			throw new SealToolException("Unable to get JKS instance!", e);
356 		}
357 		JarInputStream jarInputStream = getJarInputStream(vaultPath);
358 
359 		try {
360 			JarEntry jarEntry = jarInputStream.getNextJarEntry();
361 			while (jarEntry != null) {
362 				if (KEYSTORE_FILENAME.equals(jarEntry.getName())) {
363 					try {
364 						keyStore.load(jarInputStream, password.toCharArray());
365 					} catch (NoSuchAlgorithmException e) {
366 						throw new SealToolException("Unable to load keystore from .jar file " + vaultPath, e);
367 					} catch (CertificateException e) {
368 						throw new SealToolException("Unable to load keystore from .jar file " + vaultPath, e);
369 					}
370 				}
371 				jarEntry = jarInputStream.getNextJarEntry();
372 			}
373 		} catch (IOException e) {
374 			throw new SealToolException("Unable to read from .jar file " + vaultPath, e);
375 		} finally {
376 			try {
377 				jarInputStream.close();
378 			} catch (IOException e) {
379 				e.printStackTrace();
380 			}
381 		}
382 
383 		return keyStore;
384 	}
385 
386 	private KeyStore getKeyStore(String keyStorePath, String password) throws SealToolException {
387 
388 		KeyStore keyStore;
389 		try {
390 			keyStore = KeyStore.getInstance("JKS");
391 		} catch (KeyStoreException e) {
392 			throw new SealToolException("Unable to get JKS instance!", e);
393 		}
394 
395 		try {
396 			FileInputStream is = new FileInputStream(new File(keyStorePath));
397 			keyStore.load(is, password.toCharArray());
398 			is.close();
399 		} catch (NoSuchAlgorithmException e) {
400 			throw new SealToolException("Unable to load keystore from file " + keyStorePath, e);
401 		} catch (CertificateException e) {
402 			throw new SealToolException("Unable to load keystore from file " + keyStorePath, e);
403 		} catch (IOException e) {
404 			throw new SealToolException("Unable to load keystore from file " + keyStorePath, e);
405 		}
406 
407 		return keyStore;
408 	}
409 
410 	private JarInputStream getJarInputStream(File vaultPath) throws SealToolException {
411 
412 		JarInputStream jarInputStream;
413 
414 		try {
415 			jarInputStream = new JarInputStream(new FileInputStream(vaultPath));
416 		} catch (IOException e) {
417 			throw new SealToolException("Unable to open .jar file " + vaultPath, e);
418 		}
419 
420 		return jarInputStream;
421 	}
422 
423 	private JarOutputStream getJarOutputStream(File vaultPath) throws SealToolException {
424 
425 		JarOutputStream jarOutputStream;
426 		try {
427 			jarOutputStream = new JarOutputStream(new FileOutputStream(vaultPath));
428 		} catch (IOException e) {
429 			throw new SealToolException("Unable to open .jar file " + vaultPath, e);
430 		}
431 
432 		return jarOutputStream;
433 	}
434 
435 	private void saveKeystore(KeyStore keyStore, File vaultPath, String keystorePassword) {
436 
437 		JarOutputStream jos = getJarOutputStream(vaultPath);
438 		JarEntry entry = new JarEntry(ClasspathCredentialVault.KEYSTORE_FILENAME);
439 		try {
440 			jos.putNextEntry(entry);
441 		} catch (IOException e) {
442 			throw new SealToolException("Unable to add jar entry", e);
443 		}
444 
445 		try {
446 			keyStore.store(jos, keystorePassword.toCharArray());
447 		} catch (KeyStoreException e) {
448 			throw new SealToolException("Unable to save keystore back to file", e);
449 		} catch (IOException e) {
450 			throw new SealToolException("Unable to save keystore back to file", e);
451 		} catch (NoSuchAlgorithmException e) {
452 			throw new SealToolException("Unable to save keystore back to file", e);
453 		} catch (CertificateException e) {
454 			throw new SealToolException("Unable to save keystore back to file", e);
455 		} finally {
456 			try {
457 				jos.close();
458 			} catch (IOException e) {
459 				e.printStackTrace();
460 			}
461 		}
462 	}
463 
464 	/**
465 	 * Initialize an empty keystore, protect it with the supplied password, and
466 	 * store the result inside the .jar file specified by vaultPath
467 	 * 
468 	 * @param vaultPath
469 	 *            The full path to the vault.jar
470 	 * @param keystorePassword
471 	 *            Password to the keystore inside
472 	 * @throws SealToolException
473 	 */
474 	private void initKeystore(File vaultPath, String keystorePassword) throws SealToolException {
475 
476 		KeyStore keyStore;
477 		try {
478 			keyStore = KeyStore.getInstance("JKS");
479 			keyStore.load(null, keystorePassword.toCharArray());
480 		} catch (KeyStoreException e) {
481 			throw new SealToolException("Unable to create empty keystore", e);
482 		} catch (IOException e) {
483 			throw new SealToolException("Unable to create empty keystore", e);
484 		} catch (NoSuchAlgorithmException e) {
485 			throw new SealToolException("Unable to create empty keystore", e);
486 		} catch (CertificateException e) {
487 			throw new SealToolException("Unable to create empty keystore", e);
488 		}
489 
490 		JarOutputStream jarOutputStream = null;
491 		try {
492 			Manifest manifest = new Manifest();
493 			Attributes attrCreatedBy = new Attributes();
494 			attrCreatedBy.putValue("Created-By", "SOSI Seal");
495 			manifest.getEntries().put("Created-By", attrCreatedBy);
496 
497 			Attributes attrSpecTitle = new Attributes();
498 			attrSpecTitle.putValue("Specification-Title", "Seal Certificates");
499 			manifest.getEntries().put("Specification-Title", attrSpecTitle);
500 
501 			try {
502 				jarOutputStream = new JarOutputStream(new FileOutputStream(vaultPath, true), manifest);
503 			} catch (IOException e) {
504 				throw new SealToolException("Unable create a jar file for the supplied path " + vaultPath, e);
505 			}
506 
507 			JarEntry jarEntry = new JarEntry(ClasspathCredentialVault.KEYSTORE_FILENAME);
508 			jarEntry.setComment("SOSI Seal Keystore");
509 			try {
510 				jarOutputStream.putNextEntry(jarEntry);
511 			} catch (IOException e) {
512 				throw new SealToolException("Unable to write keystore to jarfile " + vaultPath, e);
513 			}
514 			try {
515 				keyStore.store(jarOutputStream, keystorePassword.toCharArray());
516 			} catch (KeyStoreException e) {
517 				throw new SealToolException("Unable to write keystore to jarfile " + vaultPath, e);
518 			} catch (IOException e) {
519 				throw new SealToolException("Unable to write keystore to jarfile " + vaultPath, e);
520 			} catch (NoSuchAlgorithmException e) {
521 				throw new SealToolException("Unable to write keystore to jarfile " + vaultPath, e);
522 			} catch (CertificateException e) {
523 				throw new SealToolException("Unable to write keystore to jarfile " + vaultPath, e);
524 			}
525 		} finally {
526 			try {
527 				if (jarOutputStream != null)
528 					jarOutputStream.close();
529 			} catch (IOException e) {
530 				e.printStackTrace();
531 			}
532 		}
533 	}
534 
535 }