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