/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.StringUtils;

A factory to create new block cipher objects.
/** * A factory to create new block cipher objects. */
public class CipherFactory {
The default password to use for the .h2.keystore file
/** * The default password to use for the .h2.keystore file */
public static final String KEYSTORE_PASSWORD = "h2pass";
The security property which can prevent anonymous TLS connections. Introduced into Java 6, 7, 8 in updates from July 2015.
/** * The security property which can prevent anonymous TLS connections. * Introduced into Java 6, 7, 8 in updates from July 2015. */
public static final String LEGACY_ALGORITHMS_SECURITY_KEY = "jdk.tls.legacyAlgorithms";
The value of "jdk.tls.legacyAlgorithms" security property at the time of class initialization. Null if it is not set.
/** * The value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} security * property at the time of class initialization. * Null if it is not set. */
public static final String DEFAULT_LEGACY_ALGORITHMS = getLegacyAlgorithmsSilently(); private static final String KEYSTORE = "~/.h2.keystore"; private static final String KEYSTORE_KEY = "javax.net.ssl.keyStore"; private static final String KEYSTORE_PASSWORD_KEY = "javax.net.ssl.keyStorePassword"; private CipherFactory() { // utility class }
Get a new block cipher object for the given algorithm.
Params:
  • algorithm – the algorithm
Returns:a new cipher object
/** * Get a new block cipher object for the given algorithm. * * @param algorithm the algorithm * @return a new cipher object */
public static BlockCipher getBlockCipher(String algorithm) { if ("XTEA".equalsIgnoreCase(algorithm)) { return new XTEA(); } else if ("AES".equalsIgnoreCase(algorithm)) { return new AES(); } else if ("FOG".equalsIgnoreCase(algorithm)) { return new Fog(); } throw DbException.get(ErrorCode.UNSUPPORTED_CIPHER, algorithm); }
Create a secure client socket that is connected to the given address and port.
Params:
  • address – the address to connect to
  • port – the port
Returns:the socket
/** * Create a secure client socket that is connected to the given address and * port. * * @param address the address to connect to * @param port the port * @return the socket */
public static Socket createSocket(InetAddress address, int port) throws IOException { Socket socket = null; setKeystore(); SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket secureSocket = (SSLSocket) f.createSocket(); secureSocket.connect(new InetSocketAddress(address, port), SysProperties.SOCKET_CONNECT_TIMEOUT); secureSocket.setEnabledProtocols( disableSSL(secureSocket.getEnabledProtocols())); if (SysProperties.ENABLE_ANONYMOUS_TLS) { String[] list = enableAnonymous( secureSocket.getEnabledCipherSuites(), secureSocket.getSupportedCipherSuites()); secureSocket.setEnabledCipherSuites(list); } socket = secureSocket; return socket; }
Create a secure server socket. If a bind address is specified, the socket is only bound to this address. If h2.enableAnonymousTLS is true, an attempt is made to modify the security property jdk.tls.legacyAlgorithms (in newer JVMs) to allow anonymous TLS. This system change is effectively permanent for the lifetime of the JVM.
Params:
  • port – the port to listen on
  • bindAddress – the address to bind to, or null to bind to all addresses
See Also:
  • removeAnonFromLegacyAlgorithms()
Returns:the server socket
/** * Create a secure server socket. If a bind address is specified, the * socket is only bound to this address. * If h2.enableAnonymousTLS is true, an attempt is made to modify * the security property jdk.tls.legacyAlgorithms (in newer JVMs) to allow * anonymous TLS. This system change is effectively permanent for the * lifetime of the JVM. * @see #removeAnonFromLegacyAlgorithms() * * @param port the port to listen on * @param bindAddress the address to bind to, or null to bind to all * addresses * @return the server socket */
public static ServerSocket createServerSocket(int port, InetAddress bindAddress) throws IOException { ServerSocket socket = null; if (SysProperties.ENABLE_ANONYMOUS_TLS) { removeAnonFromLegacyAlgorithms(); } setKeystore(); ServerSocketFactory f = SSLServerSocketFactory.getDefault(); SSLServerSocket secureSocket; if (bindAddress == null) { secureSocket = (SSLServerSocket) f.createServerSocket(port); } else { secureSocket = (SSLServerSocket) f.createServerSocket(port, 0, bindAddress); } secureSocket.setEnabledProtocols( disableSSL(secureSocket.getEnabledProtocols())); if (SysProperties.ENABLE_ANONYMOUS_TLS) { String[] list = enableAnonymous( secureSocket.getEnabledCipherSuites(), secureSocket.getSupportedCipherSuites()); secureSocket.setEnabledCipherSuites(list); } socket = secureSocket; return socket; }
Removes DH_anon and ECDH_anon from a comma separated list of ciphers. Only the first occurrence is removed. If there is nothing to remove, returns the reference to the argument.
Params:
  • list – a list of names separated by commas (and spaces)
Returns: a new string without DH_anon and ECDH_anon items, or the original if none were found
/** * Removes DH_anon and ECDH_anon from a comma separated list of ciphers. * Only the first occurrence is removed. * If there is nothing to remove, returns the reference to the argument. * @param list a list of names separated by commas (and spaces) * @return a new string without DH_anon and ECDH_anon items, * or the original if none were found */
public static String removeDhAnonFromCommaSeparatedList(String list) { if (list == null) { return list; } List<String> algorithms = new LinkedList<>(Arrays.asList(list.split("\\s*,\\s*"))); boolean dhAnonRemoved = algorithms.remove("DH_anon"); boolean ecdhAnonRemoved = algorithms.remove("ECDH_anon"); if (dhAnonRemoved || ecdhAnonRemoved) { String string = Arrays.toString(algorithms.toArray(new String[algorithms.size()])); return (!algorithms.isEmpty()) ? string.substring(1, string.length() - 1): ""; } return list; }
Attempts to weaken the security properties to allow anonymous TLS. New JREs would not choose an anonymous cipher suite in a TLS handshake if server-side security property "jdk.tls.legacyAlgorithms" were not modified from the default value.

NOTE: In current (as of 2016) default implementations of JSSE which use this security property, the value is permanently cached inside the ServerHandshake class upon its first use. Therefore the modification accomplished by this method has to be done before the first use of a server SSL socket. Later changes to this property will not have any effect on server socket behavior.

/** * Attempts to weaken the security properties to allow anonymous TLS. * New JREs would not choose an anonymous cipher suite in a TLS handshake * if server-side security property * {@value #LEGACY_ALGORITHMS_SECURITY_KEY} * were not modified from the default value. * <p> * NOTE: In current (as of 2016) default implementations of JSSE which use * this security property, the value is permanently cached inside the * ServerHandshake class upon its first use. * Therefore the modification accomplished by this method has to be done * before the first use of a server SSL socket. * Later changes to this property will not have any effect on server socket * behavior. */
public static synchronized void removeAnonFromLegacyAlgorithms() { String legacyOriginal = getLegacyAlgorithmsSilently(); if (legacyOriginal == null) { return; } String legacyNew = removeDhAnonFromCommaSeparatedList(legacyOriginal); if (!legacyOriginal.equals(legacyNew)) { setLegacyAlgorithmsSilently(legacyNew); } }
Attempts to resets the security property to the default value. The default value of "jdk.tls.legacyAlgorithms" was obtained at time of class initialization.

NOTE: Resetting the property might not have any effect on server socket behavior.

See Also:
/** * Attempts to resets the security property to the default value. * The default value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} was * obtained at time of class initialization. * <p> * NOTE: Resetting the property might not have any effect on server * socket behavior. * @see #removeAnonFromLegacyAlgorithms() */
public static synchronized void resetDefaultLegacyAlgorithms() { setLegacyAlgorithmsSilently(DEFAULT_LEGACY_ALGORITHMS); }
Returns the security property "jdk.tls.legacyAlgorithms". Ignores security exceptions.
Returns: the value of the security property, or null if not set or not accessible
/** * Returns the security property {@value #LEGACY_ALGORITHMS_SECURITY_KEY}. * Ignores security exceptions. * * @return the value of the security property, or null if not set * or not accessible */
public static String getLegacyAlgorithmsSilently() { String defaultLegacyAlgorithms = null; try { defaultLegacyAlgorithms = Security.getProperty(LEGACY_ALGORITHMS_SECURITY_KEY); } catch (SecurityException e) { // ignore } return defaultLegacyAlgorithms; } private static void setLegacyAlgorithmsSilently(String legacyAlgorithms) { if (legacyAlgorithms == null) { return; } try { Security.setProperty(LEGACY_ALGORITHMS_SECURITY_KEY, legacyAlgorithms); } catch (SecurityException e) { // ignore } } private static byte[] getKeyStoreBytes(KeyStore store, String password) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); try { store.store(bout, password.toCharArray()); } catch (Exception e) { throw DbException.convertToIOException(e); } return bout.toByteArray(); }
Get the keystore object using the given password.
Params:
  • password – the keystore password
Returns:the keystore
/** * Get the keystore object using the given password. * * @param password the keystore password * @return the keystore */
public static KeyStore getKeyStore(String password) throws IOException { try { // The following source code can be re-generated // if you have a keystore file. // This code is (hopefully) more Java version independent // than using keystores directly. See also: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4887561 // (1.4.2 cannot read keystore written with 1.4.1) // --- generated code start --- KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); store.load(null, password.toCharArray()); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); store.load(null, password.toCharArray()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec( StringUtils.convertHexToBytes( "30820277020100300d06092a864886f70d010101" + "0500048202613082025d02010002818100dc0a13" + "c602b7141110eade2f051b54777b060d0f74e6a1" + "10f9cce81159f271ebc88d8e8aa1f743b505fc2e" + "7dfe38d33b8d3f64d1b363d1af4d877833897954" + "cbaec2fa384c22a415498cf306bb07ac09b76b00" + "1cd68bf77ea0a628f5101959cf2993a9c23dbee7" + "9b19305977f8715ae78d023471194cc900b231ee" + "cb0aaea98d02030100010281810099aa4ff4d0a0" + "9a5af0bd953cb10c4d08c3d98df565664ac5582e" + "494314d5c3c92dddedd5d316a32a206be4ec0846" + "16fe57be15e27cad111aa3c21fa79e32258c6ca8" + "430afc69eddd52d3b751b37da6b6860910b94653" + "192c0db1d02abcfd6ce14c01f238eec7c20bd3bb" + "750940004bacba2880349a9494d10e139ecb2355" + "d101024100ffdc3defd9c05a2d377ef6019fa62b" + "3fbd5b0020a04cc8533bca730e1f6fcf5dfceea1" + "b044fbe17d9eababfbc7d955edad6bc60f9be826" + "ad2c22ba77d19a9f65024100dc28d43fdbbc9385" + "2cc3567093157702bc16f156f709fb7db0d9eec0" + "28f41fd0edcd17224c866e66be1744141fb724a1" + "0fd741c8a96afdd9141b36d67fff6309024077b1" + "cddbde0f69604bdcfe33263fb36ddf24aa3b9922" + "327915b890f8a36648295d0139ecdf68c245652c" + "4489c6257b58744fbdd961834a4cab201801a3b1" + "e52d024100b17142e8991d1b350a0802624759d4" + "8ae2b8071a158ff91fabeb6a8f7c328e762143dc" + "726b8529f42b1fab6220d1c676fdc27ba5d44e84" + "7c72c52064afd351a902407c6e23fe35bcfcd1a6" + "62aa82a2aa725fcece311644d5b6e3894853fd4c" + "e9fe78218c957b1ff03fc9e5ef8ffeb6bd58235f" + "6a215c97d354fdace7e781e4a63e8b")); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); Certificate[] certs = { CertificateFactory .getInstance("X.509") .generateCertificate( new ByteArrayInputStream( StringUtils.convertHexToBytes( "3082018b3081f502044295ce6b300d06092a8648" + "86f70d0101040500300d310b3009060355040313" + "024832301e170d3035303532363133323630335a" + "170d3337303933303036353734375a300d310b30" + "0906035504031302483230819f300d06092a8648" + "86f70d010101050003818d0030818902818100dc" + "0a13c602b7141110eade2f051b54777b060d0f74" + "e6a110f9cce81159f271ebc88d8e8aa1f743b505" + "fc2e7dfe38d33b8d3f64d1b363d1af4d87783389" + "7954cbaec2fa384c22a415498cf306bb07ac09b7" + "6b001cd68bf77ea0a628f5101959cf2993a9c23d" + "bee79b19305977f8715ae78d023471194cc900b2" + "31eecb0aaea98d0203010001300d06092a864886" + "f70d01010405000381810083f4401a279453701b" + "ef9a7681a5b8b24f153f7d18c7c892133d97bd5f" + "13736be7505290a445a7d5ceb75522403e509751" + "5cd966ded6351ff60d5193de34cd36e5cb04d380" + "398e66286f99923fd92296645fd4ada45844d194" + "dfd815e6cd57f385c117be982809028bba1116c8" + "5740b3d27a55b1a0948bf291ddba44bed337b9"))), }; store.setKeyEntry("h2", privateKey, password.toCharArray(), certs); // --- generated code end --- return store; } catch (Exception e) { throw DbException.convertToIOException(e); } } private static void setKeystore() throws IOException { Properties p = System.getProperties(); if (p.getProperty(KEYSTORE_KEY) == null) { String fileName = KEYSTORE; byte[] data = getKeyStoreBytes(getKeyStore( KEYSTORE_PASSWORD), KEYSTORE_PASSWORD); boolean needWrite = true; if (FileUtils.exists(fileName) && FileUtils.size(fileName) == data.length) { // don't need to overwrite the file if it did not change InputStream fin = FileUtils.newInputStream(fileName); byte[] now = IOUtils.readBytesAndClose(fin, 0); if (now != null && Arrays.equals(data, now)) { needWrite = false; } } if (needWrite) { try { OutputStream out = FileUtils.newOutputStream(fileName, false); out.write(data); out.close(); } catch (Exception e) { throw DbException.convertToIOException(e); } } String absolutePath = FileUtils.toRealPath(fileName); System.setProperty(KEYSTORE_KEY, absolutePath); } if (p.getProperty(KEYSTORE_PASSWORD_KEY) == null) { System.setProperty(KEYSTORE_PASSWORD_KEY, KEYSTORE_PASSWORD); } } private static String[] enableAnonymous(String[] enabled, String[] supported) { LinkedHashSet<String> set = new LinkedHashSet<>(); for (String x : supported) { if (!x.startsWith("SSL") && x.contains("_anon_") && (x.contains("_AES_") || x.contains("_3DES_")) && x.contains("_SHA")) { set.add(x); } } Collections.addAll(set, enabled); return set.toArray(new String[0]); } private static String[] disableSSL(String[] enabled) { HashSet<String> set = new HashSet<>(); for (String x : enabled) { if (!x.startsWith("SSL")) { set.add(x); } } return set.toArray(new String[0]); } }