wolfssljni/src/java/com/wolfssl/provider/jsse/WolfSSLAuthStore.java

599 lines
20 KiB
Java

/* WolfSSLAuthStore.java
*
* Copyright (C) 2006-2023 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
package com.wolfssl.provider.jsse;
import com.wolfssl.WolfSSL;
import com.wolfssl.WolfSSL.TLS_VERSION;
import com.wolfssl.WolfSSLSession;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.KeyStoreException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class used to store common settings, objects, etc.
*/
public class WolfSSLAuthStore {
private TLS_VERSION currentVersion = TLS_VERSION.INVALID;
private X509KeyManager km = null;
private X509TrustManager tm = null;
private SecureRandom sr = null;
private String alias = null;
private WolfSSLSessionContext serverCtx = null;
private WolfSSLSessionContext clientCtx = null;
private SessionStore<Integer, WolfSSLImplementSSLSession> store = null;
private static final Object storeLock = new Object();
/**
* Protected constructor to create new WolfSSLAuthStore
* @param keyman key manager to use
* @param trustman trust manager to use
* @param random secure random
* @param version TLS protocol version to use
* @throws IllegalArgumentException when bad values are passed in
* @throws KeyManagementException in the case that getting keys fails
*/
protected WolfSSLAuthStore(KeyManager[] keyman, TrustManager[] trustman,
SecureRandom random, TLS_VERSION version)
throws IllegalArgumentException, KeyManagementException {
/* default session cache size of 33 to match native wolfSSL
* default cache size */
int defaultCacheSize = 33;
if (version == TLS_VERSION.INVALID) {
throw new IllegalArgumentException("Invalid SSL/TLS version");
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Creating new WolfSSLAuthStore");
initKeyManager(keyman);
initTrustManager(trustman);
initSecureRandom(random);
this.currentVersion = version;
store = new SessionStore<>(defaultCacheSize);
this.serverCtx = new WolfSSLSessionContext(
this, defaultCacheSize, WolfSSL.WOLFSSL_SERVER_END);
this.clientCtx = new WolfSSLSessionContext(
this, defaultCacheSize, WolfSSL.WOLFSSL_CLIENT_END);
}
/**
* Initialize key manager.
* The first instance of X509KeyManager found will be used. If null is
* passed in, installed security providers with be searched for highest
* priority implementation of the required factory.
*/
private void initKeyManager(KeyManager[] in)
throws KeyManagementException {
KeyManager[] managers = in;
if (managers == null || managers.length == 0) {
try {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"searching installed providers for X509KeyManager (type: "
+ KeyManagerFactory.getDefaultAlgorithm() +")");
/* use key managers from installed security providers */
KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmFactory.init(null, null);
managers = kmFactory.getKeyManagers();
} catch (NoSuchAlgorithmException nsae) {
throw new KeyManagementException(nsae);
} catch (KeyStoreException kse) {
throw new KeyManagementException(kse);
} catch (UnrecoverableKeyException uke) {
throw new KeyManagementException(uke);
}
}
if (managers != null) {
for (int i = 0; i < managers.length; i++) {
if (managers[i] instanceof X509KeyManager) {
km = (X509KeyManager)managers[i];
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"located X509KeyManager instance: " + km);
break;
}
}
}
}
/**
* Initialize trust manager.
* The first instance of X509TrustManager found will be used. If null is
* passed in, installed security providers with be searched for highest
* priority implementation of the required factory.
*/
private void initTrustManager(TrustManager[] in)
throws KeyManagementException {
TrustManager[] managers = in;
if (managers == null || managers.length == 0) {
try {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"searching installed providers for X509TrustManager");
/* use trust managers from installed security providers */
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init((KeyStore)null);
managers = tmFactory.getTrustManagers();
} catch (NoSuchAlgorithmException nsae) {
throw new KeyManagementException(nsae);
} catch (KeyStoreException kse) {
throw new KeyManagementException(kse);
}
}
if (managers != null) {
for (int i = 0; i < managers.length; i++) {
if (managers[i] instanceof X509TrustManager) {
tm = (X509TrustManager)managers[i];
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"located X509TrustManager instance: " + tm);
break;
}
}
}
}
/**
* Initialize secure random.
* If SecureRandom passed in is null, default implementation will
* be used.
*/
private void initSecureRandom(SecureRandom random) {
if (random == null) {
sr = new SecureRandom();
}
sr = random;
}
/**
* Get X509KeyManager for this object
* @return get the key manager used
*/
protected X509KeyManager getX509KeyManager() {
return this.km;
}
/**
* Get X509TrustManager for this object
* @return get the trust manager used
*/
protected X509TrustManager getX509TrustManager() {
return this.tm;
}
/**
* Get the SecureRandom for this object
* @return get secure random
*/
protected SecureRandom getSecureRandom() {
return this.sr;
}
/**
* Get protocol version set
* @return get the current protocol version set
*/
protected TLS_VERSION getProtocolVersion() {
return this.currentVersion;
}
/**
* Set certificate alias
* @param in alias to set for certificate used
*/
protected void setCertAlias(String in) {
this.alias = in;
}
/**
* Get certificate alias
* @return alias name
*/
protected String getCertAlias() {
return this.alias;
}
/**
* Getter function for WolfSSLSessionContext associated with store
* @return pointer to the context set
*/
protected WolfSSLSessionContext getServerContext() {
return this.serverCtx;
}
/**
* Getter function for WolfSSLSessionContext associated with store
* @return pointer to the context set
*/
protected WolfSSLSessionContext getClientContext() {
return this.clientCtx;
}
/**
* Reset the size of the array to cache sessions
* @param sz new array size
* @param side server/client side for cache resize
*/
protected void resizeCache(int sz, int side) {
SessionStore<Integer, WolfSSLImplementSSLSession> newStore =
new SessionStore<>(sz);
//@TODO check for side server/client, currently a resize is for all
synchronized (storeLock) {
store.putAll(newStore);
store = newStore;
}
}
/**
* Get and return either an existing session from the Java session cache
* table, or create a new session if one does not exist.
*
* This method can return null if ether an error occurs getting a session,
* or a new session could not be created.
*
* If called on the server side (clientMode == false), a new
* WolfSSLImplementSSLSession will be created and returned, since the
* server-side session cache is managed and maintained interal to native
* wolfSSL.
*
* @param ssl WolfSSLSession (WOLFSSL) for which the returned session
* is stored back into (ie: wolfSSL_set_session(ssl, session))
* @param port port number of peer being connected to
* @param host host of the peer being connected to
* @param clientMode if is client side then true, otherwise false
* @return an existing SSLSession from Java session cache, or a new
* object if not in cache, called on server side, or host
* is null
*/
protected synchronized WolfSSLImplementSSLSession getSession(
WolfSSLSession ssl, int port, String host, boolean clientMode) {
WolfSSLImplementSSLSession ses = null;
String toHash = null;
if (ssl == null) {
return null;
}
/* Return new session if in server mode, or if host is null */
if (!clientMode || host == null) {
return this.getSession(ssl);
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"attempting to look up session (" +
"host: " + host + ", port: " + port + ")");
/* Print current size and contents of SessionStore / LinkedHashMap.
* Synchronizes on storeLock internally. */
printSessionStoreStatus();
/* Lock on static/global storeLock, since Java session cache table
* is shared between all threads */
synchronized (storeLock) {
/* generate cache key hash (host:port) */
toHash = host.concat(Integer.toString(port));
/* try getting session out of Java store */
ses = store.get(toHash.hashCode());
if (ses == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"session not found in cache table, creating new");
/* not found in stored sessions create a new one */
ses = new WolfSSLImplementSSLSession(ssl, port, host, this);
ses.setValid(true); /* new sessions marked as valid */
ses.isFromTable = false;
ses.setPseudoSessionId(
Integer.toString(ssl.hashCode()).getBytes());
}
else {
/* Remove old entry from table. TLS 1.3 binder changes between
* resumptions and stored session should only be used to
* resume once. New session structure/object will be cached
* after the resumed session completes the handshake, for
* subsequent resumption attempts to use. */
store.remove(toHash.hashCode());
ses.isFromTable = true;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"session found in cache, trying to resume");
ses.resume(ssl);
}
return ses;
}
}
/**
* Print summary of current SessionStore (LinkedHashMap) status.
* Prints out size of current SessionStore. If size is greater than zero,
* prints out host:port of all sessions stored in the store.
* Called by getSession(). */
private void printSessionStoreStatus() {
synchronized (storeLock) {
Collection<WolfSSLImplementSSLSession> values =
store.values();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"SessionStore Status : --------------------------");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
" size: " + store.size());
if (store.size() > 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
" values: ");
for (WolfSSLImplementSSLSession s : values) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
" " + s.getHost() + ": " + s.getPort());
}
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"------------------------------------------------");
}
}
/** Returns a new session, does not check/save for resumption
* @param ssl WOLFSSL class to reference with new session
* @return a new SSLSession on success
*/
protected synchronized WolfSSLImplementSSLSession getSession(
WolfSSLSession ssl) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"creating new session");
WolfSSLImplementSSLSession ses =
new WolfSSLImplementSSLSession(ssl, this);
ses.setValid(true);
ses.isFromTable = false;
ses.setPseudoSessionId(Integer.toString(ssl.hashCode()).getBytes());
return ses;
}
/**
* Internal helper function to check if session ID is all zeros.
* Used by addSession()
*
* @param id session ID
* @return true if array is all zeros (0x00), otherwise false
*/
private boolean idAllZeros(byte[] id) {
boolean ret = true;
if (id == null) {
return true;
}
for (int i = 0; i < id.length; i++) {
if (id[i] != 0x00) {
return false;
}
}
return true;
}
/**
* Add SSLSession into wolfJSSE Java session cache table, to be used
* for session resumption.
*
* Session is stored into the session table using a hash code as the key.
* If the peer host is not null, the hash code is based on a concatenation
* of the peer host and port. If the peer host is null, the hash code
* is based on the session ID (if ID is not null, and non-zero length).
* Otherwise, no hash code is generated and the session is not stored into
* the session cache table.
*
* This method synchronizes on the static/global storeLock object, since
* the session cache is global and shared amongst all threads.
*
* @param session SSLSession to be stored in Java session cache
* @return SSL_SUCCESS on success
*/
protected int addSession(WolfSSLImplementSSLSession session) {
String toHash;
int hashCode = 0;
/* Lock access to store while adding new session, store is global */
synchronized (storeLock) {
if (session.getPeerHost() != null) {
/* Generate key for storing into session table (host:port) */
toHash = session.getPeerHost().concat(Integer.toString(
session.getPeerPort()));
hashCode = toHash.hashCode();
}
else {
/* If no peer host is available then create hash key from
* session ID if not null, not zero length, and not all zeros */
byte[] sessionId = session.getId();
if (sessionId != null && sessionId.length > 0 &&
(idAllZeros(sessionId) == false)) {
hashCode = Arrays.toString(session.getId()).hashCode();
}
}
/* Always try to store session into cache table, as long as we
* have a hashCode. If session already exists for hashCode, it
* will be overwritten with new/refreshed version */
if (hashCode != 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"stored session in cache table (host: " +
session.getPeerHost() + ", port: " +
session.getPeerPort() + ") " +
"hashCode = " + hashCode + " side = " +
session.getSide());
store.put(hashCode, session);
session.isInTable = true;
}
}
return WolfSSL.SSL_SUCCESS;
}
/**
* Internal function to return a list of all session ID's
* @param side server or client side to get list of ID's from
* @return enumerated session IDs
*/
protected Enumeration<byte[]> getAllIDs(int side) {
List<byte[]> ret = new ArrayList<>();
synchronized (storeLock) {
for (Object obj : store.values()) {
WolfSSLImplementSSLSession current =
(WolfSSLImplementSSLSession)obj;
if (current.getSide() == side) {
ret.add(current.getId());
}
}
return Collections.enumeration(ret);
}
}
/**
* Getter function for session with session id 'ID'
* @param ID the session id to search for
* @param side if the session is expected on the server or client side
* @return session from the store that has session id 'ID'
*/
protected WolfSSLImplementSSLSession getSession(byte[] ID, int side) {
WolfSSLImplementSSLSession ret = null;
synchronized (storeLock) {
for (Object obj : store.values()) {
WolfSSLImplementSSLSession current =
(WolfSSLImplementSSLSession)obj;
if (current.getSide() == side &&
java.util.Arrays.equals(ID, current.getId())) {
ret = current;
break;
}
}
return ret;
}
}
/**
* Goes through the list of sessions and checks for timeouts. If timed out
* then the session is invalidated.
* @param in the updated timeout value to check against
* @param side server or client side getting the timeout update
*/
protected void updateTimeouts(int in, int side) {
Date currentDate = new Date();
long now = currentDate.getTime();
synchronized (storeLock) {
for (Object obj : store.values()) {
long diff;
WolfSSLImplementSSLSession current =
(WolfSSLImplementSSLSession)obj;
if (current.getSide() == side) {
/* difference in seconds */
diff = (now - current.creation.getTime()) / 1000;
if (diff < 0) {
/* session is from the future ... */ //@TODO
}
if (in > 0 && diff > in) {
current.invalidate();
}
current.setNativeTimeout(in);
}
}
}
}
private class SessionStore<K, V> extends LinkedHashMap<K, V> {
/**
* user defined ID
*/
private static final long serialVersionUID = 1L;
private final int maxSz;
/**
* @param in max size of hash map before oldest entry is overwritten
*/
protected SessionStore(int in) {
maxSz = in;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> oldest) {
return size() > maxSz;
}
}
@SuppressWarnings("deprecation")
@Override
protected synchronized void finalize() throws Throwable {
/* Clear LinkedHashMap and set to null to allow
* for garbage collection */
store.clear();
store = null;
super.finalize();
}
}