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