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