1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.ssl;
17
18 import io.netty.util.CharsetUtil;
19 import io.netty.util.internal.logging.InternalLogger;
20 import io.netty.util.internal.logging.InternalLoggerFactory;
21 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
22 import org.bouncycastle.openssl.PEMDecryptorProvider;
23 import org.bouncycastle.openssl.PEMEncryptedKeyPair;
24 import org.bouncycastle.openssl.PEMKeyPair;
25 import org.bouncycastle.openssl.PEMParser;
26 import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
27 import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
28 import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
29 import org.bouncycastle.operator.InputDecryptorProvider;
30 import org.bouncycastle.operator.OperatorCreationException;
31 import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
32 import org.bouncycastle.pkcs.PKCSException;
33
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.io.FileReader;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.InputStreamReader;
40 import java.security.AccessController;
41 import java.security.PrivateKey;
42 import java.security.PrivilegedAction;
43 import java.security.Provider;
44 import java.security.Security;
45
46 final class BouncyCastlePemReader {
47 private static final String BC_PROVIDER_NAME = "BC";
48 private static final String BC_PROVIDER = "org.bouncycastle.jce.provider.BouncyCastleProvider";
49 private static final String BC_FIPS_PROVIDER_NAME = "BCFIPS";
50 private static final String BC_FIPS_PROVIDER = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider";
51 private static final String BC_PEMPARSER = "org.bouncycastle.openssl.PEMParser";
52 private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastlePemReader.class);
53
54 private static volatile Throwable unavailabilityCause;
55 private static volatile Provider bcProvider;
56 private static volatile boolean attemptedLoading;
57
58 public static boolean hasAttemptedLoading() {
59 return attemptedLoading;
60 }
61
62 public static boolean isAvailable() {
63 if (!hasAttemptedLoading()) {
64 tryLoading();
65 }
66 return unavailabilityCause == null;
67 }
68
69
70
71
72 public static Throwable unavailabilityCause() {
73 return unavailabilityCause;
74 }
75
76 private static void tryLoading() {
77 AccessController.doPrivileged(new PrivilegedAction<Void>() {
78 @Override
79 public Void run() {
80 try {
81 ClassLoader classLoader = getClass().getClassLoader();
82
83 Class.forName(BC_PEMPARSER, true, classLoader);
84
85 bcProvider = Security.getProvider(BC_PROVIDER_NAME);
86 if (bcProvider == null) {
87 bcProvider = Security.getProvider(BC_FIPS_PROVIDER_NAME);
88 }
89 if (bcProvider == null) {
90 Class<Provider> bcProviderClass;
91 try {
92 bcProviderClass = (Class<Provider>) Class.forName(BC_PROVIDER, true, classLoader);
93 } catch (ClassNotFoundException e) {
94 try {
95 bcProviderClass = (Class<Provider>) Class.forName(BC_FIPS_PROVIDER, true, classLoader);
96 } catch (ClassNotFoundException ex) {
97 e.addSuppressed(ex);
98 throw e;
99 }
100 }
101 bcProvider = bcProviderClass.getConstructor().newInstance();
102 }
103 logger.debug("Bouncy Castle provider available");
104 attemptedLoading = true;
105 } catch (Throwable e) {
106 logger.debug("Cannot load Bouncy Castle provider", e);
107 unavailabilityCause = e;
108 attemptedLoading = true;
109 }
110 return null;
111 }
112 });
113 }
114
115
116
117
118
119
120 static Provider resetBcProvider() {
121 Provider previousProvider = bcProvider;
122 bcProvider = null;
123 attemptedLoading = false;
124 unavailabilityCause = null;
125 return previousProvider;
126 }
127
128
129
130
131
132
133
134
135
136 public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) {
137 if (!isAvailable()) {
138 if (logger.isDebugEnabled()) {
139 logger.debug("Bouncy castle provider is unavailable.", unavailabilityCause());
140 }
141 return null;
142 }
143 try {
144 PEMParser parser = newParser(keyInputStream);
145 return getPrivateKey(parser, keyPassword);
146 } catch (Exception e) {
147 logger.debug("Unable to extract private key", e);
148 return null;
149 }
150 }
151
152
153
154
155
156
157
158
159
160 public static PrivateKey getPrivateKey(File keyFile, String keyPassword) {
161 if (!isAvailable()) {
162 if (logger.isDebugEnabled()) {
163 logger.debug("Bouncy castle provider is unavailable.", unavailabilityCause());
164 }
165 return null;
166 }
167 try {
168 PEMParser parser = newParser(keyFile);
169 return getPrivateKey(parser, keyPassword);
170 } catch (Exception e) {
171 logger.debug("Unable to extract private key", e);
172 return null;
173 }
174 }
175
176 private static JcaPEMKeyConverter newConverter() {
177 return new JcaPEMKeyConverter().setProvider(bcProvider);
178 }
179
180 private static PrivateKey getPrivateKey(PEMParser pemParser, String keyPassword) throws IOException,
181 PKCSException, OperatorCreationException {
182 try {
183 JcaPEMKeyConverter converter = newConverter();
184 PrivateKey pk = null;
185
186 Object object = pemParser.readObject();
187 while (object != null && pk == null) {
188 if (logger.isDebugEnabled()) {
189 logger.debug("Parsed PEM object of type {} and assume " +
190 "key is {}encrypted", object.getClass().getName(), keyPassword == null? "not " : "");
191 }
192
193 if (keyPassword == null) {
194
195 if (object instanceof PrivateKeyInfo) {
196 pk = converter.getPrivateKey((PrivateKeyInfo) object);
197 } else if (object instanceof PEMKeyPair) {
198 pk = converter.getKeyPair((PEMKeyPair) object).getPrivate();
199 } else {
200 logger.debug("Unable to handle PEM object of type {} as a non encrypted key",
201 object.getClass());
202 }
203 } else {
204
205 if (object instanceof PEMEncryptedKeyPair) {
206 PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder()
207 .setProvider(bcProvider)
208 .build(keyPassword.toCharArray());
209 pk = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate();
210 } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
211 InputDecryptorProvider pkcs8InputDecryptorProvider =
212 new JceOpenSSLPKCS8DecryptorProviderBuilder()
213 .setProvider(bcProvider)
214 .build(keyPassword.toCharArray());
215 pk = converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object)
216 .decryptPrivateKeyInfo(pkcs8InputDecryptorProvider));
217 } else {
218 logger.debug("Unable to handle PEM object of type {} as a encrypted key", object.getClass());
219 }
220 }
221
222
223 if (pk == null) {
224 object = pemParser.readObject();
225 }
226 }
227
228 if (pk == null) {
229 if (logger.isDebugEnabled()) {
230 logger.debug("No key found");
231 }
232 }
233
234 return pk;
235 } finally {
236 if (pemParser != null) {
237 try {
238 pemParser.close();
239 } catch (Exception exception) {
240 logger.debug("Failed closing pem parser", exception);
241 }
242 }
243 }
244 }
245
246 private static PEMParser newParser(File keyFile) throws FileNotFoundException {
247 return new PEMParser(new FileReader(keyFile));
248 }
249
250 private static PEMParser newParser(InputStream keyInputStream) {
251 return new PEMParser(new InputStreamReader(keyInputStream, CharsetUtil.US_ASCII));
252 }
253
254 private BouncyCastlePemReader() { }
255 }