View Javadoc
1   /*
2    * Copyright 2014 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  
17  package io.netty.handler.ssl;
18  
19  import io.netty.buffer.ByteBuf;
20  import io.netty.buffer.Unpooled;
21  import io.netty.handler.codec.base64.Base64;
22  import io.netty.util.CharsetUtil;
23  import io.netty.util.internal.logging.InternalLogger;
24  import io.netty.util.internal.logging.InternalLoggerFactory;
25  
26  import java.io.ByteArrayOutputStream;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.security.KeyException;
34  import java.security.KeyStore;
35  import java.security.cert.CertificateException;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  /**
42   * Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link KeyStore} easily.
43   */
44  final class PemReader {
45  
46      private static final InternalLogger logger = InternalLoggerFactory.getInstance(PemReader.class);
47  
48      private static final Pattern CERT_PATTERN = Pattern.compile(
49              "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header
50                      "([a-z0-9+/=\\r\\n]+)" +                    // Base64 text
51                      "-+END\\s+.*CERTIFICATE[^-]*-+",            // Footer
52              Pattern.CASE_INSENSITIVE);
53      private static final Pattern KEY_PATTERN = Pattern.compile(
54              "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
55                      "([a-z0-9+/=\\r\\n]+)" +                       // Base64 text
56                      "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+",            // Footer
57              Pattern.CASE_INSENSITIVE);
58  
59      static ByteBuf[] readCertificates(File file) throws CertificateException {
60          try {
61              InputStream in = new FileInputStream(file);
62  
63              try {
64                  return readCertificates(in);
65              } finally {
66                  safeClose(in);
67              }
68          } catch (FileNotFoundException e) {
69              throw new CertificateException("could not find certificate file: " + file);
70          }
71      }
72  
73      static ByteBuf[] readCertificates(InputStream in) throws CertificateException {
74          String content;
75          try {
76              content = readContent(in);
77          } catch (IOException e) {
78              throw new CertificateException("failed to read certificate input stream", e);
79          }
80  
81          List<ByteBuf> certs = new ArrayList<ByteBuf>();
82          Matcher m = CERT_PATTERN.matcher(content);
83          int start = 0;
84          for (;;) {
85              if (!m.find(start)) {
86                  break;
87              }
88  
89              ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
90              ByteBuf der = Base64.decode(base64);
91              base64.release();
92              certs.add(der);
93  
94              start = m.end();
95          }
96  
97          if (certs.isEmpty()) {
98              throw new CertificateException("found no certificates in input stream");
99          }
100 
101         return certs.toArray(new ByteBuf[certs.size()]);
102     }
103 
104     static ByteBuf readPrivateKey(File file) throws KeyException {
105         try {
106             InputStream in = new FileInputStream(file);
107 
108             try {
109                 return readPrivateKey(in);
110             } finally {
111                 safeClose(in);
112             }
113         } catch (FileNotFoundException e) {
114             throw new KeyException("could not find key file: " + file);
115         }
116     }
117 
118     static ByteBuf readPrivateKey(InputStream in) throws KeyException {
119         String content;
120         try {
121             content = readContent(in);
122         } catch (IOException e) {
123             throw new KeyException("failed to read key input stream", e);
124         }
125 
126         Matcher m = KEY_PATTERN.matcher(content);
127         if (!m.find()) {
128             throw new KeyException("could not find a PKCS #8 private key in input stream" +
129                     " (see http://netty.io/wiki/sslcontextbuilder-and-private-key.html for more information)");
130         }
131 
132         ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
133         ByteBuf der = Base64.decode(base64);
134         base64.release();
135         return der;
136     }
137 
138     private static String readContent(InputStream in) throws IOException {
139         ByteArrayOutputStream out = new ByteArrayOutputStream();
140         try {
141             byte[] buf = new byte[8192];
142             for (;;) {
143                 int ret = in.read(buf);
144                 if (ret < 0) {
145                     break;
146                 }
147                 out.write(buf, 0, ret);
148             }
149             return out.toString(CharsetUtil.US_ASCII.name());
150         } finally {
151             safeClose(out);
152         }
153     }
154 
155     private static void safeClose(InputStream in) {
156         try {
157             in.close();
158         } catch (IOException e) {
159             logger.warn("Failed to close a stream.", e);
160         }
161     }
162 
163     private static void safeClose(OutputStream out) {
164         try {
165             out.close();
166         } catch (IOException e) {
167             logger.warn("Failed to close a stream.", e);
168         }
169     }
170 
171     private PemReader() { }
172 }