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