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.internal.tcnative.SSLPrivateKeyMethod;
19 import io.netty.util.collection.IntCollections;
20 import io.netty.util.collection.IntObjectHashMap;
21 import io.netty.util.collection.IntObjectMap;
22 import io.netty.util.internal.ObjectUtil;
23 import io.netty.util.internal.logging.InternalLogger;
24 import io.netty.util.internal.logging.InternalLoggerFactory;
25
26 import java.security.InvalidAlgorithmParameterException;
27 import java.security.InvalidKeyException;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.PrivateKey;
30 import java.security.Provider;
31 import java.security.Security;
32 import java.security.Signature;
33 import java.security.spec.MGF1ParameterSpec;
34 import java.security.spec.PSSParameterSpec;
35 import java.util.Objects;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ConcurrentMap;
38
39
40
41
42
43 final class JdkDelegatingPrivateKeyMethod implements SSLPrivateKeyMethod {
44
45 private static final InternalLogger logger =
46 InternalLoggerFactory.getInstance(JdkDelegatingPrivateKeyMethod.class);
47
48 private static final IntObjectMap<String> SSL_TO_JDK_SIGNATURE_ALGORITHM;
49 private static final ConcurrentMap<CacheKey, String> PROVIDER_CACHE = new ConcurrentHashMap<>();
50
51 static {
52 IntObjectMap<String> algorithmMap = new IntObjectHashMap<>();
53
54
55 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1, "SHA1withRSA");
56 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256, "SHA256withRSA");
57 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384, "SHA384withRSA");
58 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512, "SHA512withRSA");
59 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1, "MD5andSHA1withRSA");
60
61
62 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1, "SHA1withECDSA");
63 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, "SHA256withECDSA");
64 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384, "SHA384withECDSA");
65 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512, "SHA512withECDSA");
66
67
68 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256, "RSASSA-PSS");
69 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384, "RSASSA-PSS");
70 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512, "RSASSA-PSS");
71
72
73 algorithmMap.put(OpenSslAsyncPrivateKeyMethod.SSL_SIGN_ED25519, "EdDSA");
74
75 SSL_TO_JDK_SIGNATURE_ALGORITHM = IntCollections.unmodifiableMap(algorithmMap);
76 }
77
78 private final PrivateKey privateKey;
79 private final String privateKeyTypeName;
80
81
82
83
84
85
86 JdkDelegatingPrivateKeyMethod(PrivateKey privateKey) {
87 this.privateKey = ObjectUtil.checkNotNull(privateKey, "privateKey");
88 this.privateKeyTypeName = privateKey.getClass().getName();
89 }
90
91 @Override
92 public byte[] sign(long ssl, int signatureAlgorithm, byte[] input) throws Exception {
93 Signature signature = createSignature(signatureAlgorithm);
94 signature.update(input);
95 byte[] result = signature.sign();
96
97 if (logger.isDebugEnabled()) {
98 logger.debug("Signing operation completed successfully, result length: {}", result.length);
99 }
100 return result;
101 }
102
103 @Override
104 public byte[] decrypt(long ssl, byte[] input) {
105
106
107 throw new UnsupportedOperationException("Direct decryption is not supported");
108 }
109
110 private Signature createSignature(int opensslAlgorithm)
111 throws NoSuchAlgorithmException {
112 String jdkAlgorithm = SSL_TO_JDK_SIGNATURE_ALGORITHM.get(opensslAlgorithm);
113 if (jdkAlgorithm == null) {
114 String errorMsg = "Unsupported signature algorithm: " + opensslAlgorithm;
115 throw new NoSuchAlgorithmException(errorMsg);
116 }
117
118 CacheKey cacheKey = new CacheKey(jdkAlgorithm, privateKeyTypeName);
119
120
121 String cachedProviderName = PROVIDER_CACHE.get(cacheKey);
122 if (cachedProviderName != null) {
123 try {
124 Signature signature = Signature.getInstance(jdkAlgorithm, cachedProviderName);
125 configureOpenSslAlgorithmParameters(signature, opensslAlgorithm);
126 signature.initSign(privateKey);
127 if (logger.isDebugEnabled()) {
128 logger.debug("Using cached provider {} for OpenSSL algorithm {} ({}) with key type {}",
129 cachedProviderName, opensslAlgorithm, jdkAlgorithm, privateKeyTypeName);
130 }
131 return signature;
132 } catch (Exception e) {
133
134 PROVIDER_CACHE.remove(cacheKey);
135 if (logger.isDebugEnabled()) {
136 logger.debug("Cached provider {} failed for key type {}, removing from cache: {}",
137 cachedProviderName, privateKeyTypeName, e.getMessage());
138 }
139 }
140 }
141
142
143 Signature signature = findCompatibleSignature(opensslAlgorithm, jdkAlgorithm);
144 PROVIDER_CACHE.put(cacheKey, signature.getProvider().getName());
145
146 if (logger.isDebugEnabled()) {
147 logger.debug("Discovered and cached provider {} for OpenSSL algorithm {} ({}) with key type {}",
148 signature.getProvider().getName(), opensslAlgorithm, jdkAlgorithm, privateKeyTypeName);
149 }
150
151 return signature;
152 }
153
154 private Signature findCompatibleSignature(int opensslAlgorithm,
155 String jdkAlgorithm) throws NoSuchAlgorithmException {
156
157
158 try {
159 Signature signature = Signature.getInstance(jdkAlgorithm);
160 configureOpenSslAlgorithmParameters(signature, opensslAlgorithm);
161 signature.initSign(privateKey);
162 if (logger.isDebugEnabled()) {
163 logger.debug("Default provider {} handles key type {} for OpenSSL algorithm {} ({})",
164 signature.getProvider().getName(), privateKey.getClass().getName(),
165 opensslAlgorithm, jdkAlgorithm);
166 }
167 return signature;
168 } catch (InvalidKeyException e) {
169
170 if (logger.isDebugEnabled()) {
171 logger.debug("Default provider cannot handle key type {} for OpenSSL algorithm {} ({}): {}",
172 privateKey.getClass().getName(), opensslAlgorithm, jdkAlgorithm, e.getMessage());
173 }
174 } catch (Exception e) {
175
176 if (logger.isDebugEnabled()) {
177 logger.debug("Default provider failed for OpenSSL algorithm {} ({}): {}",
178 opensslAlgorithm, jdkAlgorithm, e.getMessage());
179 }
180 }
181
182
183 Provider[] providers = Security.getProviders();
184 for (Provider provider : providers) {
185 try {
186 Signature signature = Signature.getInstance(jdkAlgorithm, provider);
187 configureOpenSslAlgorithmParameters(signature, opensslAlgorithm);
188 signature.initSign(privateKey);
189
190 if (logger.isDebugEnabled()) {
191 logger.debug("Found compatible provider {} for key type {} with OpenSSL algorithm {} ({})",
192 provider.getName(), privateKey.getClass().getName(), opensslAlgorithm, jdkAlgorithm);
193 }
194 return signature;
195 } catch (InvalidKeyException e) {
196
197 if (logger.isTraceEnabled()) {
198 logger.trace("Provider {} cannot handle key type {}: {}",
199 provider.getName(), privateKey.getClass().getName(), e.getMessage());
200 }
201 } catch (Exception e) {
202
203 if (logger.isTraceEnabled()) {
204 logger.trace("Provider {} failed for OpenSSL algorithm {} ({}): {}",
205 provider.getName(), opensslAlgorithm, jdkAlgorithm, e.getMessage());
206 }
207 }
208 }
209
210 throw new NoSuchAlgorithmException("No provider found for OpenSSL algorithm " + opensslAlgorithm +
211 " (" + jdkAlgorithm + ") with private key type: " + privateKey.getClass().getName());
212 }
213
214 private static void configureOpenSslAlgorithmParameters(Signature signature, int opensslAlgorithm)
215 throws InvalidAlgorithmParameterException {
216
217 if (opensslAlgorithm == OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256) {
218
219 configurePssParameters(signature, MGF1ParameterSpec.SHA256, 32);
220 } else if (opensslAlgorithm == OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384) {
221
222 configurePssParameters(signature, MGF1ParameterSpec.SHA384, 48);
223 } else if (opensslAlgorithm == OpenSslAsyncPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512) {
224
225 configurePssParameters(signature, MGF1ParameterSpec.SHA512, 64);
226 } else if (SSL_TO_JDK_SIGNATURE_ALGORITHM.containsKey(opensslAlgorithm)) {
227
228 if (logger.isTraceEnabled()) {
229 logger.trace("No parameter configuration needed for OpenSSL algorithm {}", opensslAlgorithm);
230 }
231 } else {
232 if (logger.isDebugEnabled()) {
233 logger.debug("Unknown OpenSSL algorithm {}, using default configuration", opensslAlgorithm);
234 }
235 }
236 }
237
238 private static void configurePssParameters(Signature signature,
239 MGF1ParameterSpec mgfSpec, int saltLength)
240 throws InvalidAlgorithmParameterException {
241 PSSParameterSpec pssSpec = new PSSParameterSpec(mgfSpec.getDigestAlgorithm(), "MGF1", mgfSpec, saltLength, 1);
242 signature.setParameter(pssSpec);
243
244 if (logger.isDebugEnabled()) {
245 logger.debug("Configured PSS parameters: hash={}, saltLength={}", mgfSpec.getDigestAlgorithm(), saltLength);
246 }
247 }
248
249 private static final class CacheKey {
250 private final String jdkAlgorithm;
251 private final String keyTypeName;
252 private final int hashCode;
253
254 @Override
255 public boolean equals(Object o) {
256 if (o == null || getClass() != o.getClass()) {
257 return false;
258 }
259 CacheKey cacheKey = (CacheKey) o;
260 return Objects.equals(cacheKey.jdkAlgorithm, jdkAlgorithm)
261 && Objects.equals(cacheKey.keyTypeName, keyTypeName);
262 }
263
264 @Override
265 public int hashCode() {
266 return hashCode;
267 }
268
269 CacheKey(String jdkAlgorithm, String keyTypeName) {
270 this.jdkAlgorithm = jdkAlgorithm;
271 this.keyTypeName = keyTypeName;
272 this.hashCode = 31 * jdkAlgorithm.hashCode() + keyTypeName.hashCode();
273 }
274 }
275 }