Path: blob/master/src/java.base/share/classes/sun/security/ssl/DHKeyExchange.java
41159 views
/*1* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.ssl;2627import java.io.IOException;28import java.math.BigInteger;29import java.security.GeneralSecurityException;30import java.security.InvalidKeyException;31import java.security.KeyFactory;32import java.security.KeyPair;33import java.security.KeyPairGenerator;34import java.security.NoSuchAlgorithmException;35import java.security.PrivateKey;36import java.security.PublicKey;37import java.security.SecureRandom;38import java.security.spec.InvalidKeySpecException;39import javax.crypto.interfaces.DHPublicKey;40import javax.crypto.spec.DHParameterSpec;41import javax.crypto.spec.DHPublicKeySpec;42import sun.security.action.GetPropertyAction;43import sun.security.ssl.NamedGroup.NamedGroupSpec;44import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;45import sun.security.ssl.X509Authentication.X509Possession;46import sun.security.util.KeyUtil;4748final class DHKeyExchange {49static final SSLPossessionGenerator poGenerator =50new DHEPossessionGenerator(false);51static final SSLPossessionGenerator poExportableGenerator =52new DHEPossessionGenerator(true);53static final SSLKeyAgreementGenerator kaGenerator =54new DHEKAGenerator();5556static final class DHECredentials implements NamedGroupCredentials {57final DHPublicKey popPublicKey;58final NamedGroup namedGroup;5960DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) {61this.popPublicKey = popPublicKey;62this.namedGroup = namedGroup;63}6465@Override66public PublicKey getPublicKey() {67return popPublicKey;68}6970@Override71public NamedGroup getNamedGroup() {72return namedGroup;73}7475static DHECredentials valueOf(NamedGroup ng,76byte[] encodedPublic) throws IOException, GeneralSecurityException {7778if (ng.spec != NamedGroupSpec.NAMED_GROUP_FFDHE) {79throw new RuntimeException(80"Credentials decoding: Not FFDHE named group");81}8283if (encodedPublic == null || encodedPublic.length == 0) {84return null;85}8687DHParameterSpec params = (DHParameterSpec)ng.keAlgParamSpec;88KeyFactory kf = KeyFactory.getInstance("DiffieHellman");89DHPublicKeySpec spec = new DHPublicKeySpec(90new BigInteger(1, encodedPublic),91params.getP(), params.getG());92DHPublicKey publicKey =93(DHPublicKey)kf.generatePublic(spec);9495return new DHECredentials(publicKey, ng);96}97}9899static final class DHEPossession implements NamedGroupPossession {100final PrivateKey privateKey;101final DHPublicKey publicKey;102final NamedGroup namedGroup;103104DHEPossession(NamedGroup namedGroup, SecureRandom random) {105try {106KeyPairGenerator kpg =107KeyPairGenerator.getInstance("DiffieHellman");108kpg.initialize(namedGroup.keAlgParamSpec, random);109KeyPair kp = generateDHKeyPair(kpg);110if (kp == null) {111throw new RuntimeException("Could not generate DH keypair");112}113privateKey = kp.getPrivate();114publicKey = (DHPublicKey)kp.getPublic();115} catch (GeneralSecurityException gse) {116throw new RuntimeException(117"Could not generate DH keypair", gse);118}119120this.namedGroup = namedGroup;121}122123DHEPossession(int keyLength, SecureRandom random) {124DHParameterSpec params =125PredefinedDHParameterSpecs.definedParams.get(keyLength);126try {127KeyPairGenerator kpg =128KeyPairGenerator.getInstance("DiffieHellman");129if (params != null) {130kpg.initialize(params, random);131} else {132kpg.initialize(keyLength, random);133}134135KeyPair kp = generateDHKeyPair(kpg);136if (kp == null) {137throw new RuntimeException(138"Could not generate DH keypair of " +139keyLength + " bits");140}141privateKey = kp.getPrivate();142publicKey = (DHPublicKey)kp.getPublic();143} catch (GeneralSecurityException gse) {144throw new RuntimeException(145"Could not generate DH keypair", gse);146}147148this.namedGroup = NamedGroup.valueOf(publicKey.getParams());149}150151DHEPossession(DHECredentials credentials, SecureRandom random) {152try {153KeyPairGenerator kpg =154KeyPairGenerator.getInstance("DiffieHellman");155kpg.initialize(credentials.popPublicKey.getParams(), random);156KeyPair kp = generateDHKeyPair(kpg);157if (kp == null) {158throw new RuntimeException("Could not generate DH keypair");159}160privateKey = kp.getPrivate();161publicKey = (DHPublicKey)kp.getPublic();162} catch (GeneralSecurityException gse) {163throw new RuntimeException(164"Could not generate DH keypair", gse);165}166167this.namedGroup = credentials.namedGroup;168}169170// Generate and validate DHPublicKeySpec171private KeyPair generateDHKeyPair(172KeyPairGenerator kpg) throws GeneralSecurityException {173boolean doExtraValidation =174(!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName()));175boolean isRecovering = false;176for (int i = 0; i <= 2; i++) { // Try to recover from failure.177KeyPair kp = kpg.generateKeyPair();178// validate the Diffie-Hellman public key179if (doExtraValidation) {180DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic());181try {182KeyUtil.validate(spec);183} catch (InvalidKeyException ivke) {184if (isRecovering) {185throw ivke;186}187// otherwise, ignore the exception and try again188isRecovering = true;189continue;190}191}192193return kp;194}195196return null;197}198199private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) {200if (key instanceof DHPublicKey) {201DHPublicKey dhKey = (DHPublicKey)key;202DHParameterSpec params = dhKey.getParams();203return new DHPublicKeySpec(dhKey.getY(),204params.getP(), params.getG());205}206try {207KeyFactory factory = KeyFactory.getInstance("DiffieHellman");208return factory.getKeySpec(key, DHPublicKeySpec.class);209} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {210// unlikely211throw new RuntimeException("Unable to get DHPublicKeySpec", e);212}213}214215@Override216public byte[] encode() {217// Note: the DH public value is encoded as a big-endian integer218// and padded to the left with zeros to the size of p in bytes.219byte[] encoded = Utilities.toByteArray(publicKey.getY());220int pSize = (KeyUtil.getKeySize(publicKey) + 7) >>> 3;221if (pSize > 0 && encoded.length < pSize) {222byte[] buffer = new byte[pSize];223System.arraycopy(encoded, 0,224buffer, pSize - encoded.length, encoded.length);225encoded = buffer;226}227228return encoded;229}230231@Override232public PublicKey getPublicKey() {233return publicKey;234}235236@Override237public NamedGroup getNamedGroup() {238return namedGroup;239}240241@Override242public PrivateKey getPrivateKey() {243return privateKey;244}245}246247private static final class248DHEPossessionGenerator implements SSLPossessionGenerator {249// Flag to use smart ephemeral DH key which size matches the250// corresponding authentication key251private static final boolean useSmartEphemeralDHKeys;252253// Flag to use legacy ephemeral DH key which size is 512 bits for254// exportable cipher suites, and 768 bits for others255private static final boolean useLegacyEphemeralDHKeys;256257// The customized ephemeral DH key size for non-exportable258// cipher suites.259private static final int customizedDHKeySize;260261// Is it for exportable cipher suite?262private final boolean exportable;263264static {265String property = GetPropertyAction.privilegedGetProperty(266"jdk.tls.ephemeralDHKeySize");267if (property == null || property.isEmpty()) {268useLegacyEphemeralDHKeys = false;269useSmartEphemeralDHKeys = false;270customizedDHKeySize = -1;271} else if ("matched".equals(property)) {272useLegacyEphemeralDHKeys = false;273useSmartEphemeralDHKeys = true;274customizedDHKeySize = -1;275} else if ("legacy".equals(property)) {276useLegacyEphemeralDHKeys = true;277useSmartEphemeralDHKeys = false;278customizedDHKeySize = -1;279} else {280useLegacyEphemeralDHKeys = false;281useSmartEphemeralDHKeys = false;282283try {284// DH parameter generation can be extremely slow, best to285// use one of the supported pre-computed DH parameters286// (see DHCrypt class).287customizedDHKeySize = Integer.parseUnsignedInt(property);288if (customizedDHKeySize < 1024 ||289customizedDHKeySize > 8192 ||290(customizedDHKeySize & 0x3f) != 0) {291throw new IllegalArgumentException(292"Unsupported customized DH key size: " +293customizedDHKeySize + ". " +294"The key size must be multiple of 64, " +295"and range from 1024 to 8192 (inclusive)");296}297} catch (NumberFormatException nfe) {298throw new IllegalArgumentException(299"Invalid system property jdk.tls.ephemeralDHKeySize");300}301}302}303304// Prevent instantiation of this class.305private DHEPossessionGenerator(boolean exportable) {306this.exportable = exportable;307}308309// Used for ServerKeyExchange, TLS 1.2 and prior versions.310@Override311public SSLPossession createPossession(HandshakeContext context) {312NamedGroup preferableNamedGroup;313if (!useLegacyEphemeralDHKeys &&314(context.clientRequestedNamedGroups != null) &&315(!context.clientRequestedNamedGroups.isEmpty())) {316preferableNamedGroup =317SupportedGroups.getPreferredGroup(context.negotiatedProtocol,318context.algorithmConstraints,319new NamedGroupSpec [] {320NamedGroupSpec.NAMED_GROUP_FFDHE },321context.clientRequestedNamedGroups);322if (preferableNamedGroup != null) {323return new DHEPossession(preferableNamedGroup,324context.sslContext.getSecureRandom());325}326}327328/*329* 768 bits ephemeral DH private keys were used to be used in330* ServerKeyExchange except that exportable ciphers max out at 512331* bits modulus values. We still adhere to this behavior in legacy332* mode (system property "jdk.tls.ephemeralDHKeySize" is defined333* as "legacy").334*335* Old JDK (JDK 7 and previous) releases don't support DH keys336* bigger than 1024 bits. We have to consider the compatibility337* requirement. 1024 bits DH key is always used for non-exportable338* cipher suites in default mode (system property339* "jdk.tls.ephemeralDHKeySize" is not defined).340*341* However, if applications want more stronger strength, setting342* system property "jdk.tls.ephemeralDHKeySize" to "matched"343* is a workaround to use ephemeral DH key which size matches the344* corresponding authentication key. For example, if the public key345* size of an authentication certificate is 2048 bits, then the346* ephemeral DH key size should be 2048 bits accordingly unless347* the cipher suite is exportable. This key sizing scheme keeps348* the cryptographic strength consistent between authentication349* keys and key-exchange keys.350*351* Applications may also want to customize the ephemeral DH key352* size to a fixed length for non-exportable cipher suites. This353* can be approached by setting system property354* "jdk.tls.ephemeralDHKeySize" to a valid positive integer between355* 1024 and 8192 bits, inclusive.356*357* Note that the minimum acceptable key size is 1024 bits except358* exportable cipher suites or legacy mode.359*360* Note that per RFC 2246, the key size limit of DH is 512 bits for361* exportable cipher suites. Because of the weakness, exportable362* cipher suites are deprecated since TLS v1.1 and they are not363* enabled by default in Oracle provider. The legacy behavior is364* reserved and 512 bits DH key is always used for exportable365* cipher suites.366*/367int keySize = exportable ? 512 : 1024; // default mode368if (!exportable) {369if (useLegacyEphemeralDHKeys) { // legacy mode370keySize = 768;371} else if (useSmartEphemeralDHKeys) { // matched mode372PrivateKey key = null;373ServerHandshakeContext shc =374(ServerHandshakeContext)context;375if (shc.interimAuthn instanceof X509Possession) {376key = ((X509Possession)shc.interimAuthn).popPrivateKey;377}378379if (key != null) {380int ks = KeyUtil.getKeySize(key);381382// DH parameter generation can be extremely slow, make383// sure to use one of the supported pre-computed DH384// parameters.385//386// Old deployed applications may not be ready to387// support DH key sizes bigger than 2048 bits. Please388// DON'T use value other than 1024 and 2048 at present.389// May improve the underlying providers and key size390// limit in the future when the compatibility and391// interoperability impact is limited.392keySize = ks <= 1024 ? 1024 : 2048;393} // Otherwise, anonymous cipher suites, 1024-bit is used.394} else if (customizedDHKeySize > 0) { // customized mode395keySize = customizedDHKeySize;396}397}398399return new DHEPossession(400keySize, context.sslContext.getSecureRandom());401}402}403404private static final405class DHEKAGenerator implements SSLKeyAgreementGenerator {406private static final DHEKAGenerator instance = new DHEKAGenerator();407408// Prevent instantiation of this class.409private DHEKAGenerator() {410// blank411}412413@Override414public SSLKeyDerivation createKeyDerivation(415HandshakeContext context) throws IOException {416DHEPossession dhePossession = null;417DHECredentials dheCredentials = null;418for (SSLPossession poss : context.handshakePossessions) {419if (!(poss instanceof DHEPossession)) {420continue;421}422423DHEPossession dhep = (DHEPossession)poss;424for (SSLCredentials cred : context.handshakeCredentials) {425if (!(cred instanceof DHECredentials)) {426continue;427}428DHECredentials dhec = (DHECredentials)cred;429if (dhep.namedGroup != null && dhec.namedGroup != null) {430if (dhep.namedGroup.equals(dhec.namedGroup)) {431dheCredentials = (DHECredentials)cred;432break;433}434} else {435DHParameterSpec pps = dhep.publicKey.getParams();436DHParameterSpec cps = dhec.popPublicKey.getParams();437if (pps.getP().equals(cps.getP()) &&438pps.getG().equals(cps.getG())) {439dheCredentials = (DHECredentials)cred;440break;441}442}443}444445if (dheCredentials != null) {446dhePossession = (DHEPossession)poss;447break;448}449}450451if (dhePossession == null || dheCredentials == null) {452throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,453"No sufficient DHE key agreement parameters negotiated");454}455456return new KAKeyDerivation("DiffieHellman", context,457dhePossession.privateKey, dheCredentials.popPublicKey);458}459}460}461462463