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 org.jboss.netty.handler.ssl;
18  
19  import org.jboss.netty.buffer.ChannelBuffer;
20  import org.jboss.netty.buffer.ChannelBuffers;
21  import org.jboss.netty.handler.codec.base64.Base64;
22  import org.jboss.netty.logging.InternalLogger;
23  import org.jboss.netty.logging.InternalLoggerFactory;
24  import org.jboss.netty.util.CharsetUtil;
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 ChannelBuffer[] 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<ChannelBuffer> certs = new ArrayList<ChannelBuffer>();
67          Matcher m = CERT_PATTERN.matcher(content);
68          int start = 0;
69          for (;;) {
70              if (!m.find(start)) {
71                  break;
72              }
73  
74              certs.add(Base64.decode(ChannelBuffers.copiedBuffer(m.group(1), CharsetUtil.US_ASCII)));
75              start = m.end();
76          }
77  
78          if (certs.isEmpty()) {
79              throw new CertificateException("found no certificates: " + file);
80          }
81  
82          return certs.toArray(new ChannelBuffer[certs.size()]);
83      }
84  
85      static ChannelBuffer readPrivateKey(File file) throws KeyException {
86          String content;
87          try {
88              content = readContent(file);
89          } catch (IOException e) {
90              throw new KeyException("failed to read a file: " + file, e);
91          }
92  
93          Matcher m = KEY_PATTERN.matcher(content);
94          if (!m.find()) {
95              throw new KeyException("found no private key: " + file);
96          }
97  
98          return Base64.decode(ChannelBuffers.copiedBuffer(m.group(1), CharsetUtil.US_ASCII));
99      }
100 
101     private static String readContent(File file) throws IOException {
102         InputStream in = new FileInputStream(file);
103         ByteArrayOutputStream out = new ByteArrayOutputStream();
104         try {
105             byte[] buf = new byte[8192];
106             for (;;) {
107                 int ret = in.read(buf);
108                 if (ret < 0) {
109                     break;
110                 }
111                 out.write(buf, 0, ret);
112             }
113             return out.toString(CharsetUtil.US_ASCII.name());
114         } finally {
115             safeClose(in);
116             safeClose(out);
117         }
118     }
119 
120     private static void safeClose(InputStream in) {
121         try {
122             in.close();
123         } catch (IOException e) {
124             logger.warn("Failed to close a stream.", e);
125         }
126     }
127 
128     private static void safeClose(OutputStream out) {
129         try {
130             out.close();
131         } catch (IOException e) {
132             logger.warn("Failed to close a stream.", e);
133         }
134     }
135 
136     private PemReader() { }
137 }