Repository: netty/netty
Issue: Let OpenSslContext take pre-encoded pkcs#8 private key/cert bytes
Labels: improvement
feature

Netty 4.1.1-SNAPSHOT

It would be great if it was possible to pass pre-encoded pkcs#8 private key and certificate bytes into OpenSslContext and skip some of the stuff that is happening in OpenSslContext's `toBIO(...)` methods (namely the Base64 encoding and appending of the "---- BEGIN/END ----" markers).


Issue comments:
@rkapsi - Would you be interested in submitting a PR?

@Scottmitch On it...

... but `io.netty.util.collection.*` seem to be missing on the 4.1 branch?!

@rkapsi its generated ... run mvn clean compile in the common module

@normanmaurer thanks!

Fixed by https://github.com/netty/netty/pull/5377


Pull request review comments:
Pass a `ByteBufAllocator` as a parameter and use it to allocate this buffer. We can use `UnpooledByteBufAllocator.DEFAULT` by default but at least this gives control over allocations done in this method.

Pass a `ByteBufAllocator` as a parameter and use it to allocate this buffer. We can use `UnpooledByteBufAllocator.DEFAULT` by default but at least this gives control over allocations done in this method.

if `pem.content().isDirect()` is true we should be able to just use `pem.content().retainedSlice()` instead of allocating a new buffer and copying.

we may also want have the new classes that construct the PEM cert/key allocate direct by default.

not introduced by this PR but if we adjust the ordering of events such that the encoding is done first ... we should know the exact size to allocate `pem` (`BEGIN_CERT.length + base64.readableBytes() + END_CERT.length`)

discussion: is this necessary ATM? I wonder if we can do with out this for now?

nit: add some descriptive text? `content is not accessible after this object has been destroyed`

Please elaborate. You mean classes such as `SelfSignedCertificate` create and return PemX509Certificates?

any reason why we can't implement this?

`PKCS#8` 
https://docs.oracle.com/javase/7/docs/api/java/security/Key.html#getFormat()

can/should we support this based upon https://docs.oracle.com/javase/7/docs/api/java/security/Key.html#getAlgorithm() ?

`PemX509Certificate` and `PemPrivateKey` currently use `Unpooled.buffer()` to allocate the buffer they use for internal storage. However we could change this to `ByteBufAllocator.directBuffer(...)` to make it more likely that `pem.content().isDirect()` is true and thus avoid the copy operation.

is it correct to return 0 if the length is 0 ? If so we need to update the javadocs.

move this in another finally block as just to be safe ?

to a null check via ObjectUtil.checkNonNull(...) ?

maybe you could just extend `DefaultByteBufHolder` and so reuse some code ?

directly call content.touch(...)

+1

+1

Use javadoc notion and use {@link...} 

Same above

Again why not extend `DefaultByteBufHolder` ?

null checks

null check

@normanmaurer `DefaultByteBufHolder` doesn't have a `#destroyed()` method which it used to fill the ByteBuf with zero bytes if the value is sensitive (PrivateKey).

and ? What is the problem to just add it to PemValue ? 

How about throwing an IllegalArgumentException if length==0?

yep sounds good

So, `PrivateKey` implements that `Destroyable` interface but it doesn't say when it was introduced. Netty's build agent didn't like my `@Overrides`.

Something like... extend and then...?

``` java
@Override
public boolean release() {
  if (super.release()) {
    // zero out
   return true;
  }
  return false;
}
```

nit: just pass `ByteBufAllocator.DEFAULT` directly ... no need for temp variable (even if we think JDK may optimize it away)

Actually that will not work, `DefaultByteBufHolder#release()` returns true/false after it has released the underlying ByteBuf at which point it's no longer possible for me to wipe the bytes.

can this actually happen?

The alloc is passed ~2 lines further down into the `toBIO()` call. Feels more appropriate to have it as a temp variable and ideally passed in as method argument (but that would break existing API). Happy to change it to `ByteBufAllocator.DEFAULT` though.

if the constructor of PemValue were to throw for some reason, it seems like we would leak the buffer. Consider adding comments on the constructor that it must take ownership of the buffer, or handle the release here:

``` java
PemEncoded result = new PemValue(pem, false);
success = true;
return result;
```

nit: consider using a tertiary statement.

only if `chain.length==0` is passed in. Happy to turn that into a precondition that throws an IllegalArgumentException or so.

Ah ok... Sorry for the noise

> Am 09.06.2016 um 19:28 schrieb Roger notifications@github.com:
> 
> In handler/src/main/java/io/netty/handler/ssl/PemValue.java:
> 
> > - _/
> >   +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;
> >   +
> >   +/_*
> > - \* 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 {
> >   Actually that will not work, DefaultByteBufHolder#release() returns true/false after it has released the underlying ByteBuf at which point it's no longer possible for me to wipe the bytes.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or mute the thread.

can this be package private?

just for content :)

I missed that it was used in 2 spots originally ... thanks for clarifying ... I'm fine leaving it as is.

