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.util.Resource;
21  import io.netty5.util.Send;
22  import io.netty5.buffer.api.internal.ResourceSupport;
23  import io.netty5.buffer.api.internal.Statics;
24  import io.netty5.util.CharsetUtil;
25  
26  import java.math.BigInteger;
27  import java.security.Principal;
28  import java.security.PublicKey;
29  import java.security.cert.CertificateEncodingException;
30  import java.security.cert.X509Certificate;
31  import java.util.Arrays;
32  import java.util.Date;
33  import java.util.Set;
34  
35  import static io.netty5.buffer.api.DefaultBufferAllocators.offHeapAllocator;
36  import static io.netty5.util.internal.ObjectUtil.checkNonEmpty;
37  import static java.util.Objects.requireNonNull;
38  
39  /**
40   * This is a special purpose implementation of a {@link X509Certificate} which allows
41   * the user to pass PEM/PKCS#8 encoded data straight into {@link OpenSslContext} without
42   * having to parse and re-encode bytes in Java land.
43   *
44   * All methods other than what's implemented in {@link PemEncoded}'s throw
45   * {@link UnsupportedOperationException}s.
46   *
47   * @see PemEncoded
48   * @see OpenSslContext
49   * @see #valueOf(byte[])
50   * @see #valueOf(Buffer)
51   */
52  public final class PemX509Certificate extends X509Certificate implements PemEncoded, Resource<PemX509Certificate> {
53  
54      private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
55      private static final byte[] END_CERT = "\n-----END CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
56  
57      /**
58       * Creates a {@link PemEncoded} value from the {@link X509Certificate}s.
59       */
60      static PemEncoded toPEM(BufferAllocator alloc, X509Certificate... chain) throws CertificateEncodingException {
61          checkNonEmpty(chain, "chain");
62  
63          // We can take a shortcut if there is only one certificate, and
64          // it already happens to be a PemEncoded instance. This is the
65          // ideal case and reason why all this exists. It allows the user
66          // to pass pre-encoded bytes straight into OpenSSL without having
67          // to do any of the extra work.
68          if (chain.length == 1) {
69              X509Certificate first = chain[0];
70              if (first instanceof PemEncoded) {
71                  return ((PemEncoded) first).copy();
72              }
73          }
74  
75          boolean success = false;
76          Buffer pem = null;
77          try {
78              for (X509Certificate cert : chain) {
79  
80                  if (cert == null) {
81                      throw new IllegalArgumentException("Null element in chain: " + Arrays.toString(chain));
82                  }
83  
84                  if (cert instanceof PemEncoded) {
85                      pem = append(alloc, (PemEncoded) cert, chain.length, pem);
86                  } else {
87                      pem = append(alloc, cert, chain.length, pem);
88                  }
89              }
90  
91              PemValue value = new PemValue(pem);
92              success = true;
93              return value;
94          } finally {
95              // Make sure we never leak the PEM's Buffer in the event of an Exception
96              if (!success && pem != null) {
97                  pem.close();
98              }
99          }
100     }
101 
102     /**
103      * Appends the {@link PemEncoded} value to the {@link Buffer} (last arg) and returns it.
104      * If the {@link Buffer} didn't exist yet, an off-heap buffer will be created.
105      */
106     private static Buffer append(
107             BufferAllocator alloc, PemEncoded encoded, int count, Buffer pem) {
108         Buffer content = encoded.content();
109 
110         if (pem == null) {
111             // see the other append() method
112             pem = alloc.allocate(content.readableBytes() * count);
113         } else {
114             pem.ensureWritable(content.readableBytes());
115         }
116 
117         int length = content.readableBytes();
118         pem.skipWritableBytes(length);
119         content.copyInto(content.readerOffset(), pem, pem.writerOffset(), length);
120         return pem;
121     }
122 
123     /**
124      * Appends the {@link X509Certificate} value to the {@link Buffer} (last arg) and returns it.
125      * If the {@link Buffer} didn't exist yet, an off-heap buffer will be created.
126      */
127     private static Buffer append(BufferAllocator alloc, X509Certificate cert, int count, Buffer pem)
128             throws CertificateEncodingException {
129 
130         try (Buffer encoded = alloc.copyOf(cert.getEncoded());
131              Buffer base64 = SslUtils.toBase64(alloc, encoded)) {
132             int length = BEGIN_CERT.length + base64.readableBytes() + END_CERT.length;
133             if (pem == null) {
134                 // We try to approximate the buffer's initial size. The sizes of
135                 // certificates can vary a lot so it'll be off a bit depending
136                 // on the number of elements in the array (count argument).
137                 pem = alloc.allocate(length * count);
138             } else {
139                 pem.ensureWritable(length);
140             }
141 
142             pem.writeBytes(BEGIN_CERT);
143             pem.writeBytes(base64);
144             pem.writeBytes(END_CERT);
145         }
146 
147         return pem;
148     }
149 
150     /**
151      * Creates a {@link PemX509Certificate} from raw {@code byte[]}.
152      *
153      * ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
154      * No input validation is performed to validate it.
155      */
156     public static PemX509Certificate valueOf(byte[] key) {
157         return valueOf(offHeapAllocator().copyOf(key));
158     }
159 
160     /**
161      * Creates a {@link PemX509Certificate} from raw {@code Buffer}.
162      *
163      * ATTENTION: It's assumed that the given argument is a PEM/PKCS#8 encoded value.
164      * No input validation is performed to validate it.
165      */
166     public static PemX509Certificate valueOf(Buffer key) {
167         return new PemX509Certificate(key);
168     }
169 
170     private final Buffer content;
171 
172     private PemX509Certificate(Buffer content) {
173         this.content = requireNonNull(content, "content").makeReadOnly();
174     }
175 
176     @Override
177     public Buffer content() {
178         if (!content.isAccessible()) {
179             throw Statics.attachTrace((ResourceSupport<?, ?>) content,
180                                       new IllegalStateException("PemX509Certificate is closed."));
181         }
182 
183         return content;
184     }
185 
186     @Override
187     public void close() {
188         content.close();
189     }
190 
191     @Override
192     public PemX509Certificate copy() {
193         return new PemX509Certificate(content.copy(true));
194     }
195 
196     @Override
197     public Send<PemX509Certificate> send() {
198         return content.send().map(PemX509Certificate.class, PemX509Certificate::new);
199     }
200 
201     @Override
202     public boolean isAccessible() {
203         return content.isAccessible();
204     }
205 
206     @Override
207     public byte[] getEncoded() {
208         throw new UnsupportedOperationException();
209     }
210 
211     @Override
212     public boolean hasUnsupportedCriticalExtension() {
213         throw new UnsupportedOperationException();
214     }
215 
216     @Override
217     public Set<String> getCriticalExtensionOIDs() {
218         throw new UnsupportedOperationException();
219     }
220 
221     @Override
222     public Set<String> getNonCriticalExtensionOIDs() {
223         throw new UnsupportedOperationException();
224     }
225 
226     @Override
227     public byte[] getExtensionValue(String oid) {
228         throw new UnsupportedOperationException();
229     }
230 
231     @Override
232     public void checkValidity() {
233         throw new UnsupportedOperationException();
234     }
235 
236     @Override
237     public void checkValidity(Date date) {
238         throw new UnsupportedOperationException();
239     }
240 
241     @Override
242     public int getVersion() {
243         throw new UnsupportedOperationException();
244     }
245 
246     @Override
247     public BigInteger getSerialNumber() {
248         throw new UnsupportedOperationException();
249     }
250 
251     @Override
252     public Principal getIssuerDN() {
253         throw new UnsupportedOperationException();
254     }
255 
256     @Override
257     public Principal getSubjectDN() {
258         throw new UnsupportedOperationException();
259     }
260 
261     @Override
262     public Date getNotBefore() {
263         throw new UnsupportedOperationException();
264     }
265 
266     @Override
267     public Date getNotAfter() {
268         throw new UnsupportedOperationException();
269     }
270 
271     @Override
272     public byte[] getTBSCertificate() {
273         throw new UnsupportedOperationException();
274     }
275 
276     @Override
277     public byte[] getSignature() {
278         throw new UnsupportedOperationException();
279     }
280 
281     @Override
282     public String getSigAlgName() {
283         throw new UnsupportedOperationException();
284     }
285 
286     @Override
287     public String getSigAlgOID() {
288         throw new UnsupportedOperationException();
289     }
290 
291     @Override
292     public byte[] getSigAlgParams() {
293         throw new UnsupportedOperationException();
294     }
295 
296     @Override
297     public boolean[] getIssuerUniqueID() {
298         throw new UnsupportedOperationException();
299     }
300 
301     @Override
302     public boolean[] getSubjectUniqueID() {
303         throw new UnsupportedOperationException();
304     }
305 
306     @Override
307     public boolean[] getKeyUsage() {
308         throw new UnsupportedOperationException();
309     }
310 
311     @Override
312     public int getBasicConstraints() {
313         throw new UnsupportedOperationException();
314     }
315 
316     @Override
317     public void verify(PublicKey key) {
318         throw new UnsupportedOperationException();
319     }
320 
321     @Override
322     public void verify(PublicKey key, String sigProvider) {
323         throw new UnsupportedOperationException();
324     }
325 
326     @Override
327     public PublicKey getPublicKey() {
328         throw new UnsupportedOperationException();
329     }
330 
331     @Override
332     public boolean equals(Object o) {
333         if (o == this) {
334             return true;
335         }
336         if (!(o instanceof PemX509Certificate)) {
337             return false;
338         }
339 
340         PemX509Certificate other = (PemX509Certificate) o;
341         return content.equals(other.content);
342     }
343 
344     @Override
345     public int hashCode() {
346         return content.hashCode();
347     }
348 
349     @Override
350     public String toString() {
351         return content.toString(CharsetUtil.UTF_8);
352     }
353 }