View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.handler.ssl;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.buffer.api.BufferAllocator;
20  import io.netty5.buffer.api.BufferHolder;
21  import io.netty5.buffer.api.SensitiveBufferAllocator;
22  import io.netty5.util.CharsetUtil;
23  
24  import javax.security.auth.Destroyable;
25  import java.security.PrivateKey;
26  
27  import static io.netty5.buffer.api.DefaultBufferAllocators.offHeapAllocator;
28  
29  /**
30   * This is a special purpose implementation of a {@link PrivateKey} which allows the
31   * user to pass PEM/PKCS#8 encoded key material straight into {@link OpenSslContext}
32   * without having to parse and re-encode bytes in Java land.
33   * <p>
34   * All methods other than what's implemented in {@link PemEncoded} and {@link Destroyable}
35   * throw {@link UnsupportedOperationException}s.
36   *
37   * @see PemEncoded
38   * @see OpenSslContext
39   * @see #valueOf(byte[])
40   * @see #valueOf(Buffer)
41   */
42  public final class PemPrivateKey extends BufferHolder<PemPrivateKey> implements PrivateKey, PemEncoded {
43      private static final long serialVersionUID = 7978017465645018936L;
44  
45      private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
46      private static final byte[] END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
47  
48      private static final String PKCS8_FORMAT = "PKCS#8";
49  
50      /**
51       * Creates a {@link PemEncoded} value from the {@link PrivateKey}.
52       */
53      static PemEncoded toPEM(PrivateKey key) {
54          // We can take a shortcut if the private key happens to be already
55          // PEM/PKCS#8 encoded. This is the ideal case and reason why all
56          // this exists. It allows the user to pass pre-encoded bytes straight
57          // into OpenSSL without having to do any of the extra work.
58          if (key instanceof PemEncoded) {
59              return ((PemEncoded) key).copy();
60          }
61  
62          byte[] bytes = key.getEncoded();
63          if (bytes == null) {
64              throw new IllegalArgumentException(key.getClass().getName() + " does not support encoding");
65          }
66  
67          return toPEM(bytes);
68      }
69  
70      static PemEncoded toPEM(byte[] bytes) {
71          BufferAllocator allocator = SensitiveBufferAllocator.sensitiveOffHeapAllocator();
72          try (Buffer encoded = allocator.copyOf(bytes);
73               Buffer base64 = SslUtils.toBase64(allocator, encoded)) {
74              int size = BEGIN_PRIVATE_KEY.length + base64.readableBytes() + END_PRIVATE_KEY.length;
75  
76              boolean success = false;
77              final Buffer pem = allocator.allocate(size);
78              try {
79                  pem.writeBytes(BEGIN_PRIVATE_KEY);
80                  pem.writeBytes(base64);
81                  pem.writeBytes(END_PRIVATE_KEY);
82  
83                  PemValue value = new PemValue(pem);
84                  success = true;
85                  return value;
86              } finally {
87                  // Make sure we never leak that PEM Buffer if there's an Exception.
88                  if (!success) {
89                      pem.close();
90                  }
91              }
92          }
93      }
94  
95      /**
96       * Creates a {@link PemPrivateKey} from raw {@code byte[]}.
97       *
98       * ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
99       * No input validation is performed to validate it.
100      */
101     public static PemPrivateKey valueOf(byte[] key) {
102         return valueOf(offHeapAllocator().copyOf(key));
103     }
104 
105     /**
106      * Creates a {@link PemPrivateKey} from raw {@link Buffer}.
107      *
108      * ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
109      * No input validation is performed to validate it.
110      */
111     public static PemPrivateKey valueOf(Buffer key) {
112         return new PemPrivateKey(key);
113     }
114 
115     private PemPrivateKey(Buffer content) {
116         super(content.makeReadOnly());
117     }
118 
119     @Override
120     public Buffer content() {
121         if (!isAccessible()) {
122             throw new IllegalStateException("PemPrivateKey is closed.");
123         }
124 
125         return getBuffer();
126     }
127 
128     @Override
129     public PemPrivateKey copy() {
130         Buffer buffer = getBuffer();
131         return new PemPrivateKey(buffer.copy(true));
132     }
133 
134     @Override
135     protected PemPrivateKey receive(Buffer buf) {
136         return new PemPrivateKey(buf);
137     }
138 
139     @Override
140     public byte[] getEncoded() {
141         throw new UnsupportedOperationException();
142     }
143 
144     @Override
145     public String getAlgorithm() {
146         throw new UnsupportedOperationException();
147     }
148 
149     @Override
150     public String getFormat() {
151         return PKCS8_FORMAT;
152     }
153 
154     @Override
155     public void destroy() {
156         close();
157     }
158 
159     @Override
160     public boolean isDestroyed() {
161         return !isAccessible();
162     }
163 }