io.netty.handler.ssl.OpenSslX509KeyManagerFactory Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.internal.tcnative.SSL;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.ObjectUtil;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.X509KeyManager;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* Special {@link KeyManagerFactory} that pre-compute the keymaterial used when {@link SslProvider#OPENSSL} or
* {@link SslProvider#OPENSSL_REFCNT} is used and so will improve handshake times and its performance.
*
*
*
* Because the keymaterial is pre-computed any modification to the {@link KeyStore} is ignored after
* {@link #init(KeyStore, char[])} is called.
*
* {@link #init(ManagerFactoryParameters)} is not supported by this implementation and so a call to it will always
* result in an {@link InvalidAlgorithmParameterException}.
*/
public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
private final OpenSslKeyManagerFactorySpi spi;
public OpenSslX509KeyManagerFactory() {
this(newOpenSslKeyManagerFactorySpi(null));
}
public OpenSslX509KeyManagerFactory(Provider provider) {
this(newOpenSslKeyManagerFactorySpi(provider));
}
public OpenSslX509KeyManagerFactory(String algorithm, Provider provider) throws NoSuchAlgorithmException {
this(newOpenSslKeyManagerFactorySpi(algorithm, provider));
}
private OpenSslX509KeyManagerFactory(OpenSslKeyManagerFactorySpi spi) {
super(spi, spi.kmf.getProvider(), spi.kmf.getAlgorithm());
this.spi = spi;
}
private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(Provider provider) {
try {
return newOpenSslKeyManagerFactorySpi(null, provider);
} catch (NoSuchAlgorithmException e) {
// This should never happen as we use the default algorithm.
throw new IllegalStateException(e);
}
}
private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(String algorithm, Provider provider)
throws NoSuchAlgorithmException {
if (algorithm == null) {
algorithm = KeyManagerFactory.getDefaultAlgorithm();
}
return new OpenSslKeyManagerFactorySpi(
provider == null ? KeyManagerFactory.getInstance(algorithm) :
KeyManagerFactory.getInstance(algorithm, provider));
}
OpenSslKeyMaterialProvider newProvider() {
return spi.newProvider();
}
private static final class OpenSslKeyManagerFactorySpi extends KeyManagerFactorySpi {
final KeyManagerFactory kmf;
private volatile ProviderFactory providerFactory;
OpenSslKeyManagerFactorySpi(KeyManagerFactory kmf) {
this.kmf = ObjectUtil.checkNotNull(kmf, "kmf");
}
@Override
protected synchronized void engineInit(KeyStore keyStore, char[] chars)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
if (providerFactory != null) {
throw new KeyStoreException("Already initialized");
}
if (!keyStore.aliases().hasMoreElements()) {
throw new KeyStoreException("No aliases found");
}
kmf.init(keyStore, chars);
providerFactory = new ProviderFactory(ReferenceCountedOpenSslContext.chooseX509KeyManager(
kmf.getKeyManagers()), password(chars), Collections.list(keyStore.aliases()));
}
private static String password(char[] password) {
if (password == null || password.length == 0) {
return null;
}
return new String(password);
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
throws InvalidAlgorithmParameterException {
throw new InvalidAlgorithmParameterException("Not supported");
}
@Override
protected KeyManager[] engineGetKeyManagers() {
ProviderFactory providerFactory = this.providerFactory;
if (providerFactory == null) {
throw new IllegalStateException("engineInit(...) not called yet");
}
return new KeyManager[] { providerFactory.keyManager };
}
OpenSslKeyMaterialProvider newProvider() {
ProviderFactory providerFactory = this.providerFactory;
if (providerFactory == null) {
throw new IllegalStateException("engineInit(...) not called yet");
}
return providerFactory.newProvider();
}
private static final class ProviderFactory {
private final X509KeyManager keyManager;
private final String password;
private final Iterable aliases;
ProviderFactory(X509KeyManager keyManager, String password, Iterable aliases) {
this.keyManager = keyManager;
this.password = password;
this.aliases = aliases;
}
OpenSslKeyMaterialProvider newProvider() {
return new OpenSslPopulatedKeyMaterialProvider(keyManager,
password, aliases);
}
/**
* {@link OpenSslKeyMaterialProvider} implementation that pre-compute the {@link OpenSslKeyMaterial} for
* all aliases.
*/
private static final class OpenSslPopulatedKeyMaterialProvider extends OpenSslKeyMaterialProvider {
private final Map materialMap;
OpenSslPopulatedKeyMaterialProvider(
X509KeyManager keyManager, String password, Iterable aliases) {
super(keyManager, password);
materialMap = new HashMap();
boolean initComplete = false;
try {
for (String alias: aliases) {
if (alias != null && !materialMap.containsKey(alias)) {
try {
materialMap.put(alias, super.chooseKeyMaterial(
UnpooledByteBufAllocator.DEFAULT, alias));
} catch (Exception e) {
// Just store the exception and rethrow it when we try to choose the keymaterial
// for this alias later on.
materialMap.put(alias, e);
}
}
}
initComplete = true;
} finally {
if (!initComplete) {
destroy();
}
}
if (materialMap.isEmpty()) {
throw new IllegalArgumentException("aliases must be non-empty");
}
}
@Override
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
Object value = materialMap.get(alias);
if (value == null) {
// There is no keymaterial for the requested alias, return null
return null;
}
if (value instanceof OpenSslKeyMaterial) {
return ((OpenSslKeyMaterial) value).retain();
}
throw (Exception) value;
}
@Override
void destroy() {
for (Object material: materialMap.values()) {
ReferenceCountUtil.release(material);
}
materialMap.clear();
}
}
}
}
/**
* Create a new initialized {@link OpenSslX509KeyManagerFactory} which loads its {@link PrivateKey} directly from
* an {@code OpenSSL engine} via the
* ENGINE_load_private_key
* function.
*/
public static OpenSslX509KeyManagerFactory newEngineBased(File certificateChain, String password)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
return newEngineBased(SslContext.toX509Certificates(certificateChain), password);
}
/**
* Create a new initialized {@link OpenSslX509KeyManagerFactory} which loads its {@link PrivateKey} directly from
* an {@code OpenSSL engine} via the
* ENGINE_load_private_key
* function.
*/
public static OpenSslX509KeyManagerFactory newEngineBased(X509Certificate[] certificateChain, String password)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore store = new OpenSslKeyStore(certificateChain.clone(), false);
store.load(null, null);
OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
factory.init(store, password == null ? null : password.toCharArray());
return factory;
}
/**
* See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(File chain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
return newKeyless(SslContext.toX509Certificates(chain));
}
/**
* See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(InputStream chain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
return newKeyless(SslContext.toX509Certificates(chain));
}
/**
* Returns a new initialized {@link OpenSslX509KeyManagerFactory} which will provide its private key by using the
* {@link OpenSslPrivateKeyMethod}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(X509Certificate... certificateChain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore store = new OpenSslKeyStore(certificateChain.clone(), true);
store.load(null, null);
OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
factory.init(store, null);
return factory;
}
private static final class OpenSslKeyStore extends KeyStore {
private OpenSslKeyStore(final X509Certificate[] certificateChain, final boolean keyless) {
super(new KeyStoreSpi() {
private final Date creationDate = new Date();
@Override
public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
if (engineContainsAlias(alias)) {
final long privateKeyAddress;
if (keyless) {
privateKeyAddress = 0;
} else {
try {
privateKeyAddress = SSL.loadPrivateKeyFromEngine(
alias, password == null ? null : new String(password));
} catch (Exception e) {
UnrecoverableKeyException keyException =
new UnrecoverableKeyException("Unable to load key from engine");
keyException.initCause(e);
throw keyException;
}
}
return new OpenSslPrivateKey(privateKeyAddress);
}
return null;
}
@Override
public Certificate[] engineGetCertificateChain(String alias) {
return engineContainsAlias(alias)? certificateChain.clone() : null;
}
@Override
public Certificate engineGetCertificate(String alias) {
return engineContainsAlias(alias)? certificateChain[0] : null;
}
@Override
public Date engineGetCreationDate(String alias) {
return engineContainsAlias(alias)? creationDate : null;
}
@Override
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
throws KeyStoreException {
throw new KeyStoreException("Not supported");
}
@Override
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
throw new KeyStoreException("Not supported");
}
@Override
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
throw new KeyStoreException("Not supported");
}
@Override
public void engineDeleteEntry(String alias) throws KeyStoreException {
throw new KeyStoreException("Not supported");
}
@Override
public Enumeration engineAliases() {
return Collections.enumeration(Collections.singleton(SslContext.ALIAS));
}
@Override
public boolean engineContainsAlias(String alias) {
return SslContext.ALIAS.equals(alias);
}
@Override
public int engineSize() {
return 1;
}
@Override
public boolean engineIsKeyEntry(String alias) {
return engineContainsAlias(alias);
}
@Override
public boolean engineIsCertificateEntry(String alias) {
return engineContainsAlias(alias);
}
@Override
public String engineGetCertificateAlias(Certificate cert) {
if (cert instanceof X509Certificate) {
for (X509Certificate x509Certificate : certificateChain) {
if (x509Certificate.equals(cert)) {
return SslContext.ALIAS;
}
}
}
return null;
}
@Override
public void engineStore(OutputStream stream, char[] password) {
throw new UnsupportedOperationException();
}
@Override
public void engineLoad(InputStream stream, char[] password) {
if (stream != null && password != null) {
throw new UnsupportedOperationException();
}
}
}, null, "native");
OpenSsl.ensureAvailability();
}
}
}