| /* | |
| * Copyright (c) 2018, 2024, 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.ssl.NamedGroup.NamedGroupSpec; | |
| 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 = KeyFactory.getInstance("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 = | |
| KeyPairGenerator.getInstance("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 = | |
| KeyPairGenerator.getInstance("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 = | |
| KeyPairGenerator.getInstance("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 dhKey) { | |
| DHParameterSpec params = dhKey.getParams(); | |
| return new DHPublicKeySpec(dhKey.getY(), | |
| params.getP(), params.getG()); | |
| } | |
| try { | |
| KeyFactory factory = KeyFactory.getInstance("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 = System.getProperty("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 = NamedGroup.getPreferredGroup( | |
| context.sslConfig, | |
| context.negotiatedProtocol, | |
| context.algorithmConstraints, | |
| new NamedGroupSpec [] { | |
| NamedGroupSpec.NAMED_GROUP_FFDHE }, | |
| context.clientRequestedNamedGroups); | |
| if (preferableNamedGroup != null) { | |
| return new DHEPossession(preferableNamedGroup, | |
| context.sslContext.getSecureRandom()); | |
| } | |
| } | |
| /* | |
| * 768 bit ephemeral DH private keys used to be used in | |
| * ServerKeyExchange except that exportable ciphers max out at 512 | |
| * bit modulus values. We still adhere to this behavior in legacy | |
| * mode (system property "jdk.tls.ephemeralDHKeySize" is defined | |
| * as "legacy"). | |
| * | |
| * Only very old JDK releases don't support DH keys bigger than | |
| * 1024 bits (JDK 1.5 and 6u/7u releases prior to adding support | |
| * for DH keys > 1024 bits - see JDK-8062834). A 2048 bit | |
| * DH key is always used for non-exportable cipher suites in | |
| * default mode (when the system property | |
| * "jdk.tls.ephemeralDHKeySize" is not defined). | |
| * | |
| * Applications may also want to customize the ephemeral DH key | |
| * size to a fixed length for non-exportable cipher suites. This | |
| * can be done by setting the system property | |
| * "jdk.tls.ephemeralDHKeySize" to a valid positive integer between | |
| * 1024 and 8192 bits, inclusive. | |
| * | |
| * Note that the minimum acceptable key size is 2048 bits except | |
| * for 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 | |
| * preserved and a 512 bit DH key is always used for exportable | |
| * cipher suites. | |
| */ | |
| int keySize = exportable ? 512 : 2048; // 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, 2048-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 dhep)) { | |
| continue; | |
| } | |
| for (SSLCredentials cred : context.handshakeCredentials) { | |
| if (!(cred instanceof DHECredentials dhec)) { | |
| continue; | |
| } | |
| 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) { | |
| throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "No sufficient DHE key agreement parameters negotiated"); | |
| } | |
| return new KAKeyDerivation("DiffieHellman", context, | |
| dhePossession.privateKey, dheCredentials.popPublicKey); | |
| } | |
| } | |
| } |