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