Let OpenSslContext take pre-encoded pkcs#8 private key/cert bytes
Motivation OpenSslContext is expecting Java's PrivateKey and X509Certificate objects as input (for JdkSslContext API compatibility reasons) but doesn't really use them beyond turning them into PEM/PKCS#8 strings. This conversion can be entirely skipped if the user can pass in private keys and certificates in a format that Netty's OpenSSL code can digest. Modifications Two new classes have been added that act as a wrapper around the pre-encoded byte[] and also retain API compatibility to JdkSslContext. Result It's possible to pass PEM encoded bytes straight into OpenSSL without having to parse them (e.g. File to Java's PrivateKey) and then encode them (i.e. PrivateKey into PEM/PKCS#8). File pemPrivateKeyFile; byte[] pemBytes = readBytes(pemPrivateKeyFile); PemPrivateKey pemPrivateKey = PemPrivateKey.valueOf(pemBytes); SslContextBuilder.forServer(pemPrivateKey) .sslProvider(SslProvider.OPENSSL)
This commit is contained in:
parent
c3abb9146e
commit
cc580e3ba1
@ -17,9 +17,6 @@ package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
@ -36,10 +33,8 @@ import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.CertificateRevokedException;
|
||||
@ -55,11 +50,6 @@ import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBeha
|
||||
import static io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
|
||||
public abstract class OpenSslContext extends SslContext {
|
||||
private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] END_CERT = "\n-----END CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslContext.class);
|
||||
/**
|
||||
* To make it easier for users to replace JDK implemention with OpenSsl version we also use
|
||||
@ -513,32 +503,14 @@ public abstract class OpenSslContext extends SslContext {
|
||||
if (key == null) {
|
||||
return 0;
|
||||
}
|
||||
ByteBuf buffer = Unpooled.directBuffer();
|
||||
try {
|
||||
buffer.writeBytes(BEGIN_PRIVATE_KEY);
|
||||
ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded());
|
||||
final ByteBuf encodedBuf;
|
||||
try {
|
||||
encodedBuf = Base64.encode(wrappedBuf, true);
|
||||
try {
|
||||
buffer.writeBytes(encodedBuf);
|
||||
} finally {
|
||||
zerooutAndRelease(encodedBuf);
|
||||
}
|
||||
} finally {
|
||||
zerooutAndRelease(wrappedBuf);
|
||||
}
|
||||
buffer.writeBytes(END_PRIVATE_KEY);
|
||||
return newBIO(buffer);
|
||||
} finally {
|
||||
// Zero out the buffer and so the private key it held.
|
||||
zerooutAndRelease(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void zerooutAndRelease(ByteBuf buffer) {
|
||||
buffer.setZero(0, buffer.capacity());
|
||||
buffer.release();
|
||||
ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
|
||||
PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key);
|
||||
try {
|
||||
return toBIO(allocator, pem.retain());
|
||||
} finally {
|
||||
pem.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,30 +521,52 @@ public abstract class OpenSslContext extends SslContext {
|
||||
if (certChain == null) {
|
||||
return 0;
|
||||
}
|
||||
ByteBuf buffer = Unpooled.directBuffer();
|
||||
|
||||
if (certChain.length == 0) {
|
||||
throw new IllegalArgumentException("certChain can't be empty");
|
||||
}
|
||||
|
||||
ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
|
||||
PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain);
|
||||
try {
|
||||
for (X509Certificate cert: certChain) {
|
||||
buffer.writeBytes(BEGIN_CERT);
|
||||
ByteBuf wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded());
|
||||
try {
|
||||
ByteBuf encodedBuf = Base64.encode(wrappedBuf, true);
|
||||
try {
|
||||
buffer.writeBytes(encodedBuf);
|
||||
return toBIO(allocator, pem.retain());
|
||||
} finally {
|
||||
encodedBuf.release();
|
||||
pem.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception {
|
||||
try {
|
||||
// We can turn direct buffers straight into BIOs. No need to
|
||||
// make a yet another copy.
|
||||
ByteBuf content = pem.content();
|
||||
|
||||
if (content.isDirect()) {
|
||||
return newBIO(content.retainedSlice());
|
||||
}
|
||||
|
||||
ByteBuf buffer = allocator.directBuffer(content.readableBytes());
|
||||
try {
|
||||
buffer.writeBytes(content);
|
||||
return newBIO(buffer.retainedSlice());
|
||||
} finally {
|
||||
wrappedBuf.release();
|
||||
try {
|
||||
// If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we
|
||||
// need to zero out the bytes of the copy before we're releasing it.
|
||||
if (pem.isSensitive()) {
|
||||
SslUtils.zeroout(buffer);
|
||||
}
|
||||
buffer.writeBytes(END_CERT);
|
||||
}
|
||||
return newBIO(buffer);
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pem.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static long newBIO(ByteBuf buffer) throws Exception {
|
||||
try {
|
||||
long bio = SSL.newMemBIO();
|
||||
int readable = buffer.readableBytes();
|
||||
if (SSL.writeToBIO(bio, OpenSsl.memoryAddress(buffer), readable) != readable) {
|
||||
@ -580,6 +574,9 @@ public abstract class OpenSslContext extends SslContext {
|
||||
throw new IllegalStateException("Could not write data to memory BIO");
|
||||
}
|
||||
return bio;
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
static void checkKeyManagerFactory(KeyManagerFactory keyManagerFactory) {
|
||||
|
55
handler/src/main/java/io/netty/handler/ssl/PemEncoded.java
Normal file
55
handler/src/main/java/io/netty/handler/ssl/PemEncoded.java
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufHolder;
|
||||
|
||||
/**
|
||||
* A marker interface for PEM encoded values.
|
||||
*/
|
||||
interface PemEncoded extends ByteBufHolder {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the PEM encoded value is considered
|
||||
* sensitive information such as a private key.
|
||||
*/
|
||||
boolean isSensitive();
|
||||
|
||||
@Override
|
||||
PemEncoded copy();
|
||||
|
||||
@Override
|
||||
PemEncoded duplicate();
|
||||
|
||||
@Override
|
||||
PemEncoded retainedDuplicate();
|
||||
|
||||
@Override
|
||||
PemEncoded replace(ByteBuf content);
|
||||
|
||||
@Override
|
||||
PemEncoded retain();
|
||||
|
||||
@Override
|
||||
PemEncoded retain(int increment);
|
||||
|
||||
@Override
|
||||
PemEncoded touch();
|
||||
|
||||
@Override
|
||||
PemEncoded touch(Object hint);
|
||||
}
|
218
handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java
Normal file
218
handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 java.security.PrivateKey;
|
||||
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.AbstractReferenceCounted;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.IllegalReferenceCountException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* This is a special purpose implementation of a {@link PrivateKey} which allows the
|
||||
* user to pass PEM/PKCS#8 encoded key material straight into {@link OpenSslContext}
|
||||
* without having to parse and re-encode bytes in Java land.
|
||||
*
|
||||
* All methods other than what's implemented in {@link PemEncoded} and {@link Destroyable}
|
||||
* throw {@link UnsupportedOperationException}s.
|
||||
*
|
||||
* @see PemEncoded
|
||||
* @see OpenSslContext
|
||||
* @see #valueOf(byte[])
|
||||
* @see #valueOf(ByteBuf)
|
||||
*/
|
||||
public final class PemPrivateKey extends AbstractReferenceCounted implements PrivateKey, PemEncoded {
|
||||
|
||||
private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
|
||||
private static final String PKCS8_FORMAT = "PKCS#8";
|
||||
|
||||
/**
|
||||
* Creates a {@link PemEncoded} value from the {@link PrivateKey}.
|
||||
*/
|
||||
static PemEncoded toPEM(ByteBufAllocator allocator, boolean useDirect, PrivateKey key) {
|
||||
// We can take a shortcut if the private key happens to be already
|
||||
// PEM/PKCS#8 encoded. This is the ideal case and reason why all
|
||||
// this exists. It allows the user to pass pre-encoded bytes straight
|
||||
// into OpenSSL without having to do any of the extra work.
|
||||
if (key instanceof PemEncoded) {
|
||||
return ((PemEncoded) key).retain();
|
||||
}
|
||||
|
||||
ByteBuf encoded = Unpooled.wrappedBuffer(key.getEncoded());
|
||||
try {
|
||||
ByteBuf base64 = SslUtils.toBase64(allocator, encoded);
|
||||
try {
|
||||
int size = BEGIN_PRIVATE_KEY.length + base64.readableBytes() + END_PRIVATE_KEY.length;
|
||||
|
||||
boolean success = false;
|
||||
final ByteBuf pem = useDirect ? allocator.directBuffer(size) : allocator.buffer(size);
|
||||
try {
|
||||
pem.writeBytes(BEGIN_PRIVATE_KEY);
|
||||
pem.writeBytes(base64);
|
||||
pem.writeBytes(END_PRIVATE_KEY);
|
||||
|
||||
PemValue value = new PemValue(pem, true);
|
||||
success = true;
|
||||
return value;
|
||||
} finally {
|
||||
// Make sure we never leak that PEM ByteBuf if there's an Exception.
|
||||
if (!success) {
|
||||
SslUtils.zerooutAndRelease(pem);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
SslUtils.zerooutAndRelease(base64);
|
||||
}
|
||||
} finally {
|
||||
SslUtils.zerooutAndRelease(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PemPrivateKey} from raw {@code byte[]}.
|
||||
*
|
||||
* ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
|
||||
* No input validation is performed to validate it.
|
||||
*/
|
||||
public static PemPrivateKey valueOf(byte[] key) {
|
||||
return valueOf(Unpooled.wrappedBuffer(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PemPrivateKey} from raw {@code ByteBuf}.
|
||||
*
|
||||
* ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
|
||||
* No input validation is performed to validate it.
|
||||
*/
|
||||
public static PemPrivateKey valueOf(ByteBuf key) {
|
||||
return new PemPrivateKey(key);
|
||||
}
|
||||
|
||||
private final ByteBuf content;
|
||||
|
||||
private PemPrivateKey(ByteBuf content) {
|
||||
this.content = ObjectUtil.checkNotNull(content, "content");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf content() {
|
||||
int count = refCnt();
|
||||
if (count <= 0) {
|
||||
throw new IllegalReferenceCountException(count);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey copy() {
|
||||
return replace(content.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey duplicate() {
|
||||
return replace(content.duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey retainedDuplicate() {
|
||||
return replace(content.retainedDuplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey replace(ByteBuf content) {
|
||||
return new PemPrivateKey(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey touch() {
|
||||
content.touch();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey touch(Object hint) {
|
||||
content.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey retain() {
|
||||
return (PemPrivateKey) super.retain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemPrivateKey retain(int increment) {
|
||||
return (PemPrivateKey) super.retain(increment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
// Private Keys are sensitive. We need to zero the bytes
|
||||
// before we're releasing the underlying ByteBuf
|
||||
SslUtils.zerooutAndRelease(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return PKCS8_FORMAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
105
handler/src/main/java/io/netty/handler/ssl/PemValue.java
Normal file
105
handler/src/main/java/io/netty/handler/ssl/PemValue.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.util.AbstractReferenceCounted;
|
||||
import io.netty.util.IllegalReferenceCountException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* A PEM encoded value.
|
||||
*
|
||||
* @see PemEncoded
|
||||
* @see PemPrivateKey#toPEM(ByteBufAllocator, boolean, java.security.PrivateKey)
|
||||
* @see PemX509Certificate#toPEM(ByteBufAllocator, boolean, java.security.cert.X509Certificate[])
|
||||
*/
|
||||
class PemValue extends AbstractReferenceCounted implements PemEncoded {
|
||||
|
||||
private final ByteBuf content;
|
||||
|
||||
private final boolean sensitive;
|
||||
|
||||
public PemValue(ByteBuf content, boolean sensitive) {
|
||||
this.content = ObjectUtil.checkNotNull(content, "content");
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf content() {
|
||||
int count = refCnt();
|
||||
if (count <= 0) {
|
||||
throw new IllegalReferenceCountException(count);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue copy() {
|
||||
return replace(content.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue duplicate() {
|
||||
return replace(content.duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue retainedDuplicate() {
|
||||
return replace(content.retainedDuplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue replace(ByteBuf content) {
|
||||
return new PemValue(content, sensitive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue touch() {
|
||||
return (PemValue) super.touch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue touch(Object hint) {
|
||||
content.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue retain() {
|
||||
return (PemValue) super.retain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemValue retain(int increment) {
|
||||
return (PemValue) super.retain(increment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
if (sensitive) {
|
||||
SslUtils.zeroout(content);
|
||||
}
|
||||
content.release();
|
||||
}
|
||||
}
|
@ -0,0 +1,415 @@
|
||||
/*
|
||||
* 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 java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.IllegalReferenceCountException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* This is a special purpose implementation of a {@link X509Certificate} which allows
|
||||
* the user to pass PEM/PKCS#8 encoded data straight into {@link OpenSslContext} without
|
||||
* having to parse and re-encode bytes in Java land.
|
||||
*
|
||||
* All methods other than what's implemented in {@link PemEncoded}'s throw
|
||||
* {@link UnsupportedOperationException}s.
|
||||
*
|
||||
* @see PemEncoded
|
||||
* @see OpenSslContext
|
||||
* @see #valueOf(byte[])
|
||||
* @see #valueOf(ByteBuf)
|
||||
*/
|
||||
public final class PemX509Certificate extends X509Certificate implements PemEncoded {
|
||||
|
||||
private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] END_CERT = "\n-----END CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
|
||||
/**
|
||||
* Creates a {@link PemEncoded} value from the {@link X509Certificate}s.
|
||||
*/
|
||||
static PemEncoded toPEM(ByteBufAllocator allocator, boolean useDirect,
|
||||
X509Certificate... chain) throws CertificateEncodingException {
|
||||
|
||||
if (chain == null || chain.length == 0) {
|
||||
throw new IllegalArgumentException("X.509 certificate chain can't be null or empty");
|
||||
}
|
||||
|
||||
// We can take a shortcut if there is only one certificate and
|
||||
// it already happens to be a PemEncoded instance. This is the
|
||||
// ideal case and reason why all this exists. It allows the user
|
||||
// to pass pre-encoded bytes straight into OpenSSL without having
|
||||
// to do any of the extra work.
|
||||
if (chain.length == 1) {
|
||||
X509Certificate first = chain[0];
|
||||
if (first instanceof PemEncoded) {
|
||||
return ((PemEncoded) first).retain();
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
ByteBuf pem = null;
|
||||
try {
|
||||
for (X509Certificate cert : chain) {
|
||||
|
||||
if (cert == null) {
|
||||
throw new IllegalArgumentException("Null element in chain: " + Arrays.toString(chain));
|
||||
}
|
||||
|
||||
if (cert instanceof PemEncoded) {
|
||||
pem = append(allocator, useDirect, (PemEncoded) cert, chain.length, pem);
|
||||
} else {
|
||||
pem = append(allocator, useDirect, cert, chain.length, pem);
|
||||
}
|
||||
}
|
||||
|
||||
PemValue value = new PemValue(pem, false);
|
||||
success = true;
|
||||
return value;
|
||||
} finally {
|
||||
// Make sure we never leak the PEM's ByteBuf in the event of an Exception
|
||||
if (!success && pem != null) {
|
||||
pem.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the {@link PemEncoded} value to the {@link ByteBuf} (last arg) and returns it.
|
||||
* If the {@link ByteBuf} didn't exist yet it'll create it using the {@link ByteBufAllocator}.
|
||||
*/
|
||||
private static ByteBuf append(ByteBufAllocator allocator, boolean useDirect,
|
||||
PemEncoded encoded, int count, ByteBuf pem) {
|
||||
|
||||
ByteBuf content = encoded.content();
|
||||
|
||||
if (pem == null) {
|
||||
// see the other append() method
|
||||
pem = newBuffer(allocator, useDirect, content.readableBytes() * count);
|
||||
}
|
||||
|
||||
pem.writeBytes(content.slice());
|
||||
return pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the {@link X509Certificate} value to the {@link ByteBuf} (last arg) and returns it.
|
||||
* If the {@link ByteBuf} didn't exist yet it'll create it using the {@link ByteBufAllocator}.
|
||||
*/
|
||||
private static ByteBuf append(ByteBufAllocator allocator, boolean useDirect,
|
||||
X509Certificate cert, int count, ByteBuf pem) throws CertificateEncodingException {
|
||||
|
||||
ByteBuf encoded = Unpooled.wrappedBuffer(cert.getEncoded());
|
||||
try {
|
||||
ByteBuf base64 = SslUtils.toBase64(allocator, encoded);
|
||||
try {
|
||||
if (pem == null) {
|
||||
// We try to approximate the buffer's initial size. The sizes of
|
||||
// certificates can vary a lot so it'll be off a bit depending
|
||||
// on the number of elements in the array (count argument).
|
||||
pem = newBuffer(allocator, useDirect,
|
||||
(BEGIN_CERT.length + base64.readableBytes() + END_CERT.length) * count);
|
||||
}
|
||||
|
||||
pem.writeBytes(BEGIN_CERT);
|
||||
pem.writeBytes(base64);
|
||||
pem.writeBytes(END_CERT);
|
||||
} finally {
|
||||
base64.release();
|
||||
}
|
||||
} finally {
|
||||
encoded.release();
|
||||
}
|
||||
|
||||
return pem;
|
||||
}
|
||||
|
||||
private static ByteBuf newBuffer(ByteBufAllocator allocator, boolean useDirect, int initialCapacity) {
|
||||
return useDirect ? allocator.directBuffer(initialCapacity) : allocator.buffer(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PemX509Certificate} from raw {@code byte[]}.
|
||||
*
|
||||
* ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
|
||||
* No input validation is performed to validate it.
|
||||
*/
|
||||
public static PemX509Certificate valueOf(byte[] key) {
|
||||
return valueOf(Unpooled.wrappedBuffer(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PemX509Certificate} from raw {@code ByteBuf}.
|
||||
*
|
||||
* ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
|
||||
* No input validation is performed to validate it.
|
||||
*/
|
||||
public static PemX509Certificate valueOf(ByteBuf key) {
|
||||
return new PemX509Certificate(key);
|
||||
}
|
||||
|
||||
private final ByteBuf content;
|
||||
|
||||
private PemX509Certificate(ByteBuf content) {
|
||||
this.content = ObjectUtil.checkNotNull(content, "content");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
// There is no sensitive information in a X509 Certificate
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return content.refCnt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf content() {
|
||||
int count = refCnt();
|
||||
if (count <= 0) {
|
||||
throw new IllegalReferenceCountException(count);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate copy() {
|
||||
return replace(content.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate duplicate() {
|
||||
return replace(content.duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate retainedDuplicate() {
|
||||
return replace(content.retainedDuplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate replace(ByteBuf content) {
|
||||
return new PemX509Certificate(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate retain() {
|
||||
content.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate retain(int increment) {
|
||||
content.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate touch() {
|
||||
content.touch();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PemX509Certificate touch(Object hint) {
|
||||
content.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return content.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return content.release(decrement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() throws CertificateEncodingException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUnsupportedCriticalExtension() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getCriticalExtensionOIDs() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNonCriticalExtensionOIDs() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getExtensionValue(String oid) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity() throws CertificateExpiredException,
|
||||
CertificateNotYetValidException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity(Date date) throws CertificateExpiredException,
|
||||
CertificateNotYetValidException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getSerialNumber() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getIssuerDN() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getSubjectDN() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotBefore() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotAfter() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getTBSCertificate() throws CertificateEncodingException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgOID() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigAlgParams() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getIssuerUniqueID() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getSubjectUniqueID() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getKeyUsage() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBasicConstraints() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key)
|
||||
throws CertificateException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key, String sigProvider)
|
||||
throws CertificateException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
} else if (!(o instanceof PemX509Certificate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PemX509Certificate other = (PemX509Certificate) o;
|
||||
return content.equals(other.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return content.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content.toString(CharsetUtil.UTF_8);
|
||||
}
|
||||
}
|
@ -16,7 +16,10 @@
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.base64.Base64Dialect;
|
||||
|
||||
/**
|
||||
* Constants for SSL packets.
|
||||
@ -125,6 +128,35 @@ final class SslUtils {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the {@link ByteBuf} with zero bytes.
|
||||
*/
|
||||
static void zeroout(ByteBuf buffer) {
|
||||
if (!buffer.isReadOnly()) {
|
||||
buffer.setZero(0, buffer.capacity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the {@link ByteBuf} with zero bytes and releases it.
|
||||
*/
|
||||
static void zerooutAndRelease(ByteBuf buffer) {
|
||||
zeroout(buffer);
|
||||
buffer.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link Base64#encode(ByteBuf, boolean)} but allows the use of a custom {@link ByteBufAllocator}.
|
||||
*
|
||||
* @see Base64#encode(ByteBuf, boolean)
|
||||
*/
|
||||
static ByteBuf toBase64(ByteBufAllocator allocator, ByteBuf src) {
|
||||
ByteBuf dst = Base64.encode(src, src.readerIndex(),
|
||||
src.readableBytes(), true, Base64Dialect.STANDARD, allocator);
|
||||
src.readerIndex(src.writerIndex());
|
||||
return dst;
|
||||
}
|
||||
|
||||
private SslUtils() {
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user