| /* |
| * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package com.sun.enterprise.security.store; |
| |
| import static com.sun.enterprise.util.SystemPropertyConstants.INSTANCE_ROOT_PROPERTY; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.security.Key; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.UnrecoverableKeyException; |
| import java.security.cert.CertificateException; |
| import java.util.Enumeration; |
| |
| import javax.crypto.SecretKey; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| /** |
| * This class implements an adapter for password manipulation a JCEKS. Note that although it uses locks ('synchronized'), it |
| * tends to be created anew with each use, an inefficient and potentially problematic use that could create more than one |
| * instance accessing the same keystore at a time. |
| */ |
| public final class PasswordAdapter { |
| public static final String PASSWORD_ALIAS_KEYSTORE = "domain-passwords"; |
| |
| private KeyStore _pwdStore; |
| private final File _keyFile; |
| private char[] _masterPassword; |
| |
| private char[] getMasterPassword() { |
| return _masterPassword; |
| } |
| |
| private static String getDefaultKeyFileName() { |
| return System.getProperty(INSTANCE_ROOT_PROPERTY) + File.separator + "config" + File.separator + PASSWORD_ALIAS_KEYSTORE; |
| } |
| |
| /** |
| * Construct a PasswordAdapter with given Shared Master Password, SMP using the default keyfile (domain-passwords.jceks) |
| * |
| * @param smp master password |
| * @throws CertificateException |
| * @throws IOException |
| * @throws KeyStoreException |
| * @throws NoSuchAlgorithmException |
| */ |
| public PasswordAdapter(char[] masterPassword) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| this(getDefaultKeyFileName(), masterPassword); |
| } |
| |
| /** |
| * Construct a PasswordAdapter with given Shared Master Password, SMP. |
| * |
| * @param keyfileName the jceks key file name |
| * @param smp master password |
| * @throws CertificateException |
| * @throws IOException |
| * @throws KeyStoreException |
| * @throws NoSuchAlgorithmException |
| */ |
| public PasswordAdapter(final String keyStoreFileName, final char[] masterPassword) |
| throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| final File keyStoreFile = new File(keyStoreFileName); |
| |
| _pwdStore = loadKeyStore(keyStoreFile, masterPassword); |
| |
| // assign these only once the store is good; no need to keep copies otherwise! |
| _keyFile = keyStoreFile; |
| _masterPassword = masterPassword; |
| |
| //debugState( "PasswordAdapter constructor" ); |
| } |
| |
| /** |
| * Construct a PasswordAdapter with given Shared Master Password, SMP. |
| * |
| * @param keyfileName the jceks key file name |
| * @param smp the master password |
| * @exception CertificateException |
| * @exception IOException |
| * @exception KeyStoreException |
| * @exception NoSuchAlgorithmException |
| */ |
| private static KeyStore loadKeyStore(final File keyStoreFile, final char[] masterPassword) |
| throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { |
| final KeyStore keyStore = KeyStore.getInstance("JCEKS"); |
| |
| if (keyStoreFile.exists()) { |
| // don't buffer keystore; it's tiny anyway |
| final FileInputStream input = new FileInputStream(keyStoreFile); |
| try (input) { |
| keyStore.load(input, masterPassword); |
| } |
| } else { |
| keyStore.load(null, masterPassword); |
| } |
| |
| return keyStore; |
| } |
| |
| /** |
| * This methods returns password String for a given alias and SMP. |
| * |
| * @param alias |
| * @return corresponding password or null if the alias does not exist. |
| * @exception KeyStoreException |
| * @exception NoSuchAlgorithmException |
| * @exception UnrecoverableKeyException |
| */ |
| public synchronized String getPasswordForAlias(final String alias) |
| throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
| String passwordString = null; |
| |
| final Key key = _pwdStore.getKey(alias, getMasterPassword()); |
| if (key != null) { |
| passwordString = new String(key.getEncoded()); |
| } |
| |
| return passwordString; |
| } |
| |
| /** |
| * This methods returns password SecretKey for a given alias and SMP. |
| * |
| * @param alias |
| * @return corresponding password SecretKey or null if the alias does not exist. |
| * @exception KeyStoreException |
| * @exception NoSuchAlgorithmException |
| * @exception UnrecoverableKeyException |
| */ |
| public synchronized SecretKey getPasswordSecretKeyForAlias(String alias) |
| throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
| |
| return (SecretKey) _pwdStore.getKey(alias, getMasterPassword()); |
| } |
| |
| /** |
| * See if the given alias exists |
| * |
| * @param alias the alias name |
| * @return true if the alias exists in the keystore |
| */ |
| public synchronized boolean aliasExists(final String alias) throws KeyStoreException { |
| return _pwdStore.containsAlias(alias); |
| } |
| |
| /** |
| * Remove an alias from the keystore |
| * |
| * @param alias The name of the alias to remove |
| * @throws KeyStoreException |
| * @throws IOException |
| * @throws NoSuchAlgorithmException |
| * @throws CertificateException |
| */ |
| public synchronized void removeAlias(final String alias) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
| _pwdStore.deleteEntry(alias); |
| writeStore(); |
| } |
| |
| /** |
| * Return the aliases from the keystore. |
| * |
| * @return An enumeration containing all the aliases in the keystore. |
| */ |
| public synchronized Enumeration<String> getAliases() throws KeyStoreException { |
| return _pwdStore.aliases(); |
| } |
| |
| /** |
| * Writes the keystore to disk |
| * |
| * @throws KeyStoreException |
| * @throws IOException |
| * @throws NoSuchAlgorithmException |
| * @throws CertificateException |
| */ |
| public void writeStore() |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
| writeKeyStoreSafe(getMasterPassword()); |
| } |
| |
| /** |
| * This methods set alias, secretKey into JCEKS keystore. |
| * |
| * @param alias |
| * @param secretKey |
| * @exception CertificateException |
| * @exception IOException |
| * @exception KeyStoreException |
| * @exception NoSuchAlgorithmException |
| */ |
| public synchronized void setPasswordForAlias(final String alias, final byte[] keyBytes) |
| throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { |
| final Key key = new SecretKeySpec(keyBytes, "AES"); |
| _pwdStore.setKeyEntry(alias, key, getMasterPassword(), null); |
| writeStore(); |
| } |
| |
| /** |
| * Make a new in-memory KeyStore with all the keys secured with the new master password. |
| */ |
| private KeyStore duplicateKeyStore(final char[] newMasterPassword) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
| final char[] oldMasterPassword = getMasterPassword(); |
| |
| final KeyStore oldStore = _pwdStore; |
| final KeyStore newKeyStore = KeyStore.getInstance("JCEKS", _pwdStore.getProvider()); |
| newKeyStore.load(null, newMasterPassword); |
| |
| final Enumeration<String> aliasesEnum = oldStore.aliases(); |
| while (aliasesEnum.hasMoreElements()) { |
| final String alias = aliasesEnum.nextElement(); |
| |
| if (!oldStore.isKeyEntry(alias)) { |
| throw new IllegalArgumentException("Expecting keys only"); |
| } |
| |
| final Key key = oldStore.getKey(alias, oldMasterPassword); |
| newKeyStore.setKeyEntry(alias, key, newMasterPassword, null); |
| } |
| |
| return newKeyStore; |
| } |
| |
| /** |
| * Write the KeyStore to disk. Calling code should protect against overwriting any original file. |
| * |
| * @param keyStore |
| * @param file |
| * @param masterPassword |
| */ |
| private static void writeKeyStoreToFile(final KeyStore keyStore, final File file, final char[] masterPassword) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { |
| final FileOutputStream out = new FileOutputStream(file); |
| try (out) { |
| keyStore.store(out, masterPassword); |
| } |
| } |
| |
| /** |
| * Writes the current KeyStore to disk in a manner that preserves its on-disk representation from being destroyed if something |
| * goes wrong; a temporary file is used. |
| */ |
| private synchronized void writeKeyStoreSafe(final char[] masterPassword) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
| final boolean keystoreExists = _keyFile.exists(); |
| |
| // if the KeyStore exists, update it in a manner that doesn't destroy |
| // the existing store if a failure occurs. |
| if (keystoreExists) { |
| final KeyStore newKeyStore = duplicateKeyStore(masterPassword); |
| |
| // 'newKeyStore' is now complete; rename the old KeyStore, the write the new one in its place |
| final File saveOld = new File(_keyFile.toString() + ".save"); |
| |
| if (!_keyFile.renameTo(saveOld)) { |
| throw new IOException("Can't rename " + _keyFile + " to " + saveOld); |
| } |
| |
| try { |
| writeKeyStoreToFile(newKeyStore, _keyFile, masterPassword); |
| _pwdStore = newKeyStore; |
| _masterPassword = masterPassword; |
| } catch (final Throwable t) { |
| try { |
| if (!saveOld.renameTo(_keyFile)) { |
| throw new RuntimeException("Could not write new KeyStore, and " + "cannot restore KeyStore to original state", t); |
| } |
| } catch (final Throwable tt) { |
| /* best effort failed */ |
| throw new RuntimeException("Could not write new KeyStore, and " + "cannot restore KeyStore to original state", tt); |
| } |
| |
| throw new RuntimeException("Can't write new KeyStore", t); |
| } |
| |
| try { |
| if (!saveOld.delete()) { |
| throw new RuntimeException("Can't remove old KeyStore \"" + _keyFile + "\""); |
| } |
| } catch (Throwable t) { |
| throw new RuntimeException("Can't remove old KeyStore \"" + _keyFile + "\"", t); |
| } |
| } else { |
| writeKeyStoreToFile(_pwdStore, _keyFile, masterPassword); |
| } |
| |
| loadKeyStore(_keyFile, getMasterPassword()); |
| } |
| |
| /** |
| * Changes the keystore password, including the encoding of the keys within it. |
| * <p> |
| * There are several error conditions that could occur: |
| * <ul> |
| * <li>Problem extracting existing alias keys with new ones.</li> |
| * <li>Problem writing the keystore, including destroying it if an I/O problem occurs.</li> |
| * <li></li> |
| * </ul> |
| * For these reasons, make a new KeyStore and write it, then swap it with the old one. |
| * |
| * @param newpassword the new keystore password |
| * @throws KeyStoreException |
| * @throws IOException |
| * @throws NoSuchAlgorithmException |
| * @throws CertificateException |
| */ |
| public synchronized void changePassword(char[] newMasterPassword) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
| |
| writeKeyStoreSafe(newMasterPassword); |
| } |
| } |