| /* | |
| * Copyright (c) 2018, 2022, 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.security.AlgorithmConstraints; | |
| import java.security.CryptoPrimitive; | |
| import java.security.GeneralSecurityException; | |
| import java.security.KeyFactory; | |
| import java.security.KeyPair; | |
| import java.security.KeyPairGenerator; | |
| import java.security.PrivateKey; | |
| import java.security.PublicKey; | |
| import java.security.SecureRandom; | |
| import java.security.interfaces.ECPublicKey; | |
| import java.security.spec.ECParameterSpec; | |
| import java.security.spec.ECPoint; | |
| import java.security.spec.ECPublicKeySpec; | |
| import java.util.EnumSet; | |
| import javax.crypto.KeyAgreement; | |
| import javax.crypto.SecretKey; | |
| import javax.net.ssl.SSLHandshakeException; | |
| import sun.security.ssl.NamedGroup.NamedGroupSpec; | |
| import sun.security.ssl.X509Authentication.X509Credentials; | |
| import sun.security.ssl.X509Authentication.X509Possession; | |
| import sun.security.ssl.XDHKeyExchange.XDHECredentials; | |
| import sun.security.ssl.XDHKeyExchange.XDHEPossession; | |
| import sun.security.util.ECUtil; | |
| final class ECDHKeyExchange { | |
| static final SSLPossessionGenerator poGenerator = | |
| new ECDHEPossessionGenerator(); | |
| static final SSLKeyAgreementGenerator ecdhKAGenerator = | |
| new ECDHKAGenerator(); | |
| // TLSv1.3 | |
| static final SSLKeyAgreementGenerator ecdheKAGenerator = | |
| new ECDHEKAGenerator(); | |
| // TLSv1-1.2, the KA gets more difficult with EC/XEC keys | |
| static final SSLKeyAgreementGenerator ecdheXdhKAGenerator = | |
| new ECDHEXDHKAGenerator(); | |
| static final class ECDHECredentials implements NamedGroupCredentials { | |
| final ECPublicKey popPublicKey; | |
| final NamedGroup namedGroup; | |
| ECDHECredentials(ECPublicKey popPublicKey, NamedGroup namedGroup) { | |
| this.popPublicKey = popPublicKey; | |
| this.namedGroup = namedGroup; | |
| } | |
| @Override | |
| public PublicKey getPublicKey() { | |
| return popPublicKey; | |
| } | |
| @Override | |
| public NamedGroup getNamedGroup() { | |
| return namedGroup; | |
| } | |
| static ECDHECredentials valueOf(NamedGroup namedGroup, | |
| byte[] encodedPoint) throws IOException, GeneralSecurityException { | |
| if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_ECDHE) { | |
| throw new RuntimeException( | |
| "Credentials decoding: Not ECDHE named group"); | |
| } | |
| if (encodedPoint == null || encodedPoint.length == 0) { | |
| return null; | |
| } | |
| ECParameterSpec parameters = | |
| (ECParameterSpec)namedGroup.keAlgParamSpec; | |
| ECPoint point = ECUtil.decodePoint( | |
| encodedPoint, parameters.getCurve()); | |
| KeyFactory factory = KeyFactory.getInstance("EC"); | |
| ECPublicKey publicKey = (ECPublicKey)factory.generatePublic( | |
| new ECPublicKeySpec(point, parameters)); | |
| return new ECDHECredentials(publicKey, namedGroup); | |
| } | |
| } | |
| static final class ECDHEPossession implements NamedGroupPossession { | |
| final PrivateKey privateKey; | |
| final ECPublicKey publicKey; | |
| final NamedGroup namedGroup; | |
| ECDHEPossession(NamedGroup namedGroup, SecureRandom random) { | |
| try { | |
| KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); | |
| kpg.initialize(namedGroup.keAlgParamSpec, random); | |
| KeyPair kp = kpg.generateKeyPair(); | |
| privateKey = kp.getPrivate(); | |
| publicKey = (ECPublicKey)kp.getPublic(); | |
| } catch (GeneralSecurityException e) { | |
| throw new RuntimeException( | |
| "Could not generate ECDH keypair", e); | |
| } | |
| this.namedGroup = namedGroup; | |
| } | |
| ECDHEPossession(ECDHECredentials credentials, SecureRandom random) { | |
| ECParameterSpec params = credentials.popPublicKey.getParams(); | |
| try { | |
| KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); | |
| kpg.initialize(params, random); | |
| KeyPair kp = kpg.generateKeyPair(); | |
| privateKey = kp.getPrivate(); | |
| publicKey = (ECPublicKey)kp.getPublic(); | |
| } catch (GeneralSecurityException e) { | |
| throw new RuntimeException( | |
| "Could not generate ECDH keypair", e); | |
| } | |
| this.namedGroup = credentials.namedGroup; | |
| } | |
| @Override | |
| public byte[] encode() { | |
| return ECUtil.encodePoint( | |
| publicKey.getW(), publicKey.getParams().getCurve()); | |
| } | |
| // called by ClientHandshaker with either the server's static or | |
| // ephemeral public key | |
| SecretKey getAgreedSecret( | |
| PublicKey peerPublicKey) throws SSLHandshakeException { | |
| try { | |
| KeyAgreement ka = KeyAgreement.getInstance("ECDH"); | |
| ka.init(privateKey); | |
| ka.doPhase(peerPublicKey, true); | |
| return ka.generateSecret("TlsPremasterSecret"); | |
| } catch (GeneralSecurityException e) { | |
| throw new SSLHandshakeException("Could not generate secret", e); | |
| } | |
| } | |
| // called by ServerHandshaker | |
| SecretKey getAgreedSecret( | |
| byte[] encodedPoint) throws SSLHandshakeException { | |
| try { | |
| ECParameterSpec params = publicKey.getParams(); | |
| ECPoint point = | |
| ECUtil.decodePoint(encodedPoint, params.getCurve()); | |
| KeyFactory kf = KeyFactory.getInstance("EC"); | |
| ECPublicKeySpec spec = new ECPublicKeySpec(point, params); | |
| PublicKey peerPublicKey = kf.generatePublic(spec); | |
| return getAgreedSecret(peerPublicKey); | |
| } catch (GeneralSecurityException | java.io.IOException e) { | |
| throw new SSLHandshakeException("Could not generate secret", e); | |
| } | |
| } | |
| // Check constraints of the specified EC public key. | |
| void checkConstraints(AlgorithmConstraints constraints, | |
| byte[] encodedPoint) throws SSLHandshakeException { | |
| try { | |
| ECParameterSpec params = publicKey.getParams(); | |
| ECPoint point = | |
| ECUtil.decodePoint(encodedPoint, params.getCurve()); | |
| ECPublicKeySpec spec = new ECPublicKeySpec(point, params); | |
| KeyFactory kf = KeyFactory.getInstance("EC"); | |
| ECPublicKey pubKey = (ECPublicKey)kf.generatePublic(spec); | |
| // check constraints of ECPublicKey | |
| if (!constraints.permits( | |
| EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), pubKey)) { | |
| throw new SSLHandshakeException( | |
| "ECPublicKey does not comply to algorithm constraints"); | |
| } | |
| } catch (GeneralSecurityException | java.io.IOException e) { | |
| throw new SSLHandshakeException( | |
| "Could not generate ECPublicKey", e); | |
| } | |
| } | |
| @Override | |
| public PublicKey getPublicKey() { | |
| return publicKey; | |
| } | |
| @Override | |
| public NamedGroup getNamedGroup() { | |
| return namedGroup; | |
| } | |
| @Override | |
| public PrivateKey getPrivateKey() { | |
| return privateKey; | |
| } | |
| } | |
| private static final | |
| class ECDHEPossessionGenerator implements SSLPossessionGenerator { | |
| // Prevent instantiation of this class. | |
| private ECDHEPossessionGenerator() { | |
| // blank | |
| } | |
| @Override | |
| public SSLPossession createPossession(HandshakeContext context) { | |
| NamedGroup preferableNamedGroup; | |
| // Find most preferred EC or XEC groups | |
| if ((context.clientRequestedNamedGroups != null) && | |
| (!context.clientRequestedNamedGroups.isEmpty())) { | |
| preferableNamedGroup = NamedGroup.getPreferredGroup( | |
| context.sslConfig, | |
| context.negotiatedProtocol, | |
| context.algorithmConstraints, | |
| new NamedGroupSpec[] { | |
| NamedGroupSpec.NAMED_GROUP_ECDHE, | |
| NamedGroupSpec.NAMED_GROUP_XDH }, | |
| context.clientRequestedNamedGroups); | |
| } else { | |
| preferableNamedGroup = NamedGroup.getPreferredGroup( | |
| context.sslConfig, | |
| context.negotiatedProtocol, | |
| context.algorithmConstraints, | |
| new NamedGroupSpec[] { | |
| NamedGroupSpec.NAMED_GROUP_ECDHE, | |
| NamedGroupSpec.NAMED_GROUP_XDH }); | |
| } | |
| if (preferableNamedGroup != null) { | |
| return preferableNamedGroup.createPossession( | |
| context.sslContext.getSecureRandom()); | |
| } | |
| // no match found, cannot use this cipher suite. | |
| // | |
| return null; | |
| } | |
| } | |
| private static final | |
| class ECDHKAGenerator implements SSLKeyAgreementGenerator { | |
| // Prevent instantiation of this class. | |
| private ECDHKAGenerator() { | |
| // blank | |
| } | |
| @Override | |
| public SSLKeyDerivation createKeyDerivation( | |
| HandshakeContext context) throws IOException { | |
| if (context instanceof ServerHandshakeContext) { | |
| return createServerKeyDerivation( | |
| (ServerHandshakeContext)context); | |
| } else { | |
| return createClientKeyDerivation( | |
| (ClientHandshakeContext)context); | |
| } | |
| } | |
| private SSLKeyDerivation createServerKeyDerivation( | |
| ServerHandshakeContext shc) throws IOException { | |
| X509Possession x509Possession = null; | |
| ECDHECredentials ecdheCredentials = null; | |
| for (SSLPossession poss : shc.handshakePossessions) { | |
| if (!(poss instanceof X509Possession)) { | |
| continue; | |
| } | |
| ECParameterSpec params = | |
| ((X509Possession)poss).getECParameterSpec(); | |
| if (params == null) { | |
| continue; | |
| } | |
| NamedGroup ng = NamedGroup.valueOf(params); | |
| if (ng == null) { | |
| // unlikely, have been checked during cipher suite | |
| // negotiation. | |
| throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Unsupported EC server cert for ECDH key exchange"); | |
| } | |
| for (SSLCredentials cred : shc.handshakeCredentials) { | |
| if (!(cred instanceof ECDHECredentials)) { | |
| continue; | |
| } | |
| if (ng.equals(((ECDHECredentials)cred).namedGroup)) { | |
| ecdheCredentials = (ECDHECredentials)cred; | |
| break; | |
| } | |
| } | |
| if (ecdheCredentials != null) { | |
| x509Possession = (X509Possession)poss; | |
| break; | |
| } | |
| } | |
| if (x509Possession == null) { | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "No sufficient ECDHE key agreement parameters negotiated"); | |
| } | |
| return new KAKeyDerivation("ECDH", shc, | |
| x509Possession.popPrivateKey, ecdheCredentials.popPublicKey); | |
| } | |
| private SSLKeyDerivation createClientKeyDerivation( | |
| ClientHandshakeContext chc) throws IOException { | |
| ECDHEPossession ecdhePossession = null; | |
| X509Credentials x509Credentials = null; | |
| for (SSLPossession poss : chc.handshakePossessions) { | |
| if (!(poss instanceof ECDHEPossession)) { | |
| continue; | |
| } | |
| NamedGroup ng = ((ECDHEPossession)poss).namedGroup; | |
| for (SSLCredentials cred : chc.handshakeCredentials) { | |
| if (!(cred instanceof X509Credentials)) { | |
| continue; | |
| } | |
| PublicKey publicKey = ((X509Credentials)cred).popPublicKey; | |
| if (!publicKey.getAlgorithm().equals("EC")) { | |
| continue; | |
| } | |
| ECParameterSpec params = | |
| ((ECPublicKey)publicKey).getParams(); | |
| NamedGroup namedGroup = NamedGroup.valueOf(params); | |
| if (namedGroup == null) { | |
| // unlikely, should have been checked previously | |
| throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Unsupported EC server cert for ECDH key exchange"); | |
| } | |
| if (ng.equals(namedGroup)) { | |
| x509Credentials = (X509Credentials)cred; | |
| break; | |
| } | |
| } | |
| if (x509Credentials != null) { | |
| ecdhePossession = (ECDHEPossession)poss; | |
| break; | |
| } | |
| } | |
| if (ecdhePossession == null) { | |
| throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "No sufficient ECDH key agreement parameters negotiated"); | |
| } | |
| return new KAKeyDerivation("ECDH", chc, | |
| ecdhePossession.privateKey, x509Credentials.popPublicKey); | |
| } | |
| } | |
| private static final | |
| class ECDHEKAGenerator implements SSLKeyAgreementGenerator { | |
| // Prevent instantiation of this class. | |
| private ECDHEKAGenerator() { | |
| // blank | |
| } | |
| @Override | |
| public SSLKeyDerivation createKeyDerivation( | |
| HandshakeContext context) throws IOException { | |
| ECDHEPossession ecdhePossession = null; | |
| ECDHECredentials ecdheCredentials = null; | |
| for (SSLPossession poss : context.handshakePossessions) { | |
| if (!(poss instanceof ECDHEPossession)) { | |
| continue; | |
| } | |
| NamedGroup ng = ((ECDHEPossession)poss).namedGroup; | |
| for (SSLCredentials cred : context.handshakeCredentials) { | |
| if (!(cred instanceof ECDHECredentials)) { | |
| continue; | |
| } | |
| if (ng.equals(((ECDHECredentials)cred).namedGroup)) { | |
| ecdheCredentials = (ECDHECredentials)cred; | |
| break; | |
| } | |
| } | |
| if (ecdheCredentials != null) { | |
| ecdhePossession = (ECDHEPossession)poss; | |
| break; | |
| } | |
| } | |
| if (ecdhePossession == null) { | |
| throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "No sufficient ECDHE key agreement parameters negotiated"); | |
| } | |
| return new KAKeyDerivation("ECDH", context, | |
| ecdhePossession.privateKey, ecdheCredentials.popPublicKey); | |
| } | |
| } | |
| /* | |
| * A Generator for TLSv1-1.2 to create a ECDHE or a XDH KeyDerivation | |
| * object depending on the negotiated group. | |
| */ | |
| private static final | |
| class ECDHEXDHKAGenerator implements SSLKeyAgreementGenerator { | |
| // Prevent instantiation of this class. | |
| private ECDHEXDHKAGenerator() { | |
| // blank | |
| } | |
| @Override | |
| public SSLKeyDerivation createKeyDerivation( | |
| HandshakeContext context) throws IOException { | |
| NamedGroupPossession namedGroupPossession = null; | |
| NamedGroupCredentials namedGroupCredentials = null; | |
| NamedGroup namedGroup = null; | |
| // Find a possession/credential combo using the same named group | |
| search: | |
| for (SSLPossession poss : context.handshakePossessions) { | |
| for (SSLCredentials cred : context.handshakeCredentials) { | |
| if (((poss instanceof ECDHEPossession) && | |
| (cred instanceof ECDHECredentials)) || | |
| (((poss instanceof XDHEPossession) && | |
| (cred instanceof XDHECredentials)))) { | |
| NamedGroupPossession p = (NamedGroupPossession)poss; | |
| NamedGroupCredentials c = (NamedGroupCredentials)cred; | |
| if (p.getNamedGroup() != c.getNamedGroup()) { | |
| continue; | |
| } else { | |
| namedGroup = p.getNamedGroup(); | |
| } | |
| namedGroupPossession = p; | |
| namedGroupCredentials = c; | |
| break search; | |
| } | |
| } | |
| } | |
| if (namedGroupPossession == null) { | |
| throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "No sufficient ECDHE/XDH key agreement " + | |
| "parameters negotiated"); | |
| } | |
| String alg; | |
| switch (namedGroup.spec) { | |
| case NAMED_GROUP_ECDHE: | |
| alg = "ECDH"; | |
| break; | |
| case NAMED_GROUP_XDH: | |
| alg = "XDH"; | |
| break; | |
| default: | |
| throw new RuntimeException("Unexpected named group type"); | |
| } | |
| return new KAKeyDerivation(alg, context, | |
| namedGroupPossession.getPrivateKey(), | |
| namedGroupCredentials.getPublicKey()); | |
| } | |
| } | |
| } |