1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.PlatformDependent;
24 import io.netty.util.internal.logging.InternalLogger;
25 import io.netty.util.internal.logging.InternalLoggerFactory;
26
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.security.KeyException;
35 import java.security.KeyStore;
36 import java.security.cert.CertificateException;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42
43
44
45 final class PemReader {
46
47 private static final InternalLogger logger = InternalLoggerFactory.getInstance(PemReader.class);
48
49 private static final Pattern CERT_HEADER = Pattern.compile(
50 "-+BEGIN\\s[^-\\r\\n]*CERTIFICATE[^-\\r\\n]*-+(?:\\s|\\r|\\n)+");
51 private static final Pattern CERT_FOOTER = Pattern.compile(
52 "-+END\\s[^-\\r\\n]*CERTIFICATE[^-\\r\\n]*-+(?:\\s|\\r|\\n)*");
53 private static final Pattern KEY_HEADER = Pattern.compile(
54 "-+BEGIN\\s[^-\\r\\n]*PRIVATE\\s+KEY[^-\\r\\n]*-+(?:\\s|\\r|\\n)+");
55 private static final Pattern KEY_FOOTER = Pattern.compile(
56 "-+END\\s[^-\\r\\n]*PRIVATE\\s+KEY[^-\\r\\n]*-+(?:\\s|\\r|\\n)*");
57 private static final Pattern BODY = Pattern.compile("[a-z0-9+/=][a-z0-9+/=\\r\\n]*", 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_HEADER.matcher(content);
83 int start = 0;
84 try {
85 for (;;) {
86 if (!m.find(start)) {
87 break;
88 }
89
90
91
92
93
94 start = m.end();
95 m.usePattern(BODY);
96 if (!m.find(start)) {
97 break;
98 }
99
100 ByteBuf base64 = Unpooled.copiedBuffer(m.group(0), CharsetUtil.US_ASCII);
101 try {
102 start = m.end();
103 m.usePattern(CERT_FOOTER);
104 if (!m.find(start)) {
105
106 break;
107 }
108 ByteBuf der = Base64.decode(base64);
109 certs.add(der);
110 } finally {
111 base64.release();
112 }
113
114 start = m.end();
115 m.usePattern(CERT_HEADER);
116 }
117 } catch (Throwable e) {
118 for (ByteBuf cert : certs) {
119 cert.release();
120 }
121 PlatformDependent.throwException(e);
122 }
123
124 if (certs.isEmpty()) {
125 throw new CertificateException("found no certificates in input stream");
126 }
127
128 return certs.toArray(new ByteBuf[0]);
129 }
130
131 static ByteBuf readPrivateKey(File file) throws KeyException {
132 try {
133 InputStream in = new FileInputStream(file);
134
135 try {
136 return readPrivateKey(in);
137 } finally {
138 safeClose(in);
139 }
140 } catch (FileNotFoundException e) {
141 throw new KeyException("could not find key file: " + file);
142 }
143 }
144
145 static ByteBuf readPrivateKey(InputStream in) throws KeyException {
146 String content;
147 try {
148 content = readContent(in);
149 } catch (IOException e) {
150 throw new KeyException("failed to read key input stream", e);
151 }
152 int start = 0;
153 Matcher m = KEY_HEADER.matcher(content);
154 if (!m.find(start)) {
155 throw keyNotFoundException();
156 }
157 start = m.end();
158 m.usePattern(BODY);
159 if (!m.find(start)) {
160 throw keyNotFoundException();
161 }
162
163 ByteBuf base64 = Unpooled.copiedBuffer(m.group(0), CharsetUtil.US_ASCII);
164 try {
165 start = m.end();
166 m.usePattern(KEY_FOOTER);
167 if (!m.find(start)) {
168
169 throw keyNotFoundException();
170 }
171 return Base64.decode(base64);
172 } finally {
173 base64.release();
174 }
175 }
176
177 private static KeyException keyNotFoundException() {
178 return new KeyException("could not find a PKCS #8 private key in input stream" +
179 " (see https://netty.io/wiki/sslcontextbuilder-and-private-key.html for more information)");
180 }
181
182 private static String readContent(InputStream in) throws IOException {
183 ByteArrayOutputStream out = new ByteArrayOutputStream();
184 try {
185 byte[] buf = new byte[8192];
186 for (;;) {
187 int ret = in.read(buf);
188 if (ret < 0) {
189 break;
190 }
191 out.write(buf, 0, ret);
192 }
193 return out.toString(CharsetUtil.US_ASCII.name());
194 } finally {
195 safeClose(out);
196 }
197 }
198
199 private static void safeClose(InputStream in) {
200 try {
201 in.close();
202 } catch (IOException e) {
203 logger.warn("Failed to close a stream.", e);
204 }
205 }
206
207 private static void safeClose(OutputStream out) {
208 try {
209 out.close();
210 } catch (IOException e) {
211 logger.warn("Failed to close a stream.", e);
212 }
213 }
214
215 private PemReader() { }
216 }