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