/* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import sun.security.action.GetPropertyAction; import sun.security.ssl.NamedGroup.NamedGroupSpec; import sun.security.ssl.SupportedGroupsExtension.SupportedGroups; import sun.security.ssl.X509Authentication.X509Possession; import sun.security.util.KeyUtil; final class DHKeyExchange { static final SSLPossessionGenerator poGenerator = new DHEPossessionGenerator(false); static final SSLPossessionGenerator poExportableGenerator = new DHEPossessionGenerator(true); static final SSLKeyAgreementGenerator kaGenerator = new DHEKAGenerator(); static final class DHECredentials implements NamedGroupCredentials { final DHPublicKey popPublicKey; final NamedGroup namedGroup; DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) { this.popPublicKey = popPublicKey; this.namedGroup = namedGroup; } @Override public PublicKey getPublicKey() { return popPublicKey; } @Override public NamedGroup getNamedGroup() { return namedGroup; } static DHECredentials valueOf(NamedGroup ng, byte[] encodedPublic) throws IOException, GeneralSecurityException { if (ng.spec != NamedGroupSpec.NAMED_GROUP_FFDHE) { throw new RuntimeException( "Credentials decoding: Not FFDHE named group"); } if (encodedPublic == null || encodedPublic.length == 0) { return null; } DHParameterSpec params = (DHParameterSpec)ng.keAlgParamSpec; KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman"); DHPublicKeySpec spec = new DHPublicKeySpec( new BigInteger(1, encodedPublic), params.getP(), params.getG()); DHPublicKey publicKey = (DHPublicKey)kf.generatePublic(spec); return new DHECredentials(publicKey, ng); } } static final class DHEPossession implements NamedGroupPossession { final PrivateKey privateKey; final DHPublicKey publicKey; final NamedGroup namedGroup; DHEPossession(NamedGroup namedGroup, SecureRandom random) { try { KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DiffieHellman"); kpg.initialize(namedGroup.keAlgParamSpec, random); KeyPair kp = generateDHKeyPair(kpg); if (kp == null) { throw new RuntimeException("Could not generate DH keypair"); } privateKey = kp.getPrivate(); publicKey = (DHPublicKey)kp.getPublic(); } catch (GeneralSecurityException gse) { throw new RuntimeException( "Could not generate DH keypair", gse); } this.namedGroup = namedGroup; } DHEPossession(int keyLength, SecureRandom random) { DHParameterSpec params = PredefinedDHParameterSpecs.definedParams.get(keyLength); try { KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DiffieHellman"); if (params != null) { kpg.initialize(params, random); } else { kpg.initialize(keyLength, random); } KeyPair kp = generateDHKeyPair(kpg); if (kp == null) { throw new RuntimeException( "Could not generate DH keypair of " + keyLength + " bits"); } privateKey = kp.getPrivate(); publicKey = (DHPublicKey)kp.getPublic(); } catch (GeneralSecurityException gse) { throw new RuntimeException( "Could not generate DH keypair", gse); } this.namedGroup = NamedGroup.valueOf(publicKey.getParams()); } DHEPossession(DHECredentials credentials, SecureRandom random) { try { KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DiffieHellman"); kpg.initialize(credentials.popPublicKey.getParams(), random); KeyPair kp = generateDHKeyPair(kpg); if (kp == null) { throw new RuntimeException("Could not generate DH keypair"); } privateKey = kp.getPrivate(); publicKey = (DHPublicKey)kp.getPublic(); } catch (GeneralSecurityException gse) { throw new RuntimeException( "Could not generate DH keypair", gse); } this.namedGroup = credentials.namedGroup; } // Generate and validate DHPublicKeySpec private KeyPair generateDHKeyPair( KeyPairGenerator kpg) throws GeneralSecurityException { boolean doExtraValidation = (!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName())); boolean isRecovering = false; for (int i = 0; i <= 2; i++) { // Try to recover from failure. KeyPair kp = kpg.generateKeyPair(); // validate the Diffie-Hellman public key if (doExtraValidation) { DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic()); try { KeyUtil.validate(spec); } catch (InvalidKeyException ivke) { if (isRecovering) { throw ivke; } // otherwise, ignore the exception and try again isRecovering = true; continue; } } return kp; } return null; } private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) { if (key instanceof DHPublicKey) { DHPublicKey dhKey = (DHPublicKey)key; DHParameterSpec params = dhKey.getParams(); return new DHPublicKeySpec(dhKey.getY(), params.getP(), params.getG()); } try { KeyFactory factory = JsseJce.getKeyFactory("DiffieHellman"); return factory.getKeySpec(key, DHPublicKeySpec.class); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { // unlikely throw new RuntimeException("Unable to get DHPublicKeySpec", e); } } @Override public byte[] encode() { // Note: the DH public value is encoded as a big-endian integer // and padded to the left with zeros to the size of p in bytes. byte[] encoded = Utilities.toByteArray(publicKey.getY()); int pSize = (KeyUtil.getKeySize(publicKey) + 7) >>> 3; if (pSize > 0 && encoded.length < pSize) { byte[] buffer = new byte[pSize]; System.arraycopy(encoded, 0, buffer, pSize - encoded.length, encoded.length); encoded = buffer; } return encoded; } @Override public PublicKey getPublicKey() { return publicKey; } @Override public NamedGroup getNamedGroup() { return namedGroup; } @Override public PrivateKey getPrivateKey() { return privateKey; } } private static final class DHEPossessionGenerator implements SSLPossessionGenerator { // Flag to use smart ephemeral DH key which size matches the // corresponding authentication key private static final boolean useSmartEphemeralDHKeys; // Flag to use legacy ephemeral DH key which size is 512 bits for // exportable cipher suites, and 768 bits for others private static final boolean useLegacyEphemeralDHKeys; // The customized ephemeral DH key size for non-exportable // cipher suites. private static final int customizedDHKeySize; // Is it for exportable cipher suite? private final boolean exportable; static { String property = GetPropertyAction.privilegedGetProperty( "jdk.tls.ephemeralDHKeySize"); if (property == null || property.isEmpty()) { useLegacyEphemeralDHKeys = false; useSmartEphemeralDHKeys = false; customizedDHKeySize = -1; } else if ("matched".equals(property)) { useLegacyEphemeralDHKeys = false; useSmartEphemeralDHKeys = true; customizedDHKeySize = -1; } else if ("legacy".equals(property)) { useLegacyEphemeralDHKeys = true; useSmartEphemeralDHKeys = false; customizedDHKeySize = -1; } else { useLegacyEphemeralDHKeys = false; useSmartEphemeralDHKeys = false; try { // DH parameter generation can be extremely slow, best to // use one of the supported pre-computed DH parameters // (see DHCrypt class). customizedDHKeySize = Integer.parseUnsignedInt(property); if (customizedDHKeySize < 1024 || customizedDHKeySize > 8192 || (customizedDHKeySize & 0x3f) != 0) { throw new IllegalArgumentException( "Unsupported customized DH key size: " + customizedDHKeySize + ". " + "The key size must be multiple of 64, " + "and range from 1024 to 8192 (inclusive)"); } } catch (NumberFormatException nfe) { throw new IllegalArgumentException( "Invalid system property jdk.tls.ephemeralDHKeySize"); } } } // Prevent instantiation of this class. private DHEPossessionGenerator(boolean exportable) { this.exportable = exportable; } // Used for ServerKeyExchange, TLS 1.2 and prior versions. @Override public SSLPossession createPossession(HandshakeContext context) { NamedGroup preferableNamedGroup; if (!useLegacyEphemeralDHKeys && (context.clientRequestedNamedGroups != null) && (!context.clientRequestedNamedGroups.isEmpty())) { preferableNamedGroup = SupportedGroups.getPreferredGroup(context.negotiatedProtocol, context.algorithmConstraints, new NamedGroupSpec [] { NamedGroupSpec.NAMED_GROUP_FFDHE }, context.clientRequestedNamedGroups); if (preferableNamedGroup != null) { return new DHEPossession(preferableNamedGroup, context.sslContext.getSecureRandom()); } } /* * 768 bits ephemeral DH private keys were used to be used in * ServerKeyExchange except that exportable ciphers max out at 512 * bits modulus values. We still adhere to this behavior in legacy * mode (system property "jdk.tls.ephemeralDHKeySize" is defined * as "legacy"). * * Old JDK (JDK 7 and previous) releases don't support DH keys * bigger than 1024 bits. We have to consider the compatibility * requirement. 1024 bits DH key is always used for non-exportable * cipher suites in default mode (system property * "jdk.tls.ephemeralDHKeySize" is not defined). * * However, if applications want more stronger strength, setting * system property "jdk.tls.ephemeralDHKeySize" to "matched" * is a workaround to use ephemeral DH key which size matches the * corresponding authentication key. For example, if the public key * size of an authentication certificate is 2048 bits, then the * ephemeral DH key size should be 2048 bits accordingly unless * the cipher suite is exportable. This key sizing scheme keeps * the cryptographic strength consistent between authentication * keys and key-exchange keys. * * Applications may also want to customize the ephemeral DH key * size to a fixed length for non-exportable cipher suites. This * can be approached by setting system property * "jdk.tls.ephemeralDHKeySize" to a valid positive integer between * 1024 and 8192 bits, inclusive. * * Note that the minimum acceptable key size is 1024 bits except * exportable cipher suites or legacy mode. * * Note that per RFC 2246, the key size limit of DH is 512 bits for * exportable cipher suites. Because of the weakness, exportable * cipher suites are deprecated since TLS v1.1 and they are not * enabled by default in Oracle provider. The legacy behavior is * reserved and 512 bits DH key is always used for exportable * cipher suites. */ int keySize = exportable ? 512 : 1024; // default mode if (!exportable) { if (useLegacyEphemeralDHKeys) { // legacy mode keySize = 768; } else if (useSmartEphemeralDHKeys) { // matched mode PrivateKey key = null; ServerHandshakeContext shc = (ServerHandshakeContext)context; if (shc.interimAuthn instanceof X509Possession) { key = ((X509Possession)shc.interimAuthn).popPrivateKey; } if (key != null) { int ks = KeyUtil.getKeySize(key); // DH parameter generation can be extremely slow, make // sure to use one of the supported pre-computed DH // parameters. // // Old deployed applications may not be ready to // support DH key sizes bigger than 2048 bits. Please // DON'T use value other than 1024 and 2048 at present. // May improve the underlying providers and key size // limit in the future when the compatibility and // interoperability impact is limited. keySize = ks <= 1024 ? 1024 : 2048; } // Otherwise, anonymous cipher suites, 1024-bit is used. } else if (customizedDHKeySize > 0) { // customized mode keySize = customizedDHKeySize; } } return new DHEPossession( keySize, context.sslContext.getSecureRandom()); } } private static final class DHEKAGenerator implements SSLKeyAgreementGenerator { private static final DHEKAGenerator instance = new DHEKAGenerator(); // Prevent instantiation of this class. private DHEKAGenerator() { // blank } @Override public SSLKeyDerivation createKeyDerivation( HandshakeContext context) throws IOException { DHEPossession dhePossession = null; DHECredentials dheCredentials = null; for (SSLPossession poss : context.handshakePossessions) { if (!(poss instanceof DHEPossession)) { continue; } DHEPossession dhep = (DHEPossession)poss; for (SSLCredentials cred : context.handshakeCredentials) { if (!(cred instanceof DHECredentials)) { continue; } DHECredentials dhec = (DHECredentials)cred; if (dhep.namedGroup != null && dhec.namedGroup != null) { if (dhep.namedGroup.equals(dhec.namedGroup)) { dheCredentials = (DHECredentials)cred; break; } } else { DHParameterSpec pps = dhep.publicKey.getParams(); DHParameterSpec cps = dhec.popPublicKey.getParams(); if (pps.getP().equals(cps.getP()) && pps.getG().equals(cps.getG())) { dheCredentials = (DHECredentials)cred; break; } } } if (dheCredentials != null) { dhePossession = (DHEPossession)poss; break; } } if (dhePossession == null || dheCredentials == null) { throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE, "No sufficient DHE key agreement parameters negotiated"); } return new KAKeyDerivation("DiffieHellman", context, dhePossession.privateKey, dheCredentials.popPublicKey); } } }