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