is this necessary any more now that we don't implement `Destroyable`?

nvm ... `PrivateKey` brings `Destroyable` in to the heirarchy ... add `@Override`?

add `@Override`?

yah lets handle that case explicitly as a precondition then

I'm a bit on the fence. Java's PrivateKey interface extends Destroyable. No idea when that was introduced. I assume JDK8 because its got `default` impls. straight on the interface. Yesterday's build errors were because of that.

consider simplifying this `try` block to:

``` java
                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);
                    }
                }
```

The build agent didn't like that yesterday which lead to that error

```
[Compiler] Compilation failure
  where T is a type-variable:
    T extends Object declared in method <T>attr(AttributeKey<T>)
/var/lib/teamcity-agent/work/a00bdf77e621d005/handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java:[178,4] error: method does not override or implement a method from a supertype
```

@rkapsi - Good point. `PrivateKey` started extending `Destroyable` in [JDK8](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/security/PrivateKey.java/). I recall you had the `//@Override // JDK8` and I now understand why :) ... Maybe add something like this back `//@Override will only in work with JDK8 but Netty must be JDK6 compatible so we can't have nice things`

+1 that makes sense ... we can discuss above in https://github.com/netty/netty/pull/5377#discussion_r66490755

+1

sgtm

@rkapsi just address this comment and I will pull in. Great work!


Patch that resolved the issue:
diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
index 446560492fd..67de79d4320 100644
--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java
@@ -17,9 +17,6 @@
 
 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.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.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,34 +503,16 @@ static long toBIO(PrivateKey key) throws Exception {
         if (key == null) {
             return 0;
         }
-        ByteBuf buffer = Unpooled.directBuffer();
+
+        ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+        PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key);
         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);
+            return toBIO(allocator, pem.retain());
         } finally {
-            // Zero out the buffer and so the private key it held.
-            zerooutAndRelease(buffer);
+            pem.release();
         }
     }
 
-    private static void zerooutAndRelease(ByteBuf buffer) {
-        buffer.setZero(0, buffer.capacity());
-        buffer.release();
-    }
-
     /**
      * Return the pointer to a <a href="https://www.openssl.org/docs/crypto/BIO_get_mem_ptr.html">in-memory BIO</a>
      * or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}.
@@ -549,37 +521,62 @@ static long toBIO(X509Certificate[] certChain) throws Exception {
         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());
+            return toBIO(allocator, pem.retain());
+        } finally {
+            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 {
                 try {
-                    ByteBuf encodedBuf = Base64.encode(wrappedBuf, true);
-                    try {
-                        buffer.writeBytes(encodedBuf);
-                    } finally {
-                        encodedBuf.release();
+                    // 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);
                     }
                 } finally {
-                    wrappedBuf.release();
+                    buffer.release();
                 }
-                buffer.writeBytes(END_CERT);
             }
-            return newBIO(buffer);
-        }  finally {
-            buffer.release();
+        } finally {
+            pem.release();
         }
     }
 
     private static long newBIO(ByteBuf buffer) throws Exception {
-        long bio = SSL.newMemBIO();
-        int readable = buffer.readableBytes();
-        if (SSL.writeToBIO(bio, OpenSsl.memoryAddress(buffer), readable) != readable) {
-            SSL.freeBIO(bio);
-            throw new IllegalStateException("Could not write data to memory BIO");
+        try {
+            long bio = SSL.newMemBIO();
+            int readable = buffer.readableBytes();
+            if (SSL.writeToBIO(bio, OpenSsl.memoryAddress(buffer), readable) != readable) {
+                SSL.freeBIO(bio);
+                throw new IllegalStateException("Could not write data to memory BIO");
+            }
+            return bio;
+        } finally {
+            buffer.release();
         }
-        return bio;
     }
 
     static void checkKeyManagerFactory(KeyManagerFactory keyManagerFactory) {
diff --git a/handler/src/main/java/io/netty/handler/ssl/PemEncoded.java b/handler/src/main/java/io/netty/handler/ssl/PemEncoded.java
new file mode 100644
index 00000000000..fc3db93aa31
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/PemEncoded.java
@@ -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);
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java b/handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java
new file mode 100644
index 00000000000..c1b828c027a
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/PemPrivateKey.java
@@ -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;
+    }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/PemValue.java b/handler/src/main/java/io/netty/handler/ssl/PemValue.java
new file mode 100644
index 00000000000..becb5b84921
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/PemValue.java
@@ -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();
+    }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/PemX509Certificate.java b/handler/src/main/java/io/netty/handler/ssl/PemX509Certificate.java
new file mode 100644
index 00000000000..1d60c732f52
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/PemX509Certificate.java
@@ -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);
+    }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslUtils.java b/handler/src/main/java/io/netty/handler/ssl/SslUtils.java
index a70c00c2e0f..a7ad75e3e32 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslUtils.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslUtils.java
@@ -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 @@ static void notifyHandshakeFailure(ChannelHandlerContext ctx, Throwable cause) {
         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() {
     }
 }


Test patch: