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