|
1 /* |
|
2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package sun.security.ssl; |
|
27 |
|
28 import java.io.IOException; |
|
29 import java.math.BigInteger; |
|
30 import java.security.GeneralSecurityException; |
|
31 import java.security.InvalidKeyException; |
|
32 import java.security.KeyFactory; |
|
33 import java.security.KeyPair; |
|
34 import java.security.KeyPairGenerator; |
|
35 import java.security.PrivateKey; |
|
36 import java.security.PublicKey; |
|
37 import java.security.SecureRandom; |
|
38 import java.security.spec.AlgorithmParameterSpec; |
|
39 import javax.crypto.KeyAgreement; |
|
40 import javax.crypto.SecretKey; |
|
41 import javax.crypto.interfaces.DHPublicKey; |
|
42 import javax.crypto.spec.DHParameterSpec; |
|
43 import javax.crypto.spec.DHPublicKeySpec; |
|
44 import javax.crypto.spec.SecretKeySpec; |
|
45 import javax.net.ssl.SSLHandshakeException; |
|
46 import sun.security.action.GetPropertyAction; |
|
47 import sun.security.ssl.CipherSuite.HashAlg; |
|
48 import sun.security.ssl.SupportedGroupsExtension.NamedGroup; |
|
49 import sun.security.ssl.SupportedGroupsExtension.NamedGroupType; |
|
50 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups; |
|
51 import sun.security.ssl.X509Authentication.X509Possession; |
|
52 import sun.security.util.KeyUtil; |
|
53 |
|
54 final class DHKeyExchange { |
|
55 static final SSLPossessionGenerator poGenerator = |
|
56 new DHEPossessionGenerator(false); |
|
57 static final SSLPossessionGenerator poExportableGenerator = |
|
58 new DHEPossessionGenerator(true); |
|
59 static final SSLKeyAgreementGenerator kaGenerator = |
|
60 new DHEKAGenerator(); |
|
61 |
|
62 static final class DHECredentials implements SSLCredentials { |
|
63 final DHPublicKey popPublicKey; |
|
64 final NamedGroup namedGroup; |
|
65 |
|
66 DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) { |
|
67 this.popPublicKey = popPublicKey; |
|
68 this.namedGroup = namedGroup; |
|
69 } |
|
70 |
|
71 static DHECredentials valueOf(NamedGroup ng, |
|
72 byte[] encodedPublic) throws IOException, GeneralSecurityException { |
|
73 |
|
74 if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) { |
|
75 throw new RuntimeException( |
|
76 "Credentials decoding: Not FFDHE named group"); |
|
77 } |
|
78 |
|
79 if (encodedPublic == null || encodedPublic.length == 0) { |
|
80 return null; |
|
81 } |
|
82 |
|
83 DHParameterSpec params = (DHParameterSpec)ng.getParameterSpec(); |
|
84 if (params == null) { |
|
85 return null; |
|
86 } |
|
87 |
|
88 KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman"); |
|
89 DHPublicKeySpec spec = new DHPublicKeySpec( |
|
90 new BigInteger(1, encodedPublic), |
|
91 params.getP(), params.getG()); |
|
92 DHPublicKey publicKey = |
|
93 (DHPublicKey)kf.generatePublic(spec); |
|
94 |
|
95 return new DHECredentials(publicKey, ng); |
|
96 } |
|
97 } |
|
98 |
|
99 static final class DHEPossession implements SSLPossession { |
|
100 final PrivateKey privateKey; |
|
101 final DHPublicKey publicKey; |
|
102 final NamedGroup namedGroup; |
|
103 |
|
104 DHEPossession(NamedGroup namedGroup, SecureRandom random) { |
|
105 try { |
|
106 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DH"); |
|
107 DHParameterSpec params = |
|
108 (DHParameterSpec)namedGroup.getParameterSpec(); |
|
109 kpg.initialize(params, random); |
|
110 KeyPair kp = generateDHKeyPair(kpg); |
|
111 if (kp == null) { |
|
112 throw new RuntimeException("Could not generate DH keypair"); |
|
113 } |
|
114 privateKey = kp.getPrivate(); |
|
115 publicKey = (DHPublicKey)kp.getPublic(); |
|
116 } catch (GeneralSecurityException gse) { |
|
117 throw new RuntimeException( |
|
118 "Could not generate DH keypair", gse); |
|
119 } |
|
120 |
|
121 this.namedGroup = namedGroup; |
|
122 } |
|
123 |
|
124 DHEPossession(int keyLength, SecureRandom random) { |
|
125 DHParameterSpec params = |
|
126 PredefinedDHParameterSpecs.definedParams.get(keyLength); |
|
127 try { |
|
128 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DiffieHellman"); |
|
129 if (params != null) { |
|
130 kpg.initialize(params, random); |
|
131 } else { |
|
132 kpg.initialize(keyLength, random); |
|
133 } |
|
134 |
|
135 KeyPair kp = generateDHKeyPair(kpg); |
|
136 if (kp == null) { |
|
137 throw new RuntimeException( |
|
138 "Could not generate DH keypair of " + |
|
139 keyLength + " bits"); |
|
140 } |
|
141 privateKey = kp.getPrivate(); |
|
142 publicKey = (DHPublicKey)kp.getPublic(); |
|
143 } catch (GeneralSecurityException gse) { |
|
144 throw new RuntimeException( |
|
145 "Could not generate DH keypair", gse); |
|
146 } |
|
147 |
|
148 this.namedGroup = NamedGroup.valueOf(publicKey.getParams()); |
|
149 } |
|
150 |
|
151 DHEPossession(DHECredentials credentials, SecureRandom random) { |
|
152 try { |
|
153 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DH"); |
|
154 kpg.initialize(credentials.popPublicKey.getParams(), random); |
|
155 KeyPair kp = generateDHKeyPair(kpg); |
|
156 if (kp == null) { |
|
157 throw new RuntimeException("Could not generate DH keypair"); |
|
158 } |
|
159 privateKey = kp.getPrivate(); |
|
160 publicKey = (DHPublicKey)kp.getPublic(); |
|
161 } catch (GeneralSecurityException gse) { |
|
162 throw new RuntimeException( |
|
163 "Could not generate DH keypair", gse); |
|
164 } |
|
165 |
|
166 this.namedGroup = credentials.namedGroup; |
|
167 } |
|
168 |
|
169 // Generate and validate DHPublicKeySpec |
|
170 private KeyPair generateDHKeyPair( |
|
171 KeyPairGenerator kpg) throws GeneralSecurityException { |
|
172 boolean doExtraValiadtion = |
|
173 (!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName())); |
|
174 boolean isRecovering = false; |
|
175 for (int i = 0; i <= 2; i++) { // Try to recove from failure. |
|
176 KeyPair kp = kpg.generateKeyPair(); |
|
177 // validate the Diffie-Hellman public key |
|
178 if (doExtraValiadtion) { |
|
179 DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic()); |
|
180 try { |
|
181 KeyUtil.validate(spec); |
|
182 } catch (InvalidKeyException ivke) { |
|
183 if (isRecovering) { |
|
184 throw ivke; |
|
185 } |
|
186 // otherwise, ignore the exception and try again |
|
187 continue; |
|
188 } |
|
189 } |
|
190 |
|
191 return kp; |
|
192 } |
|
193 |
|
194 return null; |
|
195 } |
|
196 |
|
197 private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) { |
|
198 if (key instanceof DHPublicKey) { |
|
199 DHPublicKey dhKey = (DHPublicKey)key; |
|
200 DHParameterSpec params = dhKey.getParams(); |
|
201 return new DHPublicKeySpec(dhKey.getY(), |
|
202 params.getP(), params.getG()); |
|
203 } |
|
204 try { |
|
205 KeyFactory factory = JsseJce.getKeyFactory("DiffieHellman"); |
|
206 return factory.getKeySpec(key, DHPublicKeySpec.class); |
|
207 } catch (Exception e) { |
|
208 throw new RuntimeException(e); |
|
209 } |
|
210 } |
|
211 |
|
212 @Override |
|
213 public byte[] encode() { |
|
214 // TODO: cannonical the return byte array length. |
|
215 return publicKey.getY().toByteArray(); |
|
216 } |
|
217 } |
|
218 |
|
219 private static final class |
|
220 DHEPossessionGenerator implements SSLPossessionGenerator { |
|
221 // Flag to use smart ephemeral DH key which size matches the corresponding |
|
222 // authentication key |
|
223 private static final boolean useSmartEphemeralDHKeys; |
|
224 |
|
225 // Flag to use legacy ephemeral DH key which size is 512 bits for |
|
226 // exportable cipher suites, and 768 bits for others |
|
227 private static final boolean useLegacyEphemeralDHKeys; |
|
228 |
|
229 // The customized ephemeral DH key size for non-exportable cipher suites. |
|
230 private static final int customizedDHKeySize; |
|
231 |
|
232 // Is it for exportable cipher suite? |
|
233 private final boolean exportable; |
|
234 |
|
235 static { |
|
236 String property = GetPropertyAction.privilegedGetProperty( |
|
237 "jdk.tls.ephemeralDHKeySize"); |
|
238 if (property == null || property.length() == 0) { |
|
239 useLegacyEphemeralDHKeys = false; |
|
240 useSmartEphemeralDHKeys = false; |
|
241 customizedDHKeySize = -1; |
|
242 } else if ("matched".equals(property)) { |
|
243 useLegacyEphemeralDHKeys = false; |
|
244 useSmartEphemeralDHKeys = true; |
|
245 customizedDHKeySize = -1; |
|
246 } else if ("legacy".equals(property)) { |
|
247 useLegacyEphemeralDHKeys = true; |
|
248 useSmartEphemeralDHKeys = false; |
|
249 customizedDHKeySize = -1; |
|
250 } else { |
|
251 useLegacyEphemeralDHKeys = false; |
|
252 useSmartEphemeralDHKeys = false; |
|
253 |
|
254 try { |
|
255 // DH parameter generation can be extremely slow, best to |
|
256 // use one of the supported pre-computed DH parameters |
|
257 // (see DHCrypt class). |
|
258 customizedDHKeySize = Integer.parseUnsignedInt(property); |
|
259 if (customizedDHKeySize < 1024 || |
|
260 customizedDHKeySize > 8192 || |
|
261 (customizedDHKeySize & 0x3f) != 0) { |
|
262 throw new IllegalArgumentException( |
|
263 "Unsupported customized DH key size: " + |
|
264 customizedDHKeySize + ". " + |
|
265 "The key size must be multiple of 64, " + |
|
266 "and range from 1024 to 8192 (inclusive)"); |
|
267 } |
|
268 } catch (NumberFormatException nfe) { |
|
269 throw new IllegalArgumentException( |
|
270 "Invalid system property jdk.tls.ephemeralDHKeySize"); |
|
271 } |
|
272 } |
|
273 } |
|
274 |
|
275 // Prevent instantiation of this class. |
|
276 private DHEPossessionGenerator(boolean exportable) { |
|
277 this.exportable = exportable; |
|
278 } |
|
279 |
|
280 // Used for ServerKeyExchange, TLS 1.2 and prior versions. |
|
281 @Override |
|
282 public SSLPossession createPossession(HandshakeContext context) { |
|
283 NamedGroup preferableNamedGroup = null; |
|
284 if (!useLegacyEphemeralDHKeys && |
|
285 (context.clientRequestedNamedGroups != null) && |
|
286 (!context.clientRequestedNamedGroups.isEmpty())) { |
|
287 preferableNamedGroup = |
|
288 SupportedGroups.getPreferredGroup( |
|
289 context.negotiatedProtocol, |
|
290 context.algorithmConstraints, |
|
291 NamedGroupType.NAMED_GROUP_FFDHE, |
|
292 context.clientRequestedNamedGroups); |
|
293 if (preferableNamedGroup != null) { |
|
294 return new DHEPossession(preferableNamedGroup, |
|
295 context.sslContext.getSecureRandom()); |
|
296 } |
|
297 } |
|
298 |
|
299 /* |
|
300 * 768 bits ephemeral DH private keys were used to be used in |
|
301 * ServerKeyExchange except that exportable ciphers max out at 512 |
|
302 * bits modulus values. We still adhere to this behavior in legacy |
|
303 * mode (system property "jdk.tls.ephemeralDHKeySize" is defined |
|
304 * as "legacy"). |
|
305 * |
|
306 * Old JDK (JDK 7 and previous) releases don't support DH keys |
|
307 * bigger than 1024 bits. We have to consider the compatibility |
|
308 * requirement. 1024 bits DH key is always used for non-exportable |
|
309 * cipher suites in default mode (system property |
|
310 * "jdk.tls.ephemeralDHKeySize" is not defined). |
|
311 * |
|
312 * However, if applications want more stronger strength, setting |
|
313 * system property "jdk.tls.ephemeralDHKeySize" to "matched" |
|
314 * is a workaround to use ephemeral DH key which size matches the |
|
315 * corresponding authentication key. For example, if the public key |
|
316 * size of an authentication certificate is 2048 bits, then the |
|
317 * ephemeral DH key size should be 2048 bits accordingly unless |
|
318 * the cipher suite is exportable. This key sizing scheme keeps |
|
319 * the cryptographic strength consistent between authentication |
|
320 * keys and key-exchange keys. |
|
321 * |
|
322 * Applications may also want to customize the ephemeral DH key |
|
323 * size to a fixed length for non-exportable cipher suites. This |
|
324 * can be approached by setting system property |
|
325 * "jdk.tls.ephemeralDHKeySize" to a valid positive integer between |
|
326 * 1024 and 8192 bits, inclusive. |
|
327 * |
|
328 * Note that the minimum acceptable key size is 1024 bits except |
|
329 * exportable cipher suites or legacy mode. |
|
330 * |
|
331 * Note that per RFC 2246, the key size limit of DH is 512 bits for |
|
332 * exportable cipher suites. Because of the weakness, exportable |
|
333 * cipher suites are deprecated since TLS v1.1 and they are not |
|
334 * enabled by default in Oracle provider. The legacy behavior is |
|
335 * reserved and 512 bits DH key is always used for exportable |
|
336 * cipher suites. |
|
337 */ |
|
338 int keySize = exportable ? 512 : 1024; // default mode |
|
339 if (!exportable) { |
|
340 if (useLegacyEphemeralDHKeys) { // legacy mode |
|
341 keySize = 768; |
|
342 } else if (useSmartEphemeralDHKeys) { // matched mode |
|
343 PrivateKey key = null; |
|
344 ServerHandshakeContext shc = |
|
345 (ServerHandshakeContext)context; |
|
346 if (shc.interimAuthn instanceof X509Possession) { |
|
347 key = ((X509Possession)shc.interimAuthn).popPrivateKey; |
|
348 } |
|
349 |
|
350 if (key != null) { |
|
351 int ks = KeyUtil.getKeySize(key); |
|
352 |
|
353 // DH parameter generation can be extremely slow, make |
|
354 // sure to use one of the supported pre-computed DH |
|
355 // parameters. |
|
356 // |
|
357 // Old deployed applications may not be ready to |
|
358 // support DH key sizes bigger than 2048 bits. Please |
|
359 // DON'T use value other than 1024 and 2048 at present. |
|
360 // May improve the underlying providers and key size |
|
361 // limit in the future when the compatibility and |
|
362 // interoperability impact is limited. |
|
363 keySize = ks <= 1024 ? 1024 : 2048; |
|
364 } // Otherwise, anonymous cipher suites, 1024-bit is used. |
|
365 } else if (customizedDHKeySize > 0) { // customized mode |
|
366 keySize = customizedDHKeySize; |
|
367 } |
|
368 } |
|
369 |
|
370 return new DHEPossession( |
|
371 keySize, context.sslContext.getSecureRandom()); |
|
372 } |
|
373 } |
|
374 |
|
375 private static final |
|
376 class DHEKAGenerator implements SSLKeyAgreementGenerator { |
|
377 static private DHEKAGenerator instance = new DHEKAGenerator(); |
|
378 |
|
379 // Prevent instantiation of this class. |
|
380 private DHEKAGenerator() { |
|
381 // blank |
|
382 } |
|
383 |
|
384 @Override |
|
385 public SSLKeyDerivation createKeyDerivation( |
|
386 HandshakeContext context) throws IOException { |
|
387 DHEPossession dhePossession = null; |
|
388 DHECredentials dheCredentials = null; |
|
389 for (SSLPossession poss : context.handshakePossessions) { |
|
390 if (!(poss instanceof DHEPossession)) { |
|
391 continue; |
|
392 } |
|
393 |
|
394 DHEPossession dhep = (DHEPossession)poss; |
|
395 for (SSLCredentials cred : context.handshakeCredentials) { |
|
396 if (!(cred instanceof DHECredentials)) { |
|
397 continue; |
|
398 } |
|
399 DHECredentials dhec = (DHECredentials)cred; |
|
400 if (dhep.namedGroup != null && dhec.namedGroup != null) { |
|
401 if (dhep.namedGroup.equals(dhec.namedGroup)) { |
|
402 dheCredentials = (DHECredentials)cred; |
|
403 break; |
|
404 } |
|
405 } else { |
|
406 DHParameterSpec pps = dhep.publicKey.getParams(); |
|
407 DHParameterSpec cps = dhec.popPublicKey.getParams(); |
|
408 if (pps.getP().equals(cps.getP()) && |
|
409 pps.getG().equals(cps.getG())) { |
|
410 dheCredentials = (DHECredentials)cred; |
|
411 break; |
|
412 } |
|
413 } |
|
414 } |
|
415 |
|
416 if (dheCredentials != null) { |
|
417 dhePossession = (DHEPossession)poss; |
|
418 break; |
|
419 } |
|
420 } |
|
421 |
|
422 if (dhePossession == null || dheCredentials == null) { |
|
423 context.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
424 "No sufficient DHE key agreement parameters negotiated"); |
|
425 } |
|
426 |
|
427 return new DHEKAKeyDerivation(context, |
|
428 dhePossession.privateKey, dheCredentials.popPublicKey); |
|
429 } |
|
430 |
|
431 private static final |
|
432 class DHEKAKeyDerivation implements SSLKeyDerivation { |
|
433 private final HandshakeContext context; |
|
434 private final PrivateKey localPrivateKey; |
|
435 private final PublicKey peerPublicKey; |
|
436 |
|
437 DHEKAKeyDerivation(HandshakeContext context, |
|
438 PrivateKey localPrivateKey, |
|
439 PublicKey peerPublicKey) { |
|
440 this.context = context; |
|
441 this.localPrivateKey = localPrivateKey; |
|
442 this.peerPublicKey = peerPublicKey; |
|
443 } |
|
444 |
|
445 @Override |
|
446 public SecretKey deriveKey(String algorithm, |
|
447 AlgorithmParameterSpec params) throws IOException { |
|
448 if (!context.negotiatedProtocol.useTLS13PlusSpec()) { |
|
449 return t12DeriveKey(algorithm, params); |
|
450 } else { |
|
451 return t13DeriveKey(algorithm, params); |
|
452 } |
|
453 } |
|
454 |
|
455 private SecretKey t12DeriveKey(String algorithm, |
|
456 AlgorithmParameterSpec params) throws IOException { |
|
457 try { |
|
458 KeyAgreement ka = JsseJce.getKeyAgreement("DH"); |
|
459 ka.init(localPrivateKey); |
|
460 ka.doPhase(peerPublicKey, true); |
|
461 SecretKey preMasterSecret = |
|
462 ka.generateSecret("TlsPremasterSecret"); |
|
463 SSLMasterKeyDerivation mskd = |
|
464 SSLMasterKeyDerivation.valueOf( |
|
465 context.negotiatedProtocol); |
|
466 SSLKeyDerivation kd = mskd.createKeyDerivation( |
|
467 context, preMasterSecret); |
|
468 return kd.deriveKey("TODO", params); |
|
469 } catch (GeneralSecurityException gse) { |
|
470 throw (SSLHandshakeException) new SSLHandshakeException( |
|
471 "Could not generate secret").initCause(gse); |
|
472 } |
|
473 } |
|
474 |
|
475 private SecretKey t13DeriveKey(String algorithm, |
|
476 AlgorithmParameterSpec params) throws IOException { |
|
477 try { |
|
478 KeyAgreement ka = JsseJce.getKeyAgreement("DH"); |
|
479 ka.init(localPrivateKey); |
|
480 ka.doPhase(peerPublicKey, true); |
|
481 SecretKey sharedSecret = |
|
482 ka.generateSecret("TlsPremasterSecret"); |
|
483 |
|
484 HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; |
|
485 SSLKeyDerivation kd = context.handshakeKeyDerivation; |
|
486 HKDF hkdf = new HKDF(hashAlg.name); |
|
487 if (kd == null) { // No PSK is in use. |
|
488 // If PSK is not in use Early Secret will still be |
|
489 // HKDF-Extract(0, 0). |
|
490 byte[] zeros = new byte[hashAlg.hashLength]; |
|
491 SecretKeySpec ikm = |
|
492 new SecretKeySpec(zeros, "TlsPreSharedSecret"); |
|
493 SecretKey earlySecret = |
|
494 hkdf.extract(zeros, ikm, "TlsEarlySecret"); |
|
495 kd = new SSLSecretDerivation(context, earlySecret); |
|
496 } |
|
497 |
|
498 // derive salt secret |
|
499 SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); |
|
500 |
|
501 // derive handshake secret |
|
502 return hkdf.extract(saltSecret, sharedSecret, algorithm); |
|
503 } catch (GeneralSecurityException gse) { |
|
504 throw (SSLHandshakeException) new SSLHandshakeException( |
|
505 "Could not generate secret").initCause(gse); |
|
506 } |
|
507 } |
|
508 } |
|
509 } |
|
510 } |