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 }