ServerKeyStore.java
/*
** Module : ServerKeyStore.java
** Abstract : define a container used to export server key store.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------------Description------------------------------------------
** 001 MAG 20131120 First version.
** 002 CA 20140207 If not explicitly specified, build the server's private key from the directory.
** 003 SBI 20151119 Added the trust server alias.
** 004 CA 20160205 Reworked to initialize the external store only once (and share it accross
** copies). If the store is kept in-directory, this will create a store
** encrypted with unique passwords, for each copy.
** 005 SBI 20171116 Added web store with its store type and web store alias.
** 006 IAS 20200914 Re-work (de)serialization
** 007 CA 20210910 Allow a webstore to be initialized from the directory even if the server's store is
** initialized from the file system.
** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 009 GBB 20230825 SecurityManager certificate methods calls updated.
** 010 GBB 20240214 Making method getStore() public.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.main;
import static com.goldencode.util.NativeTypeSerializer.*;
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.logging.*;
import org.apache.commons.codec.digest.DigestUtils;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.security.SecurityManager.*;
import com.goldencode.p2j.util.logging.CentralLogger;
import com.goldencode.util.*;
/**
* A container used to export server key store.
* The server KeyStore is loaded into this structure and serialized (exported)
* via a RemoteObject protocol to the spawned process.
*
* @author mag
*
*/
public class ServerKeyStore
implements Externalizable,
Cloneable
{
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(ServerKeyStore.class.getName());
/**
* A template instance from which copies are made; if the store is in-directory, these copies
* will use unique passwords to encrypt the resulted store. Otherwise, the store file specified
* at the server's bootstrap config will be shared accross copies.
*/
private static ServerKeyStore store;
/** Key store file content */
private byte[] keyStore;
/** Key store password */
private String keyStorePassword;
/** Key store manager password */
private String keyManagerPassword;
/** Authorization token */
private String authorizationToken;
/** Trust server alias */
private String trustServerAlias;
/** Web key store file content */
private byte[] webKeyStore;
/** Web key store password */
private String webKeyStorePassword;
/** Web key store manager password */
private String webKeyManagerPassword;
/** Web server alias */
private String webServerAlias;
/** Worker to resolve the in-directory store. */
private transient EncryptedKeyStoreFunction encryptedKeyStoreWorker;
/** Flag indicating if the server store is read from the directory. */
private boolean fromDirectory = false;
/**
* Constructor. Just creates an empty instance.
*/
public ServerKeyStore()
{
// no-op
}
/**
* Initializes the template {@link #store}. Will be called during server initialization, with
* the proper server context.
*/
static synchronized void initialize()
{
if (store != null)
{
return;
}
store = new ServerKeyStore();
BootstrapConfig config = SessionManager.get().config();
SecurityManager sm = SecurityManager.getInstance();
// key store file name
String keyStoreFile = config.getString("security", "keystore", "filename", null);
if (keyStoreFile != null)
{
// read key store content
File file = new File(keyStoreFile);
store.keyStore = new byte[(int) file.length()];
DataInputStream dis;
try
{
dis = new DataInputStream(new FileInputStream(file));
dis.readFully(store.keyStore);
dis.close();
// get passwords
store.keyStorePassword = config.getString("access", "password", "keystore", null);
store.keyManagerPassword = config.getString("access", "password", "keyentry", null);
store.trustServerAlias = getAlias(store.keyStore, store.keyStorePassword.toCharArray());
}
catch (IOException e)
{
LOG.severe("Cannot load server keystore file.", e);
}
}
else
{
// store will be built on each call
store.trustServerAlias = sm.getServerAlias();
// these remain null and will be populated no each call
store.keyManagerPassword = null;
store.keyStorePassword = null;
store.keyStore = null;
store.fromDirectory = true;
}
store.encryptedKeyStoreWorker = sm.certificateSm.getEncryptedKeyStore();
store.webServerAlias = sm.getWebServerAlias();
}
/**
* Get a copy of the encrypted server store, based on the shared {@link #store template}. If
* the store is kept in-directory, the copy will contain a store encrypted with unique
* passwords for each copy.
*
* @throws NullPointerException
* If the {@link #store} is <code>null</code>.
*/
public static synchronized ServerKeyStore getStore()
{
if (store == null)
{
throw new NullPointerException("The template store was not initialized!");
}
ServerKeyStore copy = store.clone();
// generate a random authorization token
copy.authorizationToken = DigestUtils.md5Hex(UUID.randomUUID().toString());
String ksPassword = null;
String kePassword = null;
if (store.fromDirectory)
{
// store is in directory, create and encrypt using random passwords
// build the key store from the directory using the server's alias
ksPassword = RandomWordGenerator.create();
kePassword = RandomWordGenerator.create();
copy.keyStore = store.encryptedKeyStoreWorker.getEncryptedKeyStore(copy.trustServerAlias,
ksPassword,
kePassword,
Store.TRUST_STORE);
if (copy.keyStore == null)
{
copy.keyManagerPassword = null;
copy.keyStorePassword = null;
}
else
{
copy.keyStorePassword = ksPassword;
copy.keyManagerPassword = kePassword;
}
}
if (copy.webServerAlias != null)
{
ksPassword = RandomWordGenerator.create();
kePassword = RandomWordGenerator.create();
copy.webKeyStore = store.encryptedKeyStoreWorker.getEncryptedKeyStore(
copy.webServerAlias, ksPassword, kePassword, Store.WEB_STORE);
}
else
{
copy.webKeyStore = null;
}
if (copy.webKeyStore == null)
{
copy.webKeyManagerPassword = null;
copy.webKeyStorePassword = null;
}
else
{
copy.webKeyStorePassword = ksPassword;
copy.webKeyManagerPassword = kePassword;
}
return copy;
}
/**
* Get KeyStore file content.
*
* @return KeyStore file content.
*/
public byte[] getKeyStore()
{
return keyStore;
}
/**
* Get KeyStore password.
*
* @return KeyStore password.
*/
public String getKeyStorePassword()
{
return keyStorePassword;
}
/**
* Get KeyStore manager password.
*
* @return KeyStore manager password.
*/
public String getKeyManagerPassword()
{
return keyManagerPassword;
}
/**
* Get the web key store file content.
*
* @return The web key store file content
*/
public byte[] getWebKeyStore()
{
return webKeyStore;
}
/**
* Get the web key store password.
*
* @return The web key store password
*/
public String getWebKeyStorePassword()
{
return webKeyStorePassword;
}
/**
* Get the web key store manager password.
*
* @return The web key store manager password
*/
public String getWebKeyManagerPassword()
{
return webKeyManagerPassword;
}
/**
* Check if the content is valid
*
* @return <code>true</code> if all parameters are not <code>null</code>
* <code>false</code> otherwise.
*/
public boolean isValid()
{
return keyStore != null && keyStorePassword != null && keyManagerPassword != null;
}
/**
* Check if the web store content is valid
*
* @return <code>true</code> if all parameters are not <code>null</code>
* <code>false</code> otherwise.
*/
public boolean isWebValid()
{
return webKeyStore != null && webKeyStorePassword != null && webKeyManagerPassword != null;
}
/**
* Get authorization token.
*
* @return Return authorization token.
*/
public String getAuthorizationToken()
{
return authorizationToken;
}
/**
* Return the trust server certificate alias.
*
* @return The trust server alias.
*/
public String getTrustServerAlias()
{
return trustServerAlias;
}
/**
* Return the web server certificate alias.
*
* @return The web server certificate alias
*/
public String getWebServerAlias()
{
return webServerAlias;
}
/**
* Create a copy of this instance.
*
* @return A copy of this instance.
*/
@Override
protected ServerKeyStore clone()
{
ServerKeyStore copy = new ServerKeyStore();
copy.keyManagerPassword = this.keyManagerPassword;
copy.keyStore = this.keyStore;
copy.keyStorePassword = this.keyStorePassword;
copy.trustServerAlias = this.trustServerAlias;
copy.webKeyManagerPassword = this.webKeyManagerPassword;
copy.webKeyStore = this.webKeyStore;
copy.webKeyStorePassword = this.webKeyStorePassword;
copy.webServerAlias = this.webServerAlias;
return copy;
}
/**
* Retrieves the server alias from its keys store.
*
* @param store
* The bytes serialized keys store (JKS).
* @param password
* The keys store password.
* @return The server alias.
*/
private static String getAlias(byte[] store, char[] password)
{
String alias = null;
ByteArrayInputStream in = new ByteArrayInputStream(store);
try
{
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(in, password);
Enumeration<String> e = ks.aliases();
while (e.hasMoreElements())
{
alias = e.nextElement();
break;
}
}
catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e)
{
LOG.warning("", e);
}
return alias;
}
/**
* Define the store types.
*/
public static enum Store
{
/** Defines the trust store type */
TRUST_STORE,
/** Defines the web store type */
WEB_STORE
}
/**
* Replacement for the default object writing method.
*
* @param out
* The output destination to which fields will be saved.
*
* @throws IOException
* In case of I/O errors.
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException
{
writeByteArray(out, keyStore);
writeString(out, keyStorePassword);
writeString(out, keyManagerPassword);
writeString(out, authorizationToken);
writeString(out, trustServerAlias);
writeByteArray(out, webKeyStore);
writeString(out, webKeyStorePassword);
writeString(out, webKeyManagerPassword);
writeString(out, webServerAlias);
}
/**
* Replacement for the default object reading method.
*
* @param in
* Input source from which fields will be restored.
*
* @throws IOException
* In case of I/O errors.
* @throws ClassNotFoundException
* If payload can't be instantiated.
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
keyStore = readByteArray(in);
keyStorePassword = readString(in);
keyManagerPassword = readString(in);
authorizationToken = readString(in);
trustServerAlias = readString(in);
webKeyStore = readByteArray(in);
webKeyStorePassword = readString(in);
webKeyManagerPassword = readString(in);
webServerAlias = readString(in);
}
}