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