1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.ssl;
  27 
  28 import java.io.IOException;
  29 import java.security.AlgorithmConstraints;
  30 import java.security.CryptoPrimitive;
  31 import java.security.GeneralSecurityException;
  32 import java.security.KeyFactory;
  33 import java.security.KeyPair;
  34 import java.security.KeyPairGenerator;
  35 import java.security.PrivateKey;
  36 import java.security.PublicKey;
  37 import java.security.SecureRandom;
  38 import java.security.interfaces.ECPublicKey;
  39 import java.security.spec.ECParameterSpec;
  40 import java.security.spec.ECPoint;
  41 import java.security.spec.ECPublicKeySpec;
  42 import java.util.EnumSet;
  43 import javax.crypto.KeyAgreement;
  44 import javax.crypto.SecretKey;
  45 import javax.net.ssl.SSLHandshakeException;
  46 import sun.security.ssl.NamedGroup.NamedGroupSpec;
  47 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
  48 import sun.security.ssl.X509Authentication.X509Credentials;
  49 import sun.security.ssl.X509Authentication.X509Possession;
  50 import sun.security.ssl.XDHKeyExchange.XDHECredentials;
  51 import sun.security.ssl.XDHKeyExchange.XDHEPossession;
  52 import sun.security.util.ECUtil;
  53 
  54 final class ECDHKeyExchange {
  55     static final SSLPossessionGenerator poGenerator =
  56             new ECDHEPossessionGenerator();
  57     static final SSLKeyAgreementGenerator ecdhKAGenerator =
  58             new ECDHKAGenerator();
  59 
  60     // TLSv1.3
  61     static final SSLKeyAgreementGenerator ecdheKAGenerator =
  62             new ECDHEKAGenerator();
  63 
  64     // TLSv1-1.2, the KA gets more difficult with EC/XEC keys
  65     static final SSLKeyAgreementGenerator ecdheXdhKAGenerator =
  66             new ECDHEXDHKAGenerator();
  67 
  68     static final class ECDHECredentials implements NamedGroupCredentials {
  69         final ECPublicKey popPublicKey;
  70         final NamedGroup namedGroup;
  71 
  72         ECDHECredentials(ECPublicKey popPublicKey, NamedGroup namedGroup) {
  73             this.popPublicKey = popPublicKey;
  74             this.namedGroup = namedGroup;
  75         }
  76 
  77         @Override
  78         public PublicKey getPublicKey() {
  79             return popPublicKey;
  80         }
  81 
  82         @Override
  83         public NamedGroup getNamedGroup() {
  84             return namedGroup;
  85         }
  86 
  87         static ECDHECredentials valueOf(NamedGroup namedGroup,
  88             byte[] encodedPoint) throws IOException, GeneralSecurityException {
  89 
  90             if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_ECDHE) {
  91                 throw new RuntimeException(
  92                     "Credentials decoding:  Not ECDHE named group");
  93             }
  94 
  95             if (encodedPoint == null || encodedPoint.length == 0) {
  96                 return null;
  97             }
  98 
  99             ECParameterSpec parameters =
 100                     (ECParameterSpec)namedGroup.keAlgParamSpec;
 101 
 102             ECPoint point = JsseJce.decodePoint(
 103                     encodedPoint, parameters.getCurve());
 104             KeyFactory factory = JsseJce.getKeyFactory("EC");
 105             ECPublicKey publicKey = (ECPublicKey)factory.generatePublic(
 106                     new ECPublicKeySpec(point, parameters));
 107             return new ECDHECredentials(publicKey, namedGroup);
 108         }
 109     }
 110 
 111     static final class ECDHEPossession implements NamedGroupPossession {
 112         final PrivateKey privateKey;
 113         final ECPublicKey publicKey;
 114         final NamedGroup namedGroup;
 115 
 116         ECDHEPossession(NamedGroup namedGroup, SecureRandom random) {
 117             try {
 118                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC");
 119                 kpg.initialize(namedGroup.keAlgParamSpec, random);
 120                 KeyPair kp = kpg.generateKeyPair();
 121                 privateKey = kp.getPrivate();
 122                 publicKey = (ECPublicKey)kp.getPublic();
 123             } catch (GeneralSecurityException e) {
 124                 throw new RuntimeException(
 125                     "Could not generate ECDH keypair", e);
 126             }
 127 
 128             this.namedGroup = namedGroup;
 129         }
 130 
 131         ECDHEPossession(ECDHECredentials credentials, SecureRandom random) {
 132             ECParameterSpec params = credentials.popPublicKey.getParams();
 133             try {
 134                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC");
 135                 kpg.initialize(params, random);
 136                 KeyPair kp = kpg.generateKeyPair();
 137                 privateKey = kp.getPrivate();
 138                 publicKey = (ECPublicKey)kp.getPublic();
 139             } catch (GeneralSecurityException e) {
 140                 throw new RuntimeException(
 141                     "Could not generate ECDH keypair", e);
 142             }
 143 
 144             this.namedGroup = credentials.namedGroup;
 145         }
 146 
 147         @Override
 148         public byte[] encode() {
 149             return ECUtil.encodePoint(
 150                     publicKey.getW(), publicKey.getParams().getCurve());
 151         }
 152 
 153         // called by ClientHandshaker with either the server's static or
 154         // ephemeral public key
 155         SecretKey getAgreedSecret(
 156                 PublicKey peerPublicKey) throws SSLHandshakeException {
 157 
 158             try {
 159                 KeyAgreement ka = JsseJce.getKeyAgreement("ECDH");
 160                 ka.init(privateKey);
 161                 ka.doPhase(peerPublicKey, true);
 162                 return ka.generateSecret("TlsPremasterSecret");
 163             } catch (GeneralSecurityException e) {
 164                 throw (SSLHandshakeException) new SSLHandshakeException(
 165                     "Could not generate secret").initCause(e);
 166             }
 167         }
 168 
 169         // called by ServerHandshaker
 170         SecretKey getAgreedSecret(
 171                 byte[] encodedPoint) throws SSLHandshakeException {
 172             try {
 173                 ECParameterSpec params = publicKey.getParams();
 174                 ECPoint point =
 175                         JsseJce.decodePoint(encodedPoint, params.getCurve());
 176                 KeyFactory kf = JsseJce.getKeyFactory("EC");
 177                 ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
 178                 PublicKey peerPublicKey = kf.generatePublic(spec);
 179                 return getAgreedSecret(peerPublicKey);
 180             } catch (GeneralSecurityException | java.io.IOException e) {
 181                 throw (SSLHandshakeException) new SSLHandshakeException(
 182                     "Could not generate secret").initCause(e);
 183             }
 184         }
 185 
 186         // Check constraints of the specified EC public key.
 187         void checkConstraints(AlgorithmConstraints constraints,
 188                 byte[] encodedPoint) throws SSLHandshakeException {
 189             try {
 190 
 191                 ECParameterSpec params = publicKey.getParams();
 192                 ECPoint point =
 193                         JsseJce.decodePoint(encodedPoint, params.getCurve());
 194                 ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
 195 
 196                 KeyFactory kf = JsseJce.getKeyFactory("EC");
 197                 ECPublicKey pubKey = (ECPublicKey)kf.generatePublic(spec);
 198 
 199                 // check constraints of ECPublicKey
 200                 if (!constraints.permits(
 201                         EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), pubKey)) {
 202                     throw new SSLHandshakeException(
 203                         "ECPublicKey does not comply to algorithm constraints");
 204                 }
 205             } catch (GeneralSecurityException | java.io.IOException e) {
 206                 throw (SSLHandshakeException) new SSLHandshakeException(
 207                         "Could not generate ECPublicKey").initCause(e);
 208             }
 209         }
 210 
 211         @Override
 212         public PublicKey getPublicKey() {
 213             return publicKey;
 214         }
 215 
 216         @Override
 217         public NamedGroup getNamedGroup() {
 218             return namedGroup;
 219         }
 220 
 221         @Override
 222         public PrivateKey getPrivateKey() {
 223             return privateKey;
 224         }
 225     }
 226 
 227     private static final
 228             class ECDHEPossessionGenerator implements SSLPossessionGenerator {
 229         // Prevent instantiation of this class.
 230         private ECDHEPossessionGenerator() {
 231             // blank
 232         }
 233 
 234         @Override
 235         public SSLPossession createPossession(HandshakeContext context) {
 236 
 237             NamedGroup preferableNamedGroup;
 238 
 239             // Find most preferred EC or XEC groups
 240             if ((context.clientRequestedNamedGroups != null) &&
 241                     (!context.clientRequestedNamedGroups.isEmpty())) {
 242                 preferableNamedGroup = SupportedGroups.getPreferredGroup(
 243                         context.negotiatedProtocol,
 244                         context.algorithmConstraints,
 245                         new NamedGroupSpec[] {
 246                             NamedGroupSpec.NAMED_GROUP_ECDHE,
 247                             NamedGroupSpec.NAMED_GROUP_XDH },
 248                         context.clientRequestedNamedGroups);
 249             } else {
 250                 preferableNamedGroup = SupportedGroups.getPreferredGroup(
 251                         context.negotiatedProtocol,
 252                         context.algorithmConstraints,
 253                         new NamedGroupSpec[] {
 254                             NamedGroupSpec.NAMED_GROUP_ECDHE,
 255                             NamedGroupSpec.NAMED_GROUP_XDH });
 256             }
 257 
 258             if (preferableNamedGroup != null) {
 259                 return preferableNamedGroup.createPossession(
 260                     context.sslContext.getSecureRandom());
 261             }
 262 
 263             // no match found, cannot use this cipher suite.
 264             //
 265             return null;
 266         }
 267     }
 268 
 269     private static final
 270             class ECDHKAGenerator implements SSLKeyAgreementGenerator {
 271         // Prevent instantiation of this class.
 272         private ECDHKAGenerator() {
 273             // blank
 274         }
 275 
 276         @Override
 277         public SSLKeyDerivation createKeyDerivation(
 278                 HandshakeContext context) throws IOException {
 279             if (context instanceof ServerHandshakeContext) {
 280                 return createServerKeyDerivation(
 281                         (ServerHandshakeContext)context);
 282             } else {
 283                 return createClientKeyDerivation(
 284                         (ClientHandshakeContext)context);
 285             }
 286         }
 287 
 288         private SSLKeyDerivation createServerKeyDerivation(
 289                 ServerHandshakeContext shc) throws IOException {
 290             X509Possession x509Possession = null;
 291             ECDHECredentials ecdheCredentials = null;
 292             for (SSLPossession poss : shc.handshakePossessions) {
 293                 if (!(poss instanceof X509Possession)) {
 294                     continue;
 295                 }
 296 
 297                 ECParameterSpec params =
 298                         ((X509Possession)poss).getECParameterSpec();
 299                 if (params == null) {
 300                     continue;
 301                 }
 302 
 303                 NamedGroup ng = NamedGroup.valueOf(params);
 304                 if (ng == null) {
 305                     // unlikely, have been checked during cipher suite
 306                     // negotiation.
 307                     throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 308                         "Unsupported EC server cert for ECDH key exchange");
 309                 }
 310 
 311                 for (SSLCredentials cred : shc.handshakeCredentials) {
 312                     if (!(cred instanceof ECDHECredentials)) {
 313                         continue;
 314                     }
 315                     if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
 316                         ecdheCredentials = (ECDHECredentials)cred;
 317                         break;
 318                     }
 319                 }
 320 
 321                 if (ecdheCredentials != null) {
 322                     x509Possession = (X509Possession)poss;
 323                     break;
 324                 }
 325             }
 326 
 327             if (x509Possession == null || ecdheCredentials == null) {
 328                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 329                     "No sufficient ECDHE key agreement parameters negotiated");
 330             }
 331 
 332             return new KAKeyDerivation("ECDH", shc,
 333                 x509Possession.popPrivateKey, ecdheCredentials.popPublicKey);
 334         }
 335 
 336         private SSLKeyDerivation createClientKeyDerivation(
 337                 ClientHandshakeContext chc) throws IOException {
 338             ECDHEPossession ecdhePossession = null;
 339             X509Credentials x509Credentials = null;
 340             for (SSLPossession poss : chc.handshakePossessions) {
 341                 if (!(poss instanceof ECDHEPossession)) {
 342                     continue;
 343                 }
 344 
 345                 NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
 346                 for (SSLCredentials cred : chc.handshakeCredentials) {
 347                     if (!(cred instanceof X509Credentials)) {
 348                         continue;
 349                     }
 350 
 351                     PublicKey publicKey = ((X509Credentials)cred).popPublicKey;
 352                     if (!publicKey.getAlgorithm().equals("EC")) {
 353                         continue;
 354                     }
 355                     ECParameterSpec params =
 356                             ((ECPublicKey)publicKey).getParams();
 357                     NamedGroup namedGroup = NamedGroup.valueOf(params);
 358                     if (namedGroup == null) {
 359                         // unlikely, should have been checked previously
 360                         throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 361                             "Unsupported EC server cert for ECDH key exchange");
 362                     }
 363 
 364                     if (ng.equals(namedGroup)) {
 365                         x509Credentials = (X509Credentials)cred;
 366                         break;
 367                     }
 368                 }
 369 
 370                 if (x509Credentials != null) {
 371                     ecdhePossession = (ECDHEPossession)poss;
 372                     break;
 373                 }
 374             }
 375 
 376             if (ecdhePossession == null || x509Credentials == null) {
 377                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 378                     "No sufficient ECDH key agreement parameters negotiated");
 379             }
 380 
 381             return new KAKeyDerivation("ECDH", chc,
 382                 ecdhePossession.privateKey, x509Credentials.popPublicKey);
 383         }
 384     }
 385 
 386     private static final
 387             class ECDHEKAGenerator implements SSLKeyAgreementGenerator {
 388         // Prevent instantiation of this class.
 389         private ECDHEKAGenerator() {
 390             // blank
 391         }
 392 
 393         @Override
 394         public SSLKeyDerivation createKeyDerivation(
 395                 HandshakeContext context) throws IOException {
 396             ECDHEPossession ecdhePossession = null;
 397             ECDHECredentials ecdheCredentials = null;
 398             for (SSLPossession poss : context.handshakePossessions) {
 399                 if (!(poss instanceof ECDHEPossession)) {
 400                     continue;
 401                 }
 402 
 403                 NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
 404                 for (SSLCredentials cred : context.handshakeCredentials) {
 405                     if (!(cred instanceof ECDHECredentials)) {
 406                         continue;
 407                     }
 408                     if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
 409                         ecdheCredentials = (ECDHECredentials)cred;
 410                         break;
 411                     }
 412                 }
 413 
 414                 if (ecdheCredentials != null) {
 415                     ecdhePossession = (ECDHEPossession)poss;
 416                     break;
 417                 }
 418             }
 419 
 420             if (ecdhePossession == null || ecdheCredentials == null) {
 421                 throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 422                     "No sufficient ECDHE key agreement parameters negotiated");
 423             }
 424 
 425             return new KAKeyDerivation("ECDH", context,
 426                 ecdhePossession.privateKey, ecdheCredentials.popPublicKey);
 427         }
 428     }
 429 
 430     /*
 431      * A Generator for TLSv1-1.2 to create a ECDHE or a XDH KeyDerivation
 432      * object depending on the negotiated group.
 433      */
 434     private static final
 435             class ECDHEXDHKAGenerator implements SSLKeyAgreementGenerator {
 436         // Prevent instantiation of this class.
 437         private ECDHEXDHKAGenerator() {
 438             // blank
 439         }
 440 
 441         @Override
 442         public SSLKeyDerivation createKeyDerivation(
 443                 HandshakeContext context) throws IOException {
 444 
 445             NamedGroupPossession namedGroupPossession = null;
 446             NamedGroupCredentials namedGroupCredentials = null;
 447             NamedGroup namedGroup = null;
 448 
 449             // Find a possession/credential combo using the same named group
 450             search:
 451             for (SSLPossession poss : context.handshakePossessions) {
 452                 for (SSLCredentials cred : context.handshakeCredentials) {
 453                     if (((poss instanceof ECDHEPossession) &&
 454                             (cred instanceof ECDHECredentials)) ||
 455                             (((poss instanceof XDHEPossession) &&
 456                             (cred instanceof XDHECredentials)))) {
 457                         NamedGroupPossession p = (NamedGroupPossession)poss;
 458                         NamedGroupCredentials c = (NamedGroupCredentials)cred;
 459                         if (p.getNamedGroup() != c.getNamedGroup()) {
 460                             continue;
 461                         } else {
 462                             namedGroup = p.getNamedGroup();
 463                         }
 464                         namedGroupPossession = p;
 465                         namedGroupCredentials = c;
 466                         break search;
 467                     }
 468                 }
 469             }
 470 
 471             if (namedGroupPossession == null || namedGroupCredentials == null) {
 472                 throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 473                     "No sufficient ECDHE/XDH key agreement " +
 474                             "parameters negotiated");
 475             }
 476 
 477             String alg;
 478             switch (namedGroup.spec) {
 479                 case NAMED_GROUP_ECDHE:
 480                     alg = "ECDH";
 481                     break;
 482                 case NAMED_GROUP_XDH:
 483                     alg = "XDH";
 484                     break;
 485                 default:
 486                     throw new RuntimeException("Unexpected named group type");
 487             }
 488 
 489             return new KAKeyDerivation(alg, context,
 490                     namedGroupPossession.getPrivateKey(),
 491                     namedGroupCredentials.getPublicKey());
 492         }
 493     }
 494 }