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