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