[openssl] fix refcount bug in OpenSslPrivateKeyMaterial ctor

Motivation:

Subclasses of `OpenSslKeyMaterial` implement `ReferenceCounted`. This means that a new object should have an initial refcount of 1. An `OpenSslPrivateKey.OpenSslPrivateKeyMaterial` object shares its refcount with the enclosing `OpenSslPrivateKey` object. This means the enclosing object's refcount must be incremented by 1 when an instance of `OpenSslPrivateKey.OpenSslPrivateKeyMaterial` is created. Otherwise, when the key material object is `release()`-ed, the refcount on the enclosing object will drop to 0 while it is still in use.

Modification:

- Increment the refcount in the constructor of `OpenSslPrivateKey.OpenSslPrivateKeyMaterial`
- Ensure we also always release the native certificates as well.

Result:

Refcount is now correct.
This commit is contained in:
Ilya Maykov 2019-04-26 15:59:30 -07:00 committed by Norman Maurer
parent a4b05694d3
commit 1cd9f5e17b
3 changed files with 125 additions and 38 deletions

View File

@ -120,7 +120,7 @@ class OpenSslKeyMaterialProvider {
OpenSslKeyMaterial keyMaterial; OpenSslKeyMaterial keyMaterial;
if (key instanceof OpenSslPrivateKey) { if (key instanceof OpenSslPrivateKey) {
keyMaterial = ((OpenSslPrivateKey) key).toKeyMaterial(chain, certificates); keyMaterial = ((OpenSslPrivateKey) key).newKeyMaterial(chain, certificates);
} else { } else {
pkeyBio = toBIO(allocator, key); pkeyBio = toBIO(allocator, key);
pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password); pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);

View File

@ -34,7 +34,7 @@ final class OpenSslPrivateKey extends AbstractReferenceCounted implements Privat
@Override @Override
public String getAlgorithm() { public String getAlgorithm() {
return "unkown"; return "unknown";
} }
@Override @Override
@ -48,10 +48,7 @@ final class OpenSslPrivateKey extends AbstractReferenceCounted implements Privat
return null; return null;
} }
/** private long privateKeyAddress() {
* Returns the pointer to the {@code EVP_PKEY}.
*/
long privateKeyAddress() {
if (refCnt() <= 0) { if (refCnt() <= 0) {
throw new IllegalReferenceCountException(); throw new IllegalReferenceCountException();
} }
@ -110,21 +107,27 @@ final class OpenSslPrivateKey extends AbstractReferenceCounted implements Privat
} }
/** /**
* Convert to a {@link OpenSslKeyMaterial}. Reference count of both is shared. * Create a new {@link OpenSslKeyMaterial} which uses the private key that is held by {@link OpenSslPrivateKey}.
*
* When the material is created we increment the reference count of the enclosing {@link OpenSslPrivateKey} and
* decrement it again when the reference count of the {@link OpenSslKeyMaterial} reaches {@code 0}.
*/ */
OpenSslKeyMaterial toKeyMaterial(long certificateChain, X509Certificate[] chain) { OpenSslKeyMaterial newKeyMaterial(long certificateChain, X509Certificate[] chain) {
return new OpenSslPrivateKeyMaterial(certificateChain, chain); return new OpenSslPrivateKeyMaterial(certificateChain, chain);
} }
private final class OpenSslPrivateKeyMaterial implements OpenSslKeyMaterial { // Package-private for unit-test only
final class OpenSslPrivateKeyMaterial extends AbstractReferenceCounted implements OpenSslKeyMaterial {
private long certificateChain; // Package-private for unit-test only
long certificateChain;
private final X509Certificate[] x509CertificateChain; private final X509Certificate[] x509CertificateChain;
OpenSslPrivateKeyMaterial(long certificateChain, X509Certificate[] x509CertificateChain) { OpenSslPrivateKeyMaterial(long certificateChain, X509Certificate[] x509CertificateChain) {
this.certificateChain = certificateChain; this.certificateChain = certificateChain;
this.x509CertificateChain = x509CertificateChain == null ? this.x509CertificateChain = x509CertificateChain == null ?
EmptyArrays.EMPTY_X509_CERTIFICATES : x509CertificateChain; EmptyArrays.EMPTY_X509_CERTIFICATES : x509CertificateChain;
OpenSslPrivateKey.this.retain();
} }
@Override @Override
@ -142,18 +145,27 @@ final class OpenSslPrivateKey extends AbstractReferenceCounted implements Privat
@Override @Override
public long privateKeyAddress() { public long privateKeyAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return OpenSslPrivateKey.this.privateKeyAddress(); return OpenSslPrivateKey.this.privateKeyAddress();
} }
@Override
public OpenSslKeyMaterial touch(Object hint) {
OpenSslPrivateKey.this.touch(hint);
return this;
}
@Override @Override
public OpenSslKeyMaterial retain() { public OpenSslKeyMaterial retain() {
OpenSslPrivateKey.this.retain(); super.retain();
return this; return this;
} }
@Override @Override
public OpenSslKeyMaterial retain(int increment) { public OpenSslKeyMaterial retain(int increment) {
OpenSslPrivateKey.this.retain(increment); super.retain(increment);
return this; return this;
} }
@ -164,37 +176,14 @@ final class OpenSslPrivateKey extends AbstractReferenceCounted implements Privat
} }
@Override @Override
public OpenSslKeyMaterial touch(Object hint) { protected void deallocate() {
OpenSslPrivateKey.this.touch(hint); releaseChain();
return this; OpenSslPrivateKey.this.release();
}
@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() { private void releaseChain() {
SSL.freeX509Chain(certificateChain); SSL.freeX509Chain(certificateChain);
certificateChain = 0; certificateChain = 0;
} }
@Override
public int refCnt() {
return OpenSslPrivateKey.this.refCnt();
}
} }
} }

View File

@ -15,13 +15,21 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
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 org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;
import java.net.Socket;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@ -72,4 +80,94 @@ public class OpenSslKeyMaterialProviderTest {
provider.destroy(); provider.destroy();
} }
/**
* Test class used by testChooseOpenSslPrivateKeyMaterial().
*/
private static final class SingleKeyManager implements X509KeyManager {
private final String keyAlias;
private final PrivateKey pk;
private final X509Certificate[] certChain;
SingleKeyManager(String keyAlias, PrivateKey pk, X509Certificate[] certChain) {
this.keyAlias = keyAlias;
this.pk = pk;
this.certChain = certChain;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return new String[]{keyAlias};
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return keyAlias;
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return new String[]{keyAlias};
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return keyAlias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return certChain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return pk;
}
}
@Test
public void testChooseOpenSslPrivateKeyMaterial() throws Exception {
PrivateKey privateKey = SslContext.toPrivateKey(
getClass().getResourceAsStream("localhost_server.key"),
null);
assertNotNull(privateKey);
assertEquals("PKCS#8", privateKey.getFormat());
final X509Certificate[] certChain = SslContext.toX509Certificates(
getClass().getResourceAsStream("localhost_server.pem"));
assertNotNull(certChain);
PemEncoded pemKey = null;
long pkeyBio = 0L;
OpenSslPrivateKey sslPrivateKey;
try {
pemKey = PemPrivateKey.toPEM(ByteBufAllocator.DEFAULT, true, privateKey);
pkeyBio = ReferenceCountedOpenSslContext.toBIO(ByteBufAllocator.DEFAULT, pemKey.retain());
sslPrivateKey = new OpenSslPrivateKey(SSL.parsePrivateKey(pkeyBio, null));
} finally {
ReferenceCountUtil.safeRelease(pemKey);
if (pkeyBio != 0L) {
SSL.freeBIO(pkeyBio);
}
}
final String keyAlias = "key";
OpenSslKeyMaterialProvider provider = new OpenSslKeyMaterialProvider(
new SingleKeyManager(keyAlias, sslPrivateKey, certChain),
null);
OpenSslKeyMaterial material = provider.chooseKeyMaterial(ByteBufAllocator.DEFAULT, keyAlias);
assertNotNull(material);
assertEquals(2, sslPrivateKey.refCnt());
assertEquals(1, material.refCnt());
assertTrue(material.release());
assertEquals(1, sslPrivateKey.refCnt());
// Can get material multiple times from the same key
material = provider.chooseKeyMaterial(ByteBufAllocator.DEFAULT, keyAlias);
assertNotNull(material);
assertEquals(2, sslPrivateKey.refCnt());
assertTrue(material.release());
assertTrue(sslPrivateKey.release());
assertEquals(0, sslPrivateKey.refCnt());
assertEquals(0, material.refCnt());
assertEquals(0, ((OpenSslPrivateKey.OpenSslPrivateKeyMaterial) material).certificateChain);
}
} }