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.netty.handler.ssl;
17  
18  import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
19  
20  import java.math.BigInteger;
21  import java.security.Principal;
22  import java.security.PublicKey;
23  import java.security.cert.CertificateEncodingException;
24  import java.security.cert.X509Certificate;
25  import java.util.Arrays;
26  import java.util.Date;
27  import java.util.Set;
28  
29  import io.netty.buffer.ByteBuf;
30  import io.netty.buffer.ByteBufAllocator;
31  import io.netty.buffer.Unpooled;
32  import io.netty.util.CharsetUtil;
33  import io.netty.util.IllegalReferenceCountException;
34  import io.netty.util.internal.ObjectUtil;
35  
36  /**
37   * This is a special purpose implementation of a {@link X509Certificate} which allows
38   * the user to pass PEM/PKCS#8 encoded data straight into {@link OpenSslContext} without
39   * having to parse and re-encode bytes in Java land.
40   *
41   * All methods other than what's implemented in {@link PemEncoded}'s throw
42   * {@link UnsupportedOperationException}s.
43   *
44   * @see PemEncoded
45   * @see OpenSslContext
46   * @see #valueOf(byte[])
47   * @see #valueOf(ByteBuf)
48   */
49  public final class PemX509Certificate extends X509Certificate implements PemEncoded {
50  
51      private static final byte[] BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
52      private static final byte[] END_CERT = "\n-----END CERTIFICATE-----\n".getBytes(CharsetUtil.US_ASCII);
53  
54      /**
55       * Creates a {@link PemEncoded} value from the {@link X509Certificate}s.
56       */
57      static PemEncoded toPEM(ByteBufAllocator allocator, boolean useDirect,
58              X509Certificate... chain) throws CertificateEncodingException {
59  
60          checkNonEmpty(chain, "chain");
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 replace(content.copy());
205     }
206 
207     @Override
208     public PemX509Certificate duplicate() {
209         return replace(content.duplicate());
210     }
211 
212     @Override
213     public PemX509Certificate retainedDuplicate() {
214         return replace(content.retainedDuplicate());
215     }
216 
217     @Override
218     public PemX509Certificate replace(ByteBuf content) {
219         return new PemX509Certificate(content);
220     }
221 
222     @Override
223     public PemX509Certificate retain() {
224         content.retain();
225         return this;
226     }
227 
228     @Override
229     public PemX509Certificate retain(int increment) {
230         content.retain(increment);
231         return this;
232     }
233 
234     @Override
235     public PemX509Certificate touch() {
236         content.touch();
237         return this;
238     }
239 
240     @Override
241     public PemX509Certificate touch(Object hint) {
242         content.touch(hint);
243         return this;
244     }
245 
246     @Override
247     public boolean release() {
248         return content.release();
249     }
250 
251     @Override
252     public boolean release(int decrement) {
253         return content.release(decrement);
254     }
255 
256     @Override
257     public byte[] getEncoded() {
258         throw new UnsupportedOperationException();
259     }
260 
261     @Override
262     public boolean hasUnsupportedCriticalExtension() {
263         throw new UnsupportedOperationException();
264     }
265 
266     @Override
267     public Set<String> getCriticalExtensionOIDs() {
268         throw new UnsupportedOperationException();
269     }
270 
271     @Override
272     public Set<String> getNonCriticalExtensionOIDs() {
273         throw new UnsupportedOperationException();
274     }
275 
276     @Override
277     public byte[] getExtensionValue(String oid) {
278         throw new UnsupportedOperationException();
279     }
280 
281     @Override
282     public void checkValidity() {
283         throw new UnsupportedOperationException();
284     }
285 
286     @Override
287     public void checkValidity(Date date) {
288         throw new UnsupportedOperationException();
289     }
290 
291     @Override
292     public int getVersion() {
293         throw new UnsupportedOperationException();
294     }
295 
296     @Override
297     public BigInteger getSerialNumber() {
298         throw new UnsupportedOperationException();
299     }
300 
301     @Override
302     public Principal getIssuerDN() {
303         throw new UnsupportedOperationException();
304     }
305 
306     @Override
307     public Principal getSubjectDN() {
308         throw new UnsupportedOperationException();
309     }
310 
311     @Override
312     public Date getNotBefore() {
313         throw new UnsupportedOperationException();
314     }
315 
316     @Override
317     public Date getNotAfter() {
318         throw new UnsupportedOperationException();
319     }
320 
321     @Override
322     public byte[] getTBSCertificate() {
323         throw new UnsupportedOperationException();
324     }
325 
326     @Override
327     public byte[] getSignature() {
328         throw new UnsupportedOperationException();
329     }
330 
331     @Override
332     public String getSigAlgName() {
333         throw new UnsupportedOperationException();
334     }
335 
336     @Override
337     public String getSigAlgOID() {
338         throw new UnsupportedOperationException();
339     }
340 
341     @Override
342     public byte[] getSigAlgParams() {
343         throw new UnsupportedOperationException();
344     }
345 
346     @Override
347     public boolean[] getIssuerUniqueID() {
348         throw new UnsupportedOperationException();
349     }
350 
351     @Override
352     public boolean[] getSubjectUniqueID() {
353         throw new UnsupportedOperationException();
354     }
355 
356     @Override
357     public boolean[] getKeyUsage() {
358         throw new UnsupportedOperationException();
359     }
360 
361     @Override
362     public int getBasicConstraints() {
363         throw new UnsupportedOperationException();
364     }
365 
366     @Override
367     public void verify(PublicKey key) {
368         throw new UnsupportedOperationException();
369     }
370 
371     @Override
372     public void verify(PublicKey key, String sigProvider) {
373         throw new UnsupportedOperationException();
374     }
375 
376     @Override
377     public PublicKey getPublicKey() {
378         throw new UnsupportedOperationException();
379     }
380 
381     @Override
382     public boolean equals(Object o) {
383         if (o == this) {
384             return true;
385         }
386         if (!(o instanceof PemX509Certificate)) {
387             return false;
388         }
389 
390         PemX509Certificate other = (PemX509Certificate) o;
391         return content.equals(other.content);
392     }
393 
394     @Override
395     public int hashCode() {
396         return content.hashCode();
397     }
398 
399     @Override
400     public String toString() {
401         return content.toString(CharsetUtil.UTF_8);
402     }
403 }