src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java
changeset 55353 946f7f2d321c
parent 54417 f87041131515
--- a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java	Wed Jun 12 23:21:24 2019 +0200
+++ b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java	Wed Jun 12 18:58:00 2019 -0700
@@ -27,31 +27,28 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.security.AlgorithmConstraints;
-import java.security.CryptoPrimitive;
 import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
 import java.security.PublicKey;
 import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.XECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPublicKeySpec;
+import java.security.spec.NamedParameterSpec;
 import java.text.MessageFormat;
-import java.util.EnumSet;
 import java.util.Locale;
 import javax.crypto.SecretKey;
-import javax.net.ssl.SSLHandshakeException;
-import sun.security.ssl.ECDHKeyExchange.ECDHECredentials;
-import sun.security.ssl.ECDHKeyExchange.ECDHEPossession;
 import sun.security.ssl.SSLHandshake.HandshakeMessage;
-import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
 import sun.security.ssl.X509Authentication.X509Credentials;
 import sun.security.ssl.X509Authentication.X509Possession;
-import sun.security.util.ECUtil;
 import sun.security.util.HexDumpEncoder;
 
 /**
  * Pack of the "ClientKeyExchange" handshake message.
+ *
+ * This file is used by both the ECDH/ECDHE/XDH code since much of the
+ * code is the same between the EC named groups (i.e.
+ * x25519/x448/secp*r1), even though the APIs are very different (i.e.
+ * ECPublicKey/XECPublicKey, KeyExchange.getInstance("EC"/"XDH"), etc.).
  */
 final class ECDHClientKeyExchange {
     static final SSLConsumer ecdhHandshakeConsumer =
@@ -65,19 +62,17 @@
             new ECDHEClientKeyExchangeProducer();
 
     /**
-     * The ECDH/ECDHE ClientKeyExchange handshake message.
+     * The ECDH/ECDHE/XDH ClientKeyExchange handshake message.
      */
     private static final
             class ECDHClientKeyExchangeMessage extends HandshakeMessage {
         private final byte[] encodedPoint;
 
         ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext,
-                ECPublicKey publicKey) {
+                byte[] encodedPublicKey) {
             super(handshakeContext);
 
-            ECPoint point = publicKey.getW();
-            ECParameterSpec params = publicKey.getParams();
-            encodedPoint = ECUtil.encodePoint(point, params.getCurve());
+            this.encodedPoint = encodedPublicKey;
         }
 
         ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext,
@@ -90,34 +85,6 @@
             }
         }
 
-        // Check constraints of the specified EC public key.
-        static void checkConstraints(AlgorithmConstraints constraints,
-                ECPublicKey publicKey,
-                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 peerPublicKey =
-                        (ECPublicKey)kf.generatePublic(spec);
-
-                // check constraints of ECPublicKey
-                if (!constraints.permits(
-                        EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
-                        peerPublicKey)) {
-                    throw new SSLHandshakeException(
-                        "ECPublicKey does not comply to algorithm constraints");
-                }
-            } catch (GeneralSecurityException | java.io.IOException e) {
-                throw (SSLHandshakeException) new SSLHandshakeException(
-                        "Could not generate ECPublicKey").initCause(e);
-            }
-        }
-
         @Override
         public SSLHandshake handshakeType() {
             return SSLHandshake.CLIENT_KEY_EXCHANGE;
@@ -194,24 +161,41 @@
             }
 
             PublicKey publicKey = x509Credentials.popPublicKey;
-            if (!publicKey.getAlgorithm().equals("EC")) {
+
+            NamedGroup namedGroup = null;
+            String algorithm = publicKey.getAlgorithm();
+
+            // Determine which NamedGroup we'll be using, then use
+            // the creator functions.
+            if (algorithm.equals("EC")) {
+                ECParameterSpec params = ((ECPublicKey)publicKey).getParams();
+                namedGroup = NamedGroup.valueOf(params);
+            } else if (algorithm.equals("XDH")) {
+                AlgorithmParameterSpec params =
+                        ((XECPublicKey)publicKey).getParams();
+                if (params instanceof NamedParameterSpec) {
+                    String name = ((NamedParameterSpec)params).getName();
+                    namedGroup = NamedGroup.nameOf(name);
+                }
+            } else {
                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Not EC server certificate for ECDH client key exchange");
+                    "Not EC/XDH server certificate for " +
+                            "ECDH client key exchange");
             }
 
-            ECParameterSpec params = ((ECPublicKey)publicKey).getParams();
-            NamedGroup namedGroup = NamedGroup.valueOf(params);
             if (namedGroup == null) {
                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Unsupported EC server cert for ECDH client key exchange");
+                    "Unsupported EC/XDH server cert for " +
+                        "ECDH client key exchange");
             }
 
-            ECDHEPossession ecdhePossession = new ECDHEPossession(
-                    namedGroup, chc.sslContext.getSecureRandom());
-            chc.handshakePossessions.add(ecdhePossession);
+            SSLPossession sslPossession = namedGroup.createPossession(
+                    chc.sslContext.getSecureRandom());
+
+            chc.handshakePossessions.add(sslPossession);
             ECDHClientKeyExchangeMessage cke =
                     new ECDHClientKeyExchangeMessage(
-                            chc, ecdhePossession.publicKey);
+                            chc, sslPossession.encode());
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                 SSLLogger.fine(
                     "Produced ECDH ClientKeyExchange handshake message", cke);
@@ -283,18 +267,35 @@
                     "No expected EC server cert for ECDH client key exchange");
             }
 
-            ECParameterSpec params = x509Possession.getECParameterSpec();
-            if (params == null) {
+            // Determine which NamedGroup we'll be using, then use
+            // the creator functions.
+            NamedGroup namedGroup = null;
+
+            // Iteratively determine the X509Possession type's ParameterSpec.
+            ECParameterSpec ecParams = x509Possession.getECParameterSpec();
+            NamedParameterSpec namedParams = null;
+            if (ecParams != null) {
+                namedGroup = NamedGroup.valueOf(ecParams);
+            }
+
+            // Wasn't EC, try XEC.
+            if (ecParams == null) {
+                namedParams = x509Possession.getXECParameterSpec();
+                namedGroup = NamedGroup.nameOf(namedParams.getName());
+            }
+
+            // Can't figure this out, bail.
+            if ((ecParams == null) && (namedParams == null)) {
                 // unlikely, have been checked during cipher suite negotiation.
                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Not EC server cert for ECDH client key exchange");
+                    "Not EC/XDH server cert for ECDH client key exchange");
             }
 
-            NamedGroup namedGroup = NamedGroup.valueOf(params);
+            // unlikely, have been checked during cipher suite negotiation.
             if (namedGroup == null) {
-                // unlikely, have been checked during cipher suite negotiation.
                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Unsupported EC server cert for ECDH client key exchange");
+                    "Unknown named group in server cert for " +
+                        "ECDH client key exchange");
             }
 
             SSLKeyExchange ke = SSLKeyExchange.valueOf(
@@ -306,7 +307,7 @@
                         "Not supported key exchange type");
             }
 
-            // parse the handshake message
+            // parse either handshake message containing either EC/XEC.
             ECDHClientKeyExchangeMessage cke =
                     new ECDHClientKeyExchangeMessage(shc, message);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
@@ -316,27 +317,17 @@
 
             // create the credentials
             try {
-                ECPoint point =
-                    ECUtil.decodePoint(cke.encodedPoint, params.getCurve());
-                ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
-
-                KeyFactory kf = KeyFactory.getInstance("EC");
-                ECPublicKey peerPublicKey =
-                        (ECPublicKey)kf.generatePublic(spec);
+                NamedGroup ng = namedGroup;  // "effectively final" the lambda
+                // AlgorithmConstraints are checked internally.
+                SSLCredentials sslCredentials = namedGroup.decodeCredentials(
+                        cke.encodedPoint, shc.algorithmConstraints,
+                        s -> shc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
+                        "ClientKeyExchange " + ng + ": " + s));
 
-                // check constraints of peer ECPublicKey
-                if (!shc.algorithmConstraints.permits(
-                        EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
-                        peerPublicKey)) {
-                    throw new SSLHandshakeException(
-                        "ECPublicKey does not comply to algorithm constraints");
-                }
-
-                shc.handshakeCredentials.add(new ECDHECredentials(
-                        peerPublicKey, namedGroup));
-            } catch (GeneralSecurityException | java.io.IOException e) {
-                throw (SSLHandshakeException)(new SSLHandshakeException(
-                        "Could not generate ECPublicKey").initCause(e));
+                shc.handshakeCredentials.add(sslCredentials);
+            } catch (GeneralSecurityException e) {
+                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                        "Cannot decode ECDH PublicKey: " + namedGroup);
             }
 
             // update the states
@@ -374,25 +365,37 @@
             // The producing happens in client side only.
             ClientHandshakeContext chc = (ClientHandshakeContext)context;
 
-            ECDHECredentials ecdheCredentials = null;
+            SSLCredentials sslCredentials = null;
+            NamedGroup ng = null;
+            PublicKey publicKey = null;
+
+            // Find a good EC/XEC credential to use, determine the
+            // NamedGroup to use for creating Possessions/Credentials/Keys.
             for (SSLCredentials cd : chc.handshakeCredentials) {
-                if (cd instanceof ECDHECredentials) {
-                    ecdheCredentials = (ECDHECredentials)cd;
+                if (cd instanceof NamedGroupCredentials) {
+                    NamedGroupCredentials creds = (NamedGroupCredentials)cd;
+                    ng = creds.getNamedGroup();
+                    publicKey = creds.getPublicKey();
+                    sslCredentials = cd;
                     break;
                 }
             }
 
-            if (ecdheCredentials == null) {
+            if (sslCredentials == null) {
                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
                     "No ECDHE credentials negotiated for client key exchange");
             }
 
-            ECDHEPossession ecdhePossession = new ECDHEPossession(
-                    ecdheCredentials, chc.sslContext.getSecureRandom());
-            chc.handshakePossessions.add(ecdhePossession);
+            SSLPossession sslPossession = ng.createPossession(
+                    chc.sslContext.getSecureRandom());
+
+            chc.handshakePossessions.add(sslPossession);
+
+            // Write the EC/XEC message.
             ECDHClientKeyExchangeMessage cke =
                     new ECDHClientKeyExchangeMessage(
-                            chc, ecdhePossession.publicKey);
+                            chc, sslPossession.encode());
+
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                 SSLLogger.fine(
                     "Produced ECDHE ClientKeyExchange handshake message", cke);
@@ -450,23 +453,29 @@
             // The consuming happens in server side only.
             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 
-            ECDHEPossession ecdhePossession = null;
+            SSLPossession sslPossession = null;
+            NamedGroup namedGroup = null;
+
+           // Find a good EC/XEC credential to use, determine the
+           // NamedGroup to use for creating Possessions/Credentials/Keys.
             for (SSLPossession possession : shc.handshakePossessions) {
-                if (possession instanceof ECDHEPossession) {
-                    ecdhePossession = (ECDHEPossession)possession;
+                if (possession instanceof NamedGroupPossession) {
+                    NamedGroupPossession poss =
+                            (NamedGroupPossession)possession;
+                    namedGroup = poss.getNamedGroup();
+                    sslPossession = poss;
                     break;
                 }
             }
-            if (ecdhePossession == null) {
+
+            if (sslPossession == null) {
                 // unlikely
                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
                     "No expected ECDHE possessions for client key exchange");
             }
 
-            ECParameterSpec params = ecdhePossession.publicKey.getParams();
-            NamedGroup namedGroup = NamedGroup.valueOf(params);
             if (namedGroup == null) {
-                // unlikely, have been checked during cipher suite negotiation.
+                // unlikely, have been checked during cipher suite negotiation
                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
                     "Unsupported EC server cert for ECDHE client key exchange");
             }
@@ -480,7 +489,7 @@
                         "Not supported key exchange type");
             }
 
-            // parse the handshake message
+            // parse the EC/XEC handshake message
             ECDHClientKeyExchangeMessage cke =
                     new ECDHClientKeyExchangeMessage(shc, message);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
@@ -490,27 +499,17 @@
 
             // create the credentials
             try {
-                ECPoint point =
-                    ECUtil.decodePoint(cke.encodedPoint, params.getCurve());
-                ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
-
-                KeyFactory kf = KeyFactory.getInstance("EC");
-                ECPublicKey peerPublicKey =
-                        (ECPublicKey)kf.generatePublic(spec);
+                NamedGroup ng = namedGroup; // "effectively final" the lambda
+                // AlgorithmConstraints are checked internally.
+                SSLCredentials sslCredentials = namedGroup.decodeCredentials(
+                        cke.encodedPoint, shc.algorithmConstraints,
+                        s -> shc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
+                        "ClientKeyExchange " + ng + ": " + s));
 
-                // check constraints of peer ECPublicKey
-                if (!shc.algorithmConstraints.permits(
-                        EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
-                        peerPublicKey)) {
-                    throw new SSLHandshakeException(
-                        "ECPublicKey does not comply to algorithm constraints");
-                }
-
-                shc.handshakeCredentials.add(new ECDHECredentials(
-                        peerPublicKey, namedGroup));
-            } catch (GeneralSecurityException | java.io.IOException e) {
-                throw (SSLHandshakeException)(new SSLHandshakeException(
-                        "Could not generate ECPublicKey").initCause(e));
+                shc.handshakeCredentials.add(sslCredentials);
+            } catch (GeneralSecurityException e) {
+                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                        "Cannot decode named group: " + namedGroup);
             }
 
             // update the states