/* * Copyright (c) 2016 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.security; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.UnrecoverableKeyException; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.validation.constraints.NotNull; import org.obiba.crypt.CacheablePasswordCallback; import org.obiba.crypt.CachingCallbackHandler; import org.obiba.crypt.KeyProviderSecurityException; /** * Persist the keystores in files. */ public class KeyStoreRepository { private CallbackHandler callbackHandler; private File keyStoresDirectory; public void setCallbackHandler(CallbackHandler callbackHandler) { this.callbackHandler = callbackHandler; } /** * The directory where the keystore files will be written. * @param keyStoresDirectory */ public void setKeyStoresDirectory(File keyStoresDirectory) { this.keyStoresDirectory = keyStoresDirectory; keyStoresDirectory.mkdirs(); } public void saveKeyStore(@NotNull KeyStoreManager keyStore) { try { Path keyStorePath = getKeystoreFile(keyStore.getName()).toPath(); Files.write(keyStorePath, getKeyStoreByteArray(keyStore), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } catch(IOException e) { throw new RuntimeException(e); } } public KeyStoreManager getOrCreateKeyStore(@NotNull String name) { KeyStoreManager keyStore = getKeyStore(name); if(keyStore == null) { keyStore = KeyStoreManager.Builder.newStore().name(name).passwordPrompt(callbackHandler).build(); saveKeyStore(keyStore); } return keyStore; } private KeyStoreManager getKeyStore(@NotNull String name) { File keyStoreFile = getKeystoreFile(name); return keyStoreFile.exists() ? loadKeyStore(name, getKeystoreFile(name)) : null; } private File getKeystoreFile(@NotNull String name) { return new File(keyStoresDirectory, name); } private KeyStoreManager loadKeyStore(@NotNull String name, @NotNull File keyStoreFile) { CacheablePasswordCallback passwordCallback = CacheablePasswordCallback.Builder.newCallback().key(name) .prompt(getPasswordFor(name)).build(); KeyStoreManager keyStore = null; try { keyStore = new KeyStoreManager(name, loadKeyStore(Files.readAllBytes(keyStoreFile.toPath()), passwordCallback)); keyStore.setCallbackHandler(callbackHandler); KeyStoreManager.loadBouncyCastle(); } catch(GeneralSecurityException | UnsupportedCallbackException ex) { throw new RuntimeException(ex); } catch(IOException ex) { clearPasswordCache(callbackHandler, name); translateAndRethrowKeyStoreIOException(ex); } return keyStore; } private KeyStore loadKeyStore(byte[] keyStoreBytes, CacheablePasswordCallback passwordCallback) throws GeneralSecurityException, UnsupportedCallbackException, IOException { KeyStore ks = KeyStore.getInstance("JCEKS"); ks.load(new ByteArrayInputStream(keyStoreBytes), getKeyPassword(passwordCallback)); return ks; } private char[] getKeyPassword(CacheablePasswordCallback passwordCallback) throws UnsupportedCallbackException, IOException { callbackHandler.handle(new CacheablePasswordCallback[] { passwordCallback }); return passwordCallback.getPassword(); } private byte[] getKeyStoreByteArray(KeyStoreManager keyStore) { String name = keyStore.getName(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { CacheablePasswordCallback passwordCallback = CacheablePasswordCallback.Builder.newCallback().key(name) .prompt(getPasswordFor(name)).build(); keyStore.getKeyStore().store(outputStream, getKeyPassword(passwordCallback)); } catch(KeyStoreException e) { clearPasswordCache(callbackHandler, keyStore.getName()); throw new KeyProviderSecurityException("Wrong keystore password or keystore was tampered with"); } catch(GeneralSecurityException | UnsupportedCallbackException e) { throw new RuntimeException(e); } catch(IOException ex) { clearPasswordCache(callbackHandler, name); translateAndRethrowKeyStoreIOException(ex); } return outputStream.toByteArray(); } /** * Returns "Password for 'name': ". */ private String getPasswordFor(String name) { return KeyStoreManager.PASSWORD_FOR + " '" + name + "': "; } private static void clearPasswordCache(CallbackHandler callbackHandler, String passwordKey) { if(callbackHandler instanceof CachingCallbackHandler) { ((CachingCallbackHandler) callbackHandler).clearPasswordCache(passwordKey); } } private static void translateAndRethrowKeyStoreIOException(IOException ex) { if(ex.getCause() != null && ex.getCause() instanceof UnrecoverableKeyException) { throw new KeyProviderSecurityException("Wrong keystore password"); } throw new RuntimeException(ex); } }