Allow to cache keymaterial when using OpenSSL
Motiviation: During profiling it showed that a lot of time during the handshake is spent by parsing the key / chain over and over again. We should cache these parsed structures if possible to reduce the overhead during handshake. Modification: - Use new APIs provided by https://github.com/netty/netty-tcnative/pull/360. - Introduce OpensslStaticX509KeyManagerFactory which allows to wrap another KeyManagerFactory and caches the key material provided by it. Result: In benchmarks handshake times have improved by 30 %.
This commit is contained in:
parent
4a8d3a274c
commit
0337ecdcc8
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.net.ssl.X509KeyManager;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link OpenSslKeyMaterialProvider} that will cache the {@link OpenSslKeyMaterial} to reduce the overhead
|
||||||
|
* of parsing the chain and the key for generation of the material.
|
||||||
|
*/
|
||||||
|
final class OpenSslCachingKeyMaterialProvider extends OpenSslKeyMaterialProvider {
|
||||||
|
|
||||||
|
private final ConcurrentMap<String, OpenSslKeyMaterial> cache = new ConcurrentHashMap<String, OpenSslKeyMaterial>();
|
||||||
|
|
||||||
|
OpenSslCachingKeyMaterialProvider(X509KeyManager keyManager, String password) {
|
||||||
|
super(keyManager, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
|
||||||
|
OpenSslKeyMaterial material = cache.get(alias);
|
||||||
|
if (material == null) {
|
||||||
|
material = super.chooseKeyMaterial(allocator, alias);
|
||||||
|
if (material == null) {
|
||||||
|
// No keymaterial should be used.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSslKeyMaterial old = cache.putIfAbsent(alias, material);
|
||||||
|
if (old != null) {
|
||||||
|
material.release();
|
||||||
|
material = old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We need to call retain() as we want to always have at least a refCnt() of 1 before destroy() was called.
|
||||||
|
return material.retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void destroy() {
|
||||||
|
// Remove and release all entries.
|
||||||
|
do {
|
||||||
|
Iterator<OpenSslKeyMaterial> iterator = cache.values().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next().release();
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
} while (!cache.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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 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.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps another {@link KeyManagerFactory} and caches its chains / certs for an alias for better performance when using
|
||||||
|
* {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT}.
|
||||||
|
*
|
||||||
|
* Because of the caching its important that the wrapped {@link KeyManagerFactory}s {@link X509KeyManager}s always
|
||||||
|
* return the same {@link X509Certificate} chain and {@link PrivateKey} for the same alias.
|
||||||
|
*/
|
||||||
|
public final class OpenSslCachingX509KeyManagerFactory extends KeyManagerFactory {
|
||||||
|
|
||||||
|
public OpenSslCachingX509KeyManagerFactory(final KeyManagerFactory factory) {
|
||||||
|
super(new KeyManagerFactorySpi() {
|
||||||
|
@Override
|
||||||
|
protected void engineInit(KeyStore keyStore, char[] chars)
|
||||||
|
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
factory.init(keyStore, chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
|
||||||
|
throws InvalidAlgorithmParameterException {
|
||||||
|
factory.init(managerFactoryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected KeyManager[] engineGetKeyManagers() {
|
||||||
|
return factory.getKeyManagers();
|
||||||
|
}
|
||||||
|
}, factory.getProvider(), factory.getAlgorithm());
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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 javax.net.ssl.X509ExtendedKeyManager;
|
|
||||||
import javax.security.auth.x500.X500Principal;
|
|
||||||
|
|
||||||
final class OpenSslExtendedKeyMaterialManager extends OpenSslKeyMaterialManager {
|
|
||||||
|
|
||||||
private final X509ExtendedKeyManager keyManager;
|
|
||||||
|
|
||||||
OpenSslExtendedKeyMaterialManager(X509ExtendedKeyManager keyManager, String password) {
|
|
||||||
super(keyManager, password);
|
|
||||||
this.keyManager = keyManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String chooseClientAlias(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
|
|
||||||
X500Principal[] issuer) {
|
|
||||||
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) {
|
|
||||||
return keyManager.chooseEngineServerAlias(type, null, engine);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds references to the native key-material that is used by OpenSSL.
|
||||||
|
*/
|
||||||
|
final class OpenSslKeyMaterial extends AbstractReferenceCounted {
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public long certificateChainAddress() {
|
||||||
|
if (refCnt() <= 0) {
|
||||||
|
throw new IllegalReferenceCountException();
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pointer to the {@code EVP_PKEY}.
|
||||||
|
*/
|
||||||
|
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 OpenSslKeyMaterial retain() {
|
||||||
|
if (leak != null) {
|
||||||
|
leak.record();
|
||||||
|
}
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenSslKeyMaterial retain(int increment) {
|
||||||
|
if (leak != null) {
|
||||||
|
leak.record();
|
||||||
|
}
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenSslKeyMaterial touch() {
|
||||||
|
if (leak != null) {
|
||||||
|
leak.record();
|
||||||
|
}
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenSslKeyMaterial 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);
|
||||||
|
}
|
||||||
|
}
|
@ -15,11 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.ssl;
|
package io.netty.handler.ssl;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.internal.tcnative.CertificateRequestedCallback;
|
|
||||||
import io.netty.internal.tcnative.SSL;
|
import io.netty.internal.tcnative.SSL;
|
||||||
|
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509KeyManager;
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@ -29,14 +28,12 @@ import java.util.HashSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.freeBio;
|
|
||||||
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and
|
* Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and
|
||||||
* {@link X509Certificate}s.
|
* {@link X509Certificate}s.
|
||||||
*/
|
*/
|
||||||
class OpenSslKeyMaterialManager {
|
final class OpenSslKeyMaterialManager {
|
||||||
|
|
||||||
// Code in this class is inspired by code of conscrypts:
|
// Code in this class is inspired by code of conscrypts:
|
||||||
// - https://android.googlesource.com/platform/external/
|
// - https://android.googlesource.com/platform/external/
|
||||||
@ -62,15 +59,13 @@ class OpenSslKeyMaterialManager {
|
|||||||
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
|
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final X509KeyManager keyManager;
|
private final OpenSslKeyMaterialProvider provider;
|
||||||
private final String password;
|
|
||||||
|
|
||||||
OpenSslKeyMaterialManager(X509KeyManager keyManager, String password) {
|
OpenSslKeyMaterialManager(OpenSslKeyMaterialProvider provider) {
|
||||||
this.keyManager = keyManager;
|
this.provider = provider;
|
||||||
this.password = password;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeyMaterial(ReferenceCountedOpenSslEngine engine) throws SSLException {
|
void setKeyMaterialServerSide(ReferenceCountedOpenSslEngine engine) throws SSLException {
|
||||||
long ssl = engine.sslPointer();
|
long ssl = engine.sslPointer();
|
||||||
String[] authMethods = SSL.authenticationMethods(ssl);
|
String[] authMethods = SSL.authenticationMethods(ssl);
|
||||||
Set<String> aliases = new HashSet<String>(authMethods.length);
|
Set<String> aliases = new HashSet<String>(authMethods.length);
|
||||||
@ -79,101 +74,62 @@ class OpenSslKeyMaterialManager {
|
|||||||
if (type != null) {
|
if (type != null) {
|
||||||
String alias = chooseServerAlias(engine, type);
|
String alias = chooseServerAlias(engine, type);
|
||||||
if (alias != null && aliases.add(alias)) {
|
if (alias != null && aliases.add(alias)) {
|
||||||
setKeyMaterial(ssl, alias, engine.alloc);
|
OpenSslKeyMaterial keyMaterial = null;
|
||||||
|
try {
|
||||||
|
keyMaterial = provider.chooseKeyMaterial(engine.alloc, alias);
|
||||||
|
if (keyMaterial != null) {
|
||||||
|
SSL.setKeyMaterialServerSide(
|
||||||
|
ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
|
||||||
|
}
|
||||||
|
} catch (SSLException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SSLException(e);
|
||||||
|
} finally {
|
||||||
|
if (keyMaterial != null) {
|
||||||
|
keyMaterial.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CertificateRequestedCallback.KeyMaterial keyMaterial(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
|
void setKeyMaterialClientSide(ReferenceCountedOpenSslEngine engine, long certOut, long keyOut, String[] keyTypes,
|
||||||
X500Principal[] issuer) throws SSLException {
|
X500Principal[] issuer) throws SSLException {
|
||||||
String alias = chooseClientAlias(engine, keyTypes, issuer);
|
String alias = chooseClientAlias(engine, keyTypes, issuer);
|
||||||
long keyBio = 0;
|
OpenSslKeyMaterial keyMaterial = null;
|
||||||
long keyCertChainBio = 0;
|
|
||||||
long pkey = 0;
|
|
||||||
long certChain = 0;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: Should we cache these and so not need to do a memory copy all the time ?
|
keyMaterial = provider.chooseKeyMaterial(engine.alloc, alias);
|
||||||
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
|
if (keyMaterial != null) {
|
||||||
if (certificates == null || certificates.length == 0) {
|
SSL.setKeyMaterialClientSide(engine.sslPointer(), certOut, keyOut,
|
||||||
return null;
|
keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
|
||||||
}
|
|
||||||
|
|
||||||
PrivateKey key = keyManager.getPrivateKey(alias);
|
|
||||||
keyCertChainBio = toBIO(engine.alloc, certificates);
|
|
||||||
certChain = SSL.parseX509Chain(keyCertChainBio);
|
|
||||||
if (key != null) {
|
|
||||||
keyBio = toBIO(engine.alloc, key);
|
|
||||||
pkey = SSL.parsePrivateKey(keyBio, password);
|
|
||||||
}
|
|
||||||
CertificateRequestedCallback.KeyMaterial material = new CertificateRequestedCallback.KeyMaterial(
|
|
||||||
certChain, pkey);
|
|
||||||
|
|
||||||
// Reset to 0 so we do not free these. This is needed as the client certificate callback takes ownership
|
|
||||||
// of both the key and the certificate if they are returned from this method, and thus must not
|
|
||||||
// be freed here.
|
|
||||||
certChain = pkey = 0;
|
|
||||||
return material;
|
|
||||||
} catch (SSLException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException(e);
|
|
||||||
} finally {
|
|
||||||
freeBio(keyBio);
|
|
||||||
freeBio(keyCertChainBio);
|
|
||||||
SSL.freePrivateKey(pkey);
|
|
||||||
SSL.freeX509Chain(certChain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setKeyMaterial(long ssl, String alias, ByteBufAllocator allocator) throws SSLException {
|
|
||||||
long keyBio = 0;
|
|
||||||
long keyCertChainBio = 0;
|
|
||||||
long keyCertChainBio2 = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: Should we cache these and so not need to do a memory copy all the time ?
|
|
||||||
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
|
|
||||||
if (certificates == null || certificates.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivateKey key = keyManager.getPrivateKey(alias);
|
|
||||||
|
|
||||||
// Only encode one time
|
|
||||||
PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates);
|
|
||||||
try {
|
|
||||||
keyCertChainBio = toBIO(allocator, encoded.retain());
|
|
||||||
keyCertChainBio2 = toBIO(allocator, encoded.retain());
|
|
||||||
|
|
||||||
if (key != null) {
|
|
||||||
keyBio = toBIO(allocator, key);
|
|
||||||
}
|
|
||||||
SSL.setCertificateBio(ssl, keyCertChainBio, keyBio, password);
|
|
||||||
|
|
||||||
// We may have more then one cert in the chain so add all of them now.
|
|
||||||
SSL.setCertificateChainBio(ssl, keyCertChainBio2, true);
|
|
||||||
} finally {
|
|
||||||
encoded.release();
|
|
||||||
}
|
}
|
||||||
} catch (SSLException e) {
|
} catch (SSLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new SSLException(e);
|
throw new SSLException(e);
|
||||||
} finally {
|
} finally {
|
||||||
freeBio(keyBio);
|
if (keyMaterial != null) {
|
||||||
freeBio(keyCertChainBio);
|
keyMaterial.release();
|
||||||
freeBio(keyCertChainBio2);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String chooseClientAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine,
|
private String chooseClientAlias(ReferenceCountedOpenSslEngine engine,
|
||||||
String[] keyTypes, X500Principal[] issuer) {
|
String[] keyTypes, X500Principal[] issuer) {
|
||||||
return keyManager.chooseClientAlias(keyTypes, issuer, null);
|
X509KeyManager manager = provider.keyManager();
|
||||||
|
if (manager instanceof X509ExtendedKeyManager) {
|
||||||
|
return ((X509ExtendedKeyManager) manager).chooseEngineClientAlias(keyTypes, issuer, engine);
|
||||||
|
}
|
||||||
|
return manager.chooseClientAlias(keyTypes, issuer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String chooseServerAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, String type) {
|
private String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) {
|
||||||
return keyManager.chooseServerAlias(type, null, null);
|
X509KeyManager manager = provider.keyManager();
|
||||||
|
if (manager instanceof X509ExtendedKeyManager) {
|
||||||
|
return ((X509ExtendedKeyManager) manager).chooseEngineServerAlias(type, null, engine);
|
||||||
|
}
|
||||||
|
return manager.chooseServerAlias(type, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.internal.tcnative.SSL;
|
||||||
|
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides {@link OpenSslKeyMaterial} for a given alias.
|
||||||
|
*/
|
||||||
|
class OpenSslKeyMaterialProvider {
|
||||||
|
|
||||||
|
private final X509KeyManager keyManager;
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
OpenSslKeyMaterialProvider(X509KeyManager keyManager, String password) {
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the underlying {@link X509KeyManager} that is used.
|
||||||
|
*/
|
||||||
|
X509KeyManager keyManager() {
|
||||||
|
return keyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OpenSslKeyMaterial} or {@code null} (if none) that should be used during the handshake by
|
||||||
|
* OpenSSL.
|
||||||
|
*/
|
||||||
|
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
|
||||||
|
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
|
||||||
|
if (certificates == null || certificates.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateKey key = keyManager.getPrivateKey(alias);
|
||||||
|
PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates);
|
||||||
|
long chainBio = 0;
|
||||||
|
long pkeyBio = 0;
|
||||||
|
long chain = 0;
|
||||||
|
long pkey = 0;
|
||||||
|
try {
|
||||||
|
chainBio = toBIO(allocator, encoded.retain());
|
||||||
|
pkeyBio = toBIO(allocator, key);
|
||||||
|
chain = SSL.parseX509Chain(chainBio);
|
||||||
|
pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);
|
||||||
|
OpenSslKeyMaterial keyMaterial = new OpenSslKeyMaterial(chain, pkey);
|
||||||
|
|
||||||
|
// See the chain and pkey to 0 so we will not release it as the ownership was
|
||||||
|
// transferred to OpenSslKeyMaterial.
|
||||||
|
chain = 0;
|
||||||
|
pkey = 0;
|
||||||
|
return keyMaterial;
|
||||||
|
} finally {
|
||||||
|
SSL.freeBIO(chainBio);
|
||||||
|
SSL.freeBIO(pkeyBio);
|
||||||
|
if (chain != 0) {
|
||||||
|
SSL.freeX509Chain(chain);
|
||||||
|
}
|
||||||
|
if (pkey != 0) {
|
||||||
|
SSL.freePrivateKey(pkey);
|
||||||
|
}
|
||||||
|
encoded.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be invoked once the provider should be destroyed.
|
||||||
|
*/
|
||||||
|
void destroy() {
|
||||||
|
// NOOP.
|
||||||
|
}
|
||||||
|
}
|
@ -25,8 +25,8 @@ import java.util.concurrent.locks.Lock;
|
|||||||
* {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side.
|
* {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side.
|
||||||
*/
|
*/
|
||||||
public final class OpenSslServerSessionContext extends OpenSslSessionContext {
|
public final class OpenSslServerSessionContext extends OpenSslSessionContext {
|
||||||
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context) {
|
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||||
super(context);
|
super(context, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,14 +34,21 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
|||||||
private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
|
private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
|
||||||
|
|
||||||
private final OpenSslSessionStats stats;
|
private final OpenSslSessionStats stats;
|
||||||
|
|
||||||
|
// The OpenSslKeyMaterialProvider is not really used by the OpenSslSessionContext but only be stored here
|
||||||
|
// to make it easier to destroy it later because the ReferenceCountedOpenSslContext will hold a reference
|
||||||
|
// to OpenSslSessionContext.
|
||||||
|
private final OpenSslKeyMaterialProvider provider;
|
||||||
|
|
||||||
final ReferenceCountedOpenSslContext context;
|
final ReferenceCountedOpenSslContext context;
|
||||||
|
|
||||||
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
|
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
|
||||||
// the GC to collect OpenSslContext as this would also free the pointer and so could result in a
|
// the GC to collect OpenSslContext as this would also free the pointer and so could result in a
|
||||||
// segfault when the user calls any of the methods here that try to pass the pointer down to the native
|
// segfault when the user calls any of the methods here that try to pass the pointer down to the native
|
||||||
// level.
|
// level.
|
||||||
OpenSslSessionContext(ReferenceCountedOpenSslContext context) {
|
OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.provider = provider;
|
||||||
stats = new OpenSslSessionStats(context);
|
stats = new OpenSslSessionStats(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +130,12 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final void destroy() {
|
||||||
|
if (provider != null) {
|
||||||
|
provider.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class EmptyEnumeration implements Enumeration<byte[]> {
|
private static final class EmptyEnumeration implements Enumeration<byte[]> {
|
||||||
@Override
|
@Override
|
||||||
public boolean hasMoreElements() {
|
public boolean hasMoreElements() {
|
||||||
|
@ -31,9 +31,7 @@ import javax.net.ssl.KeyManagerFactory;
|
|||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.net.ssl.X509KeyManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
@ -90,71 +88,82 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Either both keyCertChain and key needs to be null or none of them");
|
"Either both keyCertChain and key needs to be null or none of them");
|
||||||
}
|
}
|
||||||
|
OpenSslKeyMaterialProvider keyMaterialProvider = null;
|
||||||
try {
|
try {
|
||||||
if (!OpenSsl.useKeyManagerFactory()) {
|
try {
|
||||||
if (keyManagerFactory != null) {
|
if (!OpenSsl.useKeyManagerFactory()) {
|
||||||
throw new IllegalArgumentException(
|
if (keyManagerFactory != null) {
|
||||||
"KeyManagerFactory not supported");
|
throw new IllegalArgumentException(
|
||||||
}
|
"KeyManagerFactory not supported");
|
||||||
if (keyCertChain != null/* && key != null*/) {
|
}
|
||||||
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
if (keyCertChain != null/* && key != null*/) {
|
||||||
}
|
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
||||||
} else {
|
}
|
||||||
// javadocs state that keyManagerFactory has precedent over keyCertChain
|
} else {
|
||||||
if (keyManagerFactory == null && keyCertChain != null) {
|
// javadocs state that keyManagerFactory has precedent over keyCertChain
|
||||||
keyManagerFactory = buildKeyManagerFactory(
|
if (keyManagerFactory == null && keyCertChain != null) {
|
||||||
keyCertChain, key, keyPassword, keyManagerFactory);
|
keyMaterialProvider = new OpenSslCachingKeyMaterialProvider(
|
||||||
}
|
chooseX509KeyManager(buildKeyManagerFactory(keyCertChain, key, keyPassword, null)
|
||||||
|
.getKeyManagers()), keyPassword);
|
||||||
|
} else if (keyManagerFactory != null) {
|
||||||
|
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
if (keyManagerFactory != null) {
|
if (keyMaterialProvider != null) {
|
||||||
X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
|
OpenSslKeyMaterialManager materialManager = new OpenSslKeyMaterialManager(keyMaterialProvider);
|
||||||
OpenSslKeyMaterialManager materialManager = useExtendedKeyManager(keyManager) ?
|
SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
|
||||||
new OpenSslExtendedKeyMaterialManager(
|
engineMap, materialManager));
|
||||||
(X509ExtendedKeyManager) keyManager, keyPassword) :
|
}
|
||||||
new OpenSslKeyMaterialManager(keyManager, keyPassword);
|
|
||||||
SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
|
|
||||||
engineMap, materialManager));
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SSLException("failed to set certificate and key", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (trustCertCollection != null) {
|
||||||
|
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
|
||||||
|
} else if (trustManagerFactory == null) {
|
||||||
|
trustManagerFactory = TrustManagerFactory.getInstance(
|
||||||
|
TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init((KeyStore) null);
|
||||||
|
}
|
||||||
|
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
|
||||||
|
|
||||||
|
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
|
||||||
|
// otherwise the context can never be collected. This is because the JNI code holds
|
||||||
|
// a global reference to the callbacks.
|
||||||
|
//
|
||||||
|
// See https://github.com/netty/netty/issues/5372
|
||||||
|
|
||||||
|
// Use this to prevent an error when running on java < 7
|
||||||
|
if (useExtendedTrustManager(manager)) {
|
||||||
|
SSLContext.setCertVerifyCallback(ctx,
|
||||||
|
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
|
||||||
|
} else {
|
||||||
|
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (keyMaterialProvider != null) {
|
||||||
|
keyMaterialProvider.destroy();
|
||||||
|
}
|
||||||
|
throw new SSLException("unable to setup trustmanager", e);
|
||||||
|
}
|
||||||
|
OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider);
|
||||||
|
keyMaterialProvider = null;
|
||||||
|
return context;
|
||||||
|
} finally {
|
||||||
|
if (keyMaterialProvider != null) {
|
||||||
|
keyMaterialProvider.destroy();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException("failed to set certificate and key", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (trustCertCollection != null) {
|
|
||||||
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
|
|
||||||
} else if (trustManagerFactory == null) {
|
|
||||||
trustManagerFactory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
trustManagerFactory.init((KeyStore) null);
|
|
||||||
}
|
|
||||||
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
|
|
||||||
|
|
||||||
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
|
|
||||||
// otherwise the context can never be collected. This is because the JNI code holds
|
|
||||||
// a global reference to the callbacks.
|
|
||||||
//
|
|
||||||
// See https://github.com/netty/netty/issues/5372
|
|
||||||
|
|
||||||
// Use this to prevent an error when running on java < 7
|
|
||||||
if (useExtendedTrustManager(manager)) {
|
|
||||||
SSLContext.setCertVerifyCallback(ctx,
|
|
||||||
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
|
|
||||||
} else {
|
|
||||||
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException("unable to setup trustmanager", e);
|
|
||||||
}
|
|
||||||
return new OpenSslClientSessionContext(thiz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cache is currently supported for client side mode.
|
// No cache is currently supported for client side mode.
|
||||||
static final class OpenSslClientSessionContext extends OpenSslSessionContext {
|
static final class OpenSslClientSessionContext extends OpenSslSessionContext {
|
||||||
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context) {
|
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
|
||||||
super(context);
|
super(context, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -232,7 +241,8 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyMaterial requested(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
|
public void requested(
|
||||||
|
long ssl, long certOut, long keyOut, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
|
||||||
final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
|
final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
|
||||||
try {
|
try {
|
||||||
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
|
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
|
||||||
@ -246,13 +256,12 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
|||||||
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
|
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keyManagerHolder.keyMaterial(engine, keyTypes, issuers);
|
keyManagerHolder.setKeyMaterialClientSide(engine, certOut, keyOut, keyTypes, issuers);
|
||||||
} catch (Throwable cause) {
|
} catch (Throwable cause) {
|
||||||
logger.debug("request of key failed", cause);
|
logger.debug("request of key failed", cause);
|
||||||
SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
|
SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
|
||||||
e.initCause(cause);
|
e.initCause(cause);
|
||||||
engine.handshakeException = e;
|
engine.handshakeException = e;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +49,11 @@ import java.util.concurrent.locks.ReadWriteLock;
|
|||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.net.ssl.X509KeyManager;
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
@ -486,6 +486,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
|||||||
|
|
||||||
SSLContext.free(ctx);
|
SSLContext.free(ctx);
|
||||||
ctx = 0;
|
ctx = 0;
|
||||||
|
sessionContext().destroy();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
writerLock.unlock();
|
writerLock.unlock();
|
||||||
@ -566,10 +567,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
|||||||
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
|
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean useExtendedKeyManager(X509KeyManager keyManager) {
|
|
||||||
return PlatformDependent.javaVersion() >= 7 && keyManager instanceof X509ExtendedKeyManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int refCnt() {
|
public final int refCnt() {
|
||||||
return refCnt.refCnt();
|
return refCnt.refCnt();
|
||||||
@ -819,4 +816,19 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
|||||||
buffer.release();
|
buffer.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link OpenSslKeyMaterialProvider} that should be used for OpenSSL. Depending on the given
|
||||||
|
* {@link KeyManagerFactory} this may cache the {@link OpenSslKeyMaterial} for better performance if it can
|
||||||
|
* ensure that the same material is always returned for the same alias.
|
||||||
|
*/
|
||||||
|
static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) {
|
||||||
|
X509KeyManager keyManager = chooseX509KeyManager(factory.getKeyManagers());
|
||||||
|
if (factory instanceof OpenSslCachingX509KeyManagerFactory) {
|
||||||
|
// The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache.
|
||||||
|
return new OpenSslCachingKeyMaterialProvider(keyManager, password);
|
||||||
|
}
|
||||||
|
// We can not be sure if the material may change at runtime so we will not cache it.
|
||||||
|
return new OpenSslKeyMaterialProvider(keyManager, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1567,7 +1567,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
|
|
||||||
if (!certificateSet && keyMaterialManager != null) {
|
if (!certificateSet && keyMaterialManager != null) {
|
||||||
certificateSet = true;
|
certificateSet = true;
|
||||||
keyMaterialManager.setKeyMaterial(this);
|
keyMaterialManager.setKeyMaterialServerSide(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
int code = SSL.doHandshake(ssl);
|
int code = SSL.doHandshake(ssl);
|
||||||
|
@ -29,9 +29,7 @@ import java.security.cert.X509Certificate;
|
|||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.net.ssl.X509KeyManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
@ -107,87 +105,97 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
|
|||||||
String keyPassword, KeyManagerFactory keyManagerFactory)
|
String keyPassword, KeyManagerFactory keyManagerFactory)
|
||||||
throws SSLException {
|
throws SSLException {
|
||||||
ServerContext result = new ServerContext();
|
ServerContext result = new ServerContext();
|
||||||
|
OpenSslKeyMaterialProvider keyMaterialProvider = null;
|
||||||
try {
|
try {
|
||||||
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
try {
|
||||||
if (!OpenSsl.useKeyManagerFactory()) {
|
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
|
||||||
if (keyManagerFactory != null) {
|
if (!OpenSsl.useKeyManagerFactory()) {
|
||||||
throw new IllegalArgumentException(
|
if (keyManagerFactory != null) {
|
||||||
"KeyManagerFactory not supported");
|
throw new IllegalArgumentException(
|
||||||
}
|
"KeyManagerFactory not supported");
|
||||||
checkNotNull(keyCertChain, "keyCertChain");
|
|
||||||
|
|
||||||
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
|
||||||
} else {
|
|
||||||
// javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a
|
|
||||||
// keyManagerFactory for the server so build one if it is not specified.
|
|
||||||
if (keyManagerFactory == null) {
|
|
||||||
keyManagerFactory = buildKeyManagerFactory(
|
|
||||||
keyCertChain, key, keyPassword, keyManagerFactory);
|
|
||||||
}
|
|
||||||
X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
|
|
||||||
result.keyMaterialManager = useExtendedKeyManager(keyManager) ?
|
|
||||||
new OpenSslExtendedKeyMaterialManager(
|
|
||||||
(X509ExtendedKeyManager) keyManager, keyPassword) :
|
|
||||||
new OpenSslKeyMaterialManager(keyManager, keyPassword);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException("failed to set certificate and key", e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (trustCertCollection != null) {
|
|
||||||
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
|
|
||||||
} else if (trustManagerFactory == null) {
|
|
||||||
// Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
|
|
||||||
trustManagerFactory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
trustManagerFactory.init((KeyStore) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
|
|
||||||
|
|
||||||
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
|
|
||||||
// otherwise the context can never be collected. This is because the JNI code holds
|
|
||||||
// a global reference to the callbacks.
|
|
||||||
//
|
|
||||||
// See https://github.com/netty/netty/issues/5372
|
|
||||||
|
|
||||||
// Use this to prevent an error when running on java < 7
|
|
||||||
if (useExtendedTrustManager(manager)) {
|
|
||||||
SSLContext.setCertVerifyCallback(ctx,
|
|
||||||
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
|
|
||||||
} else {
|
|
||||||
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
X509Certificate[] issuers = manager.getAcceptedIssuers();
|
|
||||||
if (issuers != null && issuers.length > 0) {
|
|
||||||
long bio = 0;
|
|
||||||
try {
|
|
||||||
bio = toBIO(ByteBufAllocator.DEFAULT, issuers);
|
|
||||||
if (!SSLContext.setCACertificateBio(ctx, bio)) {
|
|
||||||
throw new SSLException("unable to setup accepted issuers for trustmanager " + manager);
|
|
||||||
}
|
}
|
||||||
} finally {
|
checkNotNull(keyCertChain, "keyCertChain");
|
||||||
freeBio(bio);
|
|
||||||
|
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
|
||||||
|
} else {
|
||||||
|
// javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a
|
||||||
|
// keyManagerFactory for the server so build one if it is not specified.
|
||||||
|
if (keyManagerFactory == null) {
|
||||||
|
keyMaterialProvider = new OpenSslCachingKeyMaterialProvider(
|
||||||
|
chooseX509KeyManager(buildKeyManagerFactory(keyCertChain, key, keyPassword, null)
|
||||||
|
.getKeyManagers()), keyPassword);
|
||||||
|
} else {
|
||||||
|
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.keyMaterialManager = new OpenSslKeyMaterialManager(keyMaterialProvider);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SSLException("failed to set certificate and key", e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if (trustCertCollection != null) {
|
||||||
|
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
|
||||||
|
} else if (trustManagerFactory == null) {
|
||||||
|
// Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
|
||||||
|
trustManagerFactory = TrustManagerFactory.getInstance(
|
||||||
|
TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init((KeyStore) null);
|
||||||
|
}
|
||||||
|
|
||||||
if (PlatformDependent.javaVersion() >= 8) {
|
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
|
||||||
// Only do on Java8+ as SNIMatcher is not supported in earlier releases.
|
|
||||||
// IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as
|
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
|
||||||
// otherwise the context can never be collected. This is because the JNI code holds
|
// otherwise the context can never be collected. This is because the JNI code holds
|
||||||
// a global reference to the matcher.
|
// a global reference to the callbacks.
|
||||||
SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap));
|
//
|
||||||
}
|
// See https://github.com/netty/netty/issues/5372
|
||||||
} catch (SSLException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException("unable to setup trustmanager", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.sessionContext = new OpenSslServerSessionContext(thiz);
|
// Use this to prevent an error when running on java < 7
|
||||||
result.sessionContext.setSessionIdContext(ID);
|
if (useExtendedTrustManager(manager)) {
|
||||||
return result;
|
SSLContext.setCertVerifyCallback(ctx,
|
||||||
|
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
|
||||||
|
} else {
|
||||||
|
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
X509Certificate[] issuers = manager.getAcceptedIssuers();
|
||||||
|
if (issuers != null && issuers.length > 0) {
|
||||||
|
long bio = 0;
|
||||||
|
try {
|
||||||
|
bio = toBIO(ByteBufAllocator.DEFAULT, issuers);
|
||||||
|
if (!SSLContext.setCACertificateBio(ctx, bio)) {
|
||||||
|
throw new SSLException("unable to setup accepted issuers for trustmanager " + manager);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
freeBio(bio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlatformDependent.javaVersion() >= 8) {
|
||||||
|
// Only do on Java8+ as SNIMatcher is not supported in earlier releases.
|
||||||
|
// IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as
|
||||||
|
// otherwise the context can never be collected. This is because the JNI code holds
|
||||||
|
// a global reference to the matcher.
|
||||||
|
SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap));
|
||||||
|
}
|
||||||
|
} catch (SSLException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SSLException("unable to setup trustmanager", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider);
|
||||||
|
result.sessionContext.setSessionIdContext(ID);
|
||||||
|
|
||||||
|
keyMaterialProvider = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
if (keyMaterialProvider != null) {
|
||||||
|
keyMaterialProvider.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
|
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
|
||||||
|
@ -121,6 +121,9 @@ public final class SslContextBuilder {
|
|||||||
/**
|
/**
|
||||||
* Creates a builder for new server-side {@link SslContext}.
|
* Creates a builder for new server-side {@link SslContext}.
|
||||||
*
|
*
|
||||||
|
* If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
|
||||||
|
* {@link OpenSslCachingX509KeyManagerFactory}.
|
||||||
|
*
|
||||||
* @param keyManagerFactory non-{@code null} factory for server's private key
|
* @param keyManagerFactory non-{@code null} factory for server's private key
|
||||||
* @see #keyManager(KeyManagerFactory)
|
* @see #keyManager(KeyManagerFactory)
|
||||||
*/
|
*/
|
||||||
@ -335,6 +338,9 @@ public final class SslContextBuilder {
|
|||||||
* if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
|
* if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
|
||||||
* {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
|
* {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
|
||||||
* you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
|
* you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
|
||||||
|
*
|
||||||
|
* If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
|
||||||
|
* {@link OpenSslCachingX509KeyManagerFactory}.
|
||||||
*/
|
*/
|
||||||
public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
|
public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
|
||||||
if (forServer) {
|
if (forServer) {
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.UnpooledByteBufAllocator;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class OpenSslCachingKeyMaterialProviderTest extends OpenSslKeyMaterialProviderTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected KeyManagerFactory newKeyManagerFactory() throws Exception {
|
||||||
|
return new OpenSslCachingX509KeyManagerFactory(super.newKeyManagerFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected OpenSslKeyMaterialProvider newMaterialProvider(X509KeyManager manager, String password) {
|
||||||
|
return new OpenSslCachingKeyMaterialProvider(manager, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assertRelease(OpenSslKeyMaterial material) {
|
||||||
|
Assert.assertFalse(material.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<? extends OpenSslKeyMaterialProvider> providerClass() {
|
||||||
|
return OpenSslCachingKeyMaterialProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaterialCached() throws Exception {
|
||||||
|
X509KeyManager manager = ReferenceCountedOpenSslContext.chooseX509KeyManager(
|
||||||
|
newKeyManagerFactory().getKeyManagers());
|
||||||
|
OpenSslKeyMaterialProvider provider = newMaterialProvider(manager, PASSWORD);
|
||||||
|
|
||||||
|
OpenSslKeyMaterial material = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
|
||||||
|
assertNotNull(material);
|
||||||
|
assertNotEquals(0, material.certificateChainAddress());
|
||||||
|
assertNotEquals(0, material.privateKeyAddress());
|
||||||
|
assertEquals(2, material.refCnt());
|
||||||
|
|
||||||
|
OpenSslKeyMaterial material2 = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
|
||||||
|
assertNotNull(material2);
|
||||||
|
assertEquals(material.certificateChainAddress(), material2.certificateChainAddress());
|
||||||
|
assertEquals(material.privateKeyAddress(), material2.privateKeyAddress());
|
||||||
|
assertEquals(3, material.refCnt());
|
||||||
|
assertEquals(3, material2.refCnt());
|
||||||
|
|
||||||
|
assertFalse(material.release());
|
||||||
|
assertFalse(material2.release());
|
||||||
|
|
||||||
|
// After this the material should have been released.
|
||||||
|
provider.destroy();
|
||||||
|
|
||||||
|
assertEquals(0, material.refCnt());
|
||||||
|
assertEquals(0, material2.refCnt());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.UnpooledByteBufAllocator;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
public class OpenSslKeyMaterialProviderTest {
|
||||||
|
|
||||||
|
static final String PASSWORD = "example";
|
||||||
|
static final String EXISTING_ALIAS = "1";
|
||||||
|
private static final String NON_EXISTING_ALIAS = "nonexisting";
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void checkOpenSsl() {
|
||||||
|
assumeTrue(OpenSsl.isAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KeyManagerFactory newKeyManagerFactory() throws Exception {
|
||||||
|
char[] password = PASSWORD.toCharArray();
|
||||||
|
final KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||||
|
keystore.load(getClass().getResourceAsStream("mutual_auth_server.p12"), password);
|
||||||
|
|
||||||
|
KeyManagerFactory kmf =
|
||||||
|
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmf.init(keystore, password);
|
||||||
|
return kmf;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OpenSslKeyMaterialProvider newMaterialProvider(X509KeyManager manager, String password) {
|
||||||
|
return new OpenSslKeyMaterialProvider(manager, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class<? extends OpenSslKeyMaterialProvider> providerClass() {
|
||||||
|
return OpenSslKeyMaterialProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRelease(OpenSslKeyMaterial material) {
|
||||||
|
assertTrue(material.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChooseKeyMaterial() throws Exception {
|
||||||
|
X509KeyManager manager = ReferenceCountedOpenSslContext.chooseX509KeyManager(
|
||||||
|
newKeyManagerFactory().getKeyManagers());
|
||||||
|
OpenSslKeyMaterialProvider provider = newMaterialProvider(manager, PASSWORD);
|
||||||
|
OpenSslKeyMaterial nonExistingMaterial = provider.chooseKeyMaterial(
|
||||||
|
UnpooledByteBufAllocator.DEFAULT, NON_EXISTING_ALIAS);
|
||||||
|
assertNull(nonExistingMaterial);
|
||||||
|
|
||||||
|
OpenSslKeyMaterial material = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
|
||||||
|
assertNotNull(material);
|
||||||
|
assertNotEquals(0, material.certificateChainAddress());
|
||||||
|
assertNotEquals(0, material.privateKeyAddress());
|
||||||
|
assertRelease(material);
|
||||||
|
|
||||||
|
provider.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChooseTheCorrectProvider() throws Exception {
|
||||||
|
OpenSslKeyMaterialProvider provider = ReferenceCountedOpenSslContext.providerFor(
|
||||||
|
newKeyManagerFactory(), PASSWORD);
|
||||||
|
assertEquals(providerClass(), provider.getClass());
|
||||||
|
provider.destroy();
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
@ -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.10.Final</tcnative.version>
|
<tcnative.version>2.0.11.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>
|
||||||
|
Loading…
Reference in New Issue
Block a user