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.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.security.KeyException;
33  import java.security.KeyStore;
34  import java.security.cert.CertificateException;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  
40  /**
41   * Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link KeyStore} easily.
42   */
43  final class PemReader {
44  
45      private static final InternalLogger logger = InternalLoggerFactory.getInstance(PemReader.class);
46  
47      private static final Pattern CERT_PATTERN = Pattern.compile(
48              "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header
49                      "([a-z0-9+/=\\r\\n]+)" +                    // Base64 text
50                      "-+END\\s+.*CERTIFICATE[^-]*-+",            // Footer
51              Pattern.CASE_INSENSITIVE);
52      private static final Pattern KEY_PATTERN = Pattern.compile(
53              "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
54                      "([a-z0-9+/=\\r\\n]+)" +                       // Base64 text
55                      "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+",            // Footer
56              Pattern.CASE_INSENSITIVE);
57  
58      static ByteBuf[] readCertificates(File file) throws CertificateException {
59          String content;
60          try {
61              content = readContent(file);
62          } catch (IOException e) {
63              throw new CertificateException("failed to read a file: " + file, e);
64          }
65  
66          List<ByteBuf> certs = new ArrayList<ByteBuf>();
67          Matcher m = CERT_PATTERN.matcher(content);
68          int start = 0;
69          for (;;) {
70              if (!m.find(start)) {
71                  break;
72              }
73  
74              ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
75              ByteBuf der = Base64.decode(base64);
76              base64.release();
77              certs.add(der);
78  
79              start = m.end();
80          }
81  
82          if (certs.isEmpty()) {
83              throw new CertificateException("found no certificates: " + file);
84          }
85  
86          return certs.toArray(new ByteBuf[certs.size()]);
87      }
88  
89      static ByteBuf readPrivateKey(File file) throws KeyException {
90          String content;
91          try {
92              content = readContent(file);
93          } catch (IOException e) {
94              throw new KeyException("failed to read a file: " + file, e);
95          }
96  
97          Matcher m = KEY_PATTERN.matcher(content);
98          if (!m.find()) {
99              throw new KeyException("found no private key: " + file);
100         }
101 
102         ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
103         ByteBuf der = Base64.decode(base64);
104         base64.release();
105         return der;
106     }
107 
108     private static String readContent(File file) throws IOException {
109         InputStream in = new FileInputStream(file);
110         ByteArrayOutputStream out = new ByteArrayOutputStream();
111         try {
112             byte[] buf = new byte[8192];
113             for (;;) {
114                 int ret = in.read(buf);
115                 if (ret < 0) {
116                     break;
117                 }
118                 out.write(buf, 0, ret);
119             }
120             return out.toString(CharsetUtil.US_ASCII.name());
121         } finally {
122             safeClose(in);
123             safeClose(out);
124         }
125     }
126 
127     private static void safeClose(InputStream in) {
128         try {
129             in.close();
130         } catch (IOException e) {
131             logger.warn("Failed to close a stream.", e);
132         }
133     }
134 
135     private static void safeClose(OutputStream out) {
136         try {
137             out.close();
138         } catch (IOException e) {
139             logger.warn("Failed to close a stream.", e);
140         }
141     }
142 
143     private PemReader() { }
144 }