Allow to load PrivateKey via OpenSSL Engine (#8200)

Motivation:

OpenSSL itself has an abstraction which allows you to customize some things. For example it is possible to load the PrivateKey from the engine. We should support this.

Modifications:

Add two new static methods to OpenSslX509KeyManagerFactory which allow to create an OpenSslX509KeyManagerFactory that loads its PrivateKey via the OpenSSL Engine directly.

Result:

More flexible usage of OpenSSL possible
This commit is contained in:
Norman Maurer 2018-08-18 07:20:44 +02:00 committed by GitHub
parent bbe2e4d224
commit df00539fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 485 additions and 91 deletions

View File

@ -0,0 +1,117 @@
/*
* 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.internal.tcnative.SSL;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
final class DefaultOpenSslKeyMaterial extends AbstractReferenceCounted implements OpenSslKeyMaterial {
private static final ResourceLeakDetector<DefaultOpenSslKeyMaterial> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(DefaultOpenSslKeyMaterial.class);
private final ResourceLeakTracker<DefaultOpenSslKeyMaterial> leak;
private long chain;
private long privateKey;
DefaultOpenSslKeyMaterial(long chain, long privateKey) {
this.chain = chain;
this.privateKey = privateKey;
leak = leakDetector.track(this);
}
@Override
public long certificateChainAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return chain;
}
@Override
public long privateKeyAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return privateKey;
}
@Override
protected void deallocate() {
SSL.freeX509Chain(chain);
chain = 0;
SSL.freePrivateKey(privateKey);
privateKey = 0;
if (leak != null) {
boolean closed = leak.close(this);
assert closed;
}
}
@Override
public DefaultOpenSslKeyMaterial retain() {
if (leak != null) {
leak.record();
}
super.retain();
return this;
}
@Override
public DefaultOpenSslKeyMaterial retain(int increment) {
if (leak != null) {
leak.record();
}
super.retain(increment);
return this;
}
@Override
public DefaultOpenSslKeyMaterial touch() {
if (leak != null) {
leak.record();
}
super.touch();
return this;
}
@Override
public DefaultOpenSslKeyMaterial touch(Object hint) {
if (leak != null) {
leak.record(hint);
}
return this;
}
@Override
public boolean release() {
if (leak != null) {
leak.record();
}
return super.release();
}
@Override
public boolean release(int decrement) {
if (leak != null) {
leak.record();
}
return super.release(decrement);
}
}

View File

@ -15,110 +15,38 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
import io.netty.internal.tcnative.SSL; import io.netty.util.ReferenceCounted;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
/** /**
* Holds references to the native key-material that is used by OpenSSL. * Holds references to the native key-material that is used by OpenSSL.
*/ */
final class OpenSslKeyMaterial extends AbstractReferenceCounted { interface OpenSslKeyMaterial extends ReferenceCounted {
private static final ResourceLeakDetector<OpenSslKeyMaterial> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(OpenSslKeyMaterial.class);
private final ResourceLeakTracker<OpenSslKeyMaterial> leak;
private long chain;
private long privateKey;
OpenSslKeyMaterial(long chain, long privateKey) {
this.chain = chain;
this.privateKey = privateKey;
leak = leakDetector.track(this);
}
/** /**
* Returns the pointer to the {@code STACK_OF(X509)} which holds the certificate chain. * Returns the pointer to the {@code STACK_OF(X509)} which holds the certificate chain.
*/ */
public long certificateChainAddress() { long certificateChainAddress();
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return chain;
}
/** /**
* Returns the pointer to the {@code EVP_PKEY}. * Returns the pointer to the {@code EVP_PKEY}.
*/ */
public long privateKeyAddress() { long privateKeyAddress();
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return privateKey;
}
@Override @Override
protected void deallocate() { OpenSslKeyMaterial retain();
SSL.freeX509Chain(chain);
chain = 0;
SSL.freePrivateKey(privateKey);
privateKey = 0;
if (leak != null) {
boolean closed = leak.close(this);
assert closed;
}
}
@Override @Override
public OpenSslKeyMaterial retain() { OpenSslKeyMaterial retain(int increment);
if (leak != null) {
leak.record();
}
super.retain();
return this;
}
@Override @Override
public OpenSslKeyMaterial retain(int increment) { OpenSslKeyMaterial touch();
if (leak != null) {
leak.record();
}
super.retain(increment);
return this;
}
@Override @Override
public OpenSslKeyMaterial touch() { OpenSslKeyMaterial touch(Object hint);
if (leak != null) {
leak.record();
}
super.touch();
return this;
}
@Override @Override
public OpenSslKeyMaterial touch(Object hint) { boolean release();
if (leak != null) {
leak.record(hint);
}
return this;
}
@Override @Override
public boolean release() { boolean release(int decrement);
if (leak != null) {
leak.record();
}
return super.release();
}
@Override
public boolean release(int decrement) {
if (leak != null) {
leak.record();
}
return super.release(decrement);
}
} }

View File

@ -62,10 +62,16 @@ class OpenSslKeyMaterialProvider {
long pkey = 0; long pkey = 0;
try { try {
chainBio = toBIO(allocator, encoded.retain()); chainBio = toBIO(allocator, encoded.retain());
pkeyBio = toBIO(allocator, key);
chain = SSL.parseX509Chain(chainBio); chain = SSL.parseX509Chain(chainBio);
pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);
OpenSslKeyMaterial keyMaterial = new OpenSslKeyMaterial(chain, pkey); OpenSslKeyMaterial keyMaterial;
if (key instanceof OpenSslPrivateKey) {
keyMaterial = ((OpenSslPrivateKey) key).toKeyMaterial(chain);
} else {
pkeyBio = toBIO(allocator, key);
pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);
keyMaterial = new DefaultOpenSslKeyMaterial(chain, pkey);
}
// See the chain and pkey to 0 so we will not release it as the ownership was // See the chain and pkey to 0 so we will not release it as the ownership was
// transferred to OpenSslKeyMaterial. // transferred to OpenSslKeyMaterial.

View File

@ -0,0 +1,190 @@
/*
* 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.internal.tcnative.SSL;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.IllegalReferenceCountException;
import javax.security.auth.Destroyable;
import java.security.PrivateKey;
final class OpenSslPrivateKey extends AbstractReferenceCounted implements PrivateKey {
private long privateKeyAddress;
OpenSslPrivateKey(long privateKeyAddress) {
this.privateKeyAddress = privateKeyAddress;
}
@Override
public String getAlgorithm() {
return "unkown";
}
@Override
public String getFormat() {
// As we do not support encoding we should return null as stated in the javadocs of PrivateKey.
return null;
}
@Override
public byte[] getEncoded() {
return null;
}
/**
* Returns the pointer to the {@code EVP_PKEY}.
*/
long privateKeyAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return privateKeyAddress;
}
@Override
protected void deallocate() {
SSL.freePrivateKey(privateKeyAddress);
privateKeyAddress = 0;
}
@Override
public OpenSslPrivateKey retain() {
super.retain();
return this;
}
@Override
public OpenSslPrivateKey retain(int increment) {
super.retain(increment);
return this;
}
@Override
public OpenSslPrivateKey touch() {
super.touch();
return this;
}
@Override
public OpenSslPrivateKey touch(Object hint) {
return this;
}
/**
* NOTE: This is a JDK8 interface/method. Due to backwards compatibility
* reasons it's not possible to slap the {@code @Override} annotation onto
* this method.
*
* @see Destroyable#destroy()
*/
public void destroy() {
release(refCnt());
}
/**
* NOTE: This is a JDK8 interface/method. Due to backwards compatibility
* reasons it's not possible to slap the {@code @Override} annotation onto
* this method.
*
* @see Destroyable#isDestroyed()
*/
public boolean isDestroyed() {
return refCnt() == 0;
}
/**
* Convert to a {@link OpenSslKeyMaterial}. Reference count of both is shared.
*/
OpenSslKeyMaterial toKeyMaterial(long certificateChain) {
return new OpenSslPrivateKeyMaterial(certificateChain);
}
private final class OpenSslPrivateKeyMaterial implements OpenSslKeyMaterial {
private long certificateChain;
OpenSslPrivateKeyMaterial(long certificateChain) {
this.certificateChain = certificateChain;
}
@Override
public long certificateChainAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return certificateChain;
}
@Override
public long privateKeyAddress() {
return OpenSslPrivateKey.this.privateKeyAddress();
}
@Override
public OpenSslKeyMaterial retain() {
OpenSslPrivateKey.this.retain();
return this;
}
@Override
public OpenSslKeyMaterial retain(int increment) {
OpenSslPrivateKey.this.retain(increment);
return this;
}
@Override
public OpenSslKeyMaterial touch() {
OpenSslPrivateKey.this.touch();
return this;
}
@Override
public OpenSslKeyMaterial touch(Object hint) {
OpenSslPrivateKey.this.touch(hint);
return this;
}
@Override
public boolean release() {
if (OpenSslPrivateKey.this.release()) {
releaseChain();
return true;
}
return false;
}
@Override
public boolean release(int decrement) {
if (OpenSslPrivateKey.this.release(decrement)) {
releaseChain();
return true;
}
return false;
}
private void releaseChain() {
SSL.freeX509Chain(certificateChain);
certificateChain = 0;
}
@Override
public int refCnt() {
return OpenSslPrivateKey.this.refCnt();
}
}
}

View File

@ -17,6 +17,7 @@ package io.netty.handler.ssl;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.internal.tcnative.SSL;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.ObjectUtil;
@ -25,13 +26,25 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManagerFactorySpi; import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.X509KeyManager; 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.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.UnrecoverableKeyException; 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.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -108,9 +121,8 @@ public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
} }
kmf.init(keyStore, chars); kmf.init(keyStore, chars);
providerFactory = new ProviderFactory( providerFactory = new ProviderFactory(ReferenceCountedOpenSslContext.chooseX509KeyManager(
ReferenceCountedOpenSslContext.chooseX509KeyManager(kmf.getKeyManagers()), kmf.getKeyManagers()), password(chars), Collections.list(keyStore.aliases()));
password(chars), Collections.list(keyStore.aliases()));
} }
private static String password(char[] password) { private static String password(char[] password) {
@ -218,4 +230,144 @@ public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
} }
} }
} }
/**
* Create a new initialized {@link OpenSslX509KeyManagerFactory} which loads its {@link PrivateKey} directly from
* an {@code OpenSSL engine} via the
* <a href="https://www.openssl.org/docs/man1.1.0/crypto/ENGINE_load_private_key.html">ENGINE_load_private_key</a>
* 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
* <a href="https://www.openssl.org/docs/man1.1.0/crypto/ENGINE_load_private_key.html">ENGINE_load_private_key</a>
* function.
*/
public static OpenSslX509KeyManagerFactory newEngineBased(X509Certificate[] certificateChain, String password)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore store = new OpenSslEngineKeyStore(certificateChain.clone());
store.load(null, null);
OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
factory.init(store, password == null ? null : password.toCharArray());
return factory;
}
private static final class OpenSslEngineKeyStore extends KeyStore {
private OpenSslEngineKeyStore(final X509Certificate[] certificateChain) {
super(new KeyStoreSpi() {
private final Date creationDate = new Date();
@Override
public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
if (engineContainsAlias(alias)) {
try {
return new OpenSslPrivateKey(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 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<String> 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();
}
}
} }

View File

@ -53,7 +53,6 @@ import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
@ -85,6 +84,8 @@ import java.util.List;
* </pre> * </pre>
*/ */
public abstract class SslContext { public abstract class SslContext {
static final String ALIAS = "key";
static final CertificateFactory X509_CERT_FACTORY; static final CertificateFactory X509_CERT_FACTORY;
static { static {
try { try {
@ -1000,7 +1001,7 @@ public abstract class SslContext {
CertificateException, IOException { CertificateException, IOException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null); ks.load(null, null);
ks.setKeyEntry("key", key, keyPasswordChars, certChain); ks.setKeyEntry(ALIAS, key, keyPasswordChars, certChain);
return ks; return ks;
} }

View File

@ -221,7 +221,7 @@
<!-- Fedora-"like" systems. This is currently only used for the netty-tcnative dependency --> <!-- Fedora-"like" systems. This is currently only used for the netty-tcnative dependency -->
<os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes> <os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes>
<tcnative.artifactId>netty-tcnative</tcnative.artifactId> <tcnative.artifactId>netty-tcnative</tcnative.artifactId>
<tcnative.version>2.0.12.Final</tcnative.version> <tcnative.version>2.0.13.Final</tcnative.version>
<tcnative.classifier>${os.detected.classifier}</tcnative.classifier> <tcnative.classifier>${os.detected.classifier}</tcnative.classifier>
<conscrypt.groupId>org.conscrypt</conscrypt.groupId> <conscrypt.groupId>org.conscrypt</conscrypt.groupId>
<conscrypt.artifactId>conscrypt-openjdk-uber</conscrypt.artifactId> <conscrypt.artifactId>conscrypt-openjdk-uber</conscrypt.artifactId>