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 SSLKeyAgreementCredentials {
  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         @Override
  76         public PublicKey getPublicKey() {
  77             return popPublicKey;
  78         }
  79 
  80         static ECDHECredentials valueOf(NamedGroup namedGroup,
  81             byte[] encodedPoint) throws IOException, GeneralSecurityException {
  82 
  83             if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) {
  84                 throw new RuntimeException(
  85                     "Credentials decoding:  Not ECDHE named group");
  86             }
  87 
  88             if (encodedPoint == null || encodedPoint.length == 0) {
  89                 return null;
  90             }
  91 
  92             ECParameterSpec parameters =
  93                     JsseJce.getECParameterSpec(namedGroup.oid);
  94             if (parameters == null) {
  95                 return null;
  96             }
  97 
  98             ECPoint point = JsseJce.decodePoint(
  99                     encodedPoint, parameters.getCurve());
 100             KeyFactory factory = JsseJce.getKeyFactory("EC");
 101             ECPublicKey publicKey = (ECPublicKey)factory.generatePublic(
 102                     new ECPublicKeySpec(point, parameters));
 103             return new ECDHECredentials(publicKey, namedGroup);
 104         }
 105     }
 106 
 107     static final class ECDHEPossession implements SSLPossession {
 108         final PrivateKey privateKey;
 109         final ECPublicKey publicKey;
 110         final NamedGroup namedGroup;
 111 
 112         ECDHEPossession(NamedGroup namedGroup, SecureRandom random) {
 113             try {
 114                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC");
 115                 ECGenParameterSpec params =
 116                         (ECGenParameterSpec)namedGroup.getParameterSpec();
 117                 kpg.initialize(params, random);
 118                 KeyPair kp = kpg.generateKeyPair();
 119                 privateKey = kp.getPrivate();
 120                 publicKey = (ECPublicKey)kp.getPublic();
 121             } catch (GeneralSecurityException e) {
 122                 throw new RuntimeException(
 123                     "Could not generate ECDH keypair", e);
 124             }
 125 
 126             this.namedGroup = namedGroup;
 127         }
 128 
 129         ECDHEPossession(ECDHECredentials credentials, SecureRandom random) {
 130             ECParameterSpec params = credentials.popPublicKey.getParams();
 131             try {
 132                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC");
 133                 kpg.initialize(params, random);
 134                 KeyPair kp = kpg.generateKeyPair();
 135                 privateKey = kp.getPrivate();
 136                 publicKey = (ECPublicKey)kp.getPublic();
 137             } catch (GeneralSecurityException e) {
 138                 throw new RuntimeException(
 139                     "Could not generate ECDH keypair", e);
 140             }
 141 
 142             this.namedGroup = credentials.namedGroup;
 143         }
 144 
 145         @Override
 146         public byte[] encode() {
 147             return ECUtil.encodePoint(
 148                     publicKey.getW(), publicKey.getParams().getCurve());
 149         }
 150 
 151         // called by ClientHandshaker with either the server's static or
 152         // ephemeral public key
 153         SecretKey getAgreedSecret(
 154                 PublicKey peerPublicKey) throws SSLHandshakeException {
 155 
 156             try {
 157                 KeyAgreement ka = JsseJce.getKeyAgreement("ECDH");
 158                 ka.init(privateKey);
 159                 ka.doPhase(peerPublicKey, true);
 160                 return ka.generateSecret("TlsPremasterSecret");
 161             } catch (GeneralSecurityException e) {
 162                 throw (SSLHandshakeException) new SSLHandshakeException(
 163                     "Could not generate secret").initCause(e);
 164             }
 165         }
 166 
 167         // called by ServerHandshaker
 168         SecretKey getAgreedSecret(
 169                 byte[] encodedPoint) throws SSLHandshakeException {
 170             try {
 171                 ECParameterSpec params = publicKey.getParams();
 172                 ECPoint point =
 173                         JsseJce.decodePoint(encodedPoint, params.getCurve());
 174                 KeyFactory kf = JsseJce.getKeyFactory("EC");
 175                 ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
 176                 PublicKey peerPublicKey = kf.generatePublic(spec);
 177                 return getAgreedSecret(peerPublicKey);
 178             } catch (GeneralSecurityException | java.io.IOException e) {
 179                 throw (SSLHandshakeException) new SSLHandshakeException(
 180                     "Could not generate secret").initCause(e);
 181             }
 182         }
 183 
 184         // Check constraints of the specified EC public key.
 185         void checkConstraints(AlgorithmConstraints constraints,
 186                 byte[] encodedPoint) throws SSLHandshakeException {
 187             try {
 188 
 189                 ECParameterSpec params = publicKey.getParams();
 190                 ECPoint point =
 191                         JsseJce.decodePoint(encodedPoint, params.getCurve());
 192                 ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
 193 
 194                 KeyFactory kf = JsseJce.getKeyFactory("EC");
 195                 ECPublicKey pubKey = (ECPublicKey)kf.generatePublic(spec);
 196 
 197                 // check constraints of ECPublicKey
 198                 if (!constraints.permits(
 199                         EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), pubKey)) {
 200                     throw new SSLHandshakeException(
 201                         "ECPublicKey does not comply to algorithm constraints");
 202                 }
 203             } catch (GeneralSecurityException | java.io.IOException e) {
 204                 throw (SSLHandshakeException) new SSLHandshakeException(
 205                         "Could not generate ECPublicKey").initCause(e);
 206             }
 207         }
 208     }
 209 
 210     private static final
 211             class ECDHEPossessionGenerator implements SSLPossessionGenerator {
 212         // Prevent instantiation of this class.
 213         private ECDHEPossessionGenerator() {
 214             // blank
 215         }
 216 
 217         @Override
 218         public SSLPossession createPossession(HandshakeContext context) {
 219             NamedGroup preferableNamedGroup = null;
 220             if ((context.clientRequestedNamedGroups != null) &&
 221                     (!context.clientRequestedNamedGroups.isEmpty())) {
 222                 preferableNamedGroup = SupportedGroups.getPreferredGroup(
 223                         context.negotiatedProtocol,
 224                         context.algorithmConstraints,
 225                         NamedGroupType.NAMED_GROUP_ECDHE,
 226                         context.clientRequestedNamedGroups);
 227             } else {
 228                 preferableNamedGroup = SupportedGroups.getPreferredGroup(
 229                         context.negotiatedProtocol,
 230                         context.algorithmConstraints,
 231                         NamedGroupType.NAMED_GROUP_ECDHE);
 232             }
 233 
 234             if (preferableNamedGroup != null) {
 235                 return new ECDHEPossession(preferableNamedGroup,
 236                             context.sslContext.getSecureRandom());
 237             }
 238 
 239             // no match found, cannot use this cipher suite.
 240             //
 241             return null;
 242         }
 243     }
 244 
 245     private static final
 246             class ECDHKAGenerator implements SSLKeyAgreementGenerator {
 247         // Prevent instantiation of this class.
 248         private ECDHKAGenerator() {
 249             // blank
 250         }
 251 
 252         @Override
 253         public SSLKeyDerivation createKeyDerivation(
 254                 HandshakeContext context) throws IOException {
 255             if (context instanceof ServerHandshakeContext) {
 256                 return createServerKeyDerivation(
 257                         (ServerHandshakeContext)context);
 258             } else {
 259                 return createClientKeyDerivation(
 260                         (ClientHandshakeContext)context);
 261             }
 262         }
 263 
 264         private SSLKeyDerivation createServerKeyDerivation(
 265                 ServerHandshakeContext shc) throws IOException {
 266             X509Possession x509Possession = null;
 267             ECDHECredentials ecdheCredentials = null;
 268             for (SSLPossession poss : shc.handshakePossessions) {
 269                 if (!(poss instanceof X509Possession)) {
 270                     continue;
 271                 }
 272 
 273                 PrivateKey privateKey = ((X509Possession)poss).popPrivateKey;
 274                 if (!privateKey.getAlgorithm().equals("EC")) {
 275                     continue;
 276                 }
 277 
 278                 ECParameterSpec params = ((ECPrivateKey)privateKey).getParams();
 279                 NamedGroup ng = NamedGroup.valueOf(params);
 280                 if (ng == null) {
 281                     // unlikely, have been checked during cipher suite negotiation.
 282                     shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 283                         "Unsupported EC server cert for ECDH key exchange");
 284                 }
 285 
 286                 for (SSLCredentials cred : shc.handshakeCredentials) {
 287                     if (!(cred instanceof ECDHECredentials)) {
 288                         continue;
 289                     }
 290                     if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
 291                         ecdheCredentials = (ECDHECredentials)cred;
 292                         break;
 293                     }
 294                 }
 295 
 296                 if (ecdheCredentials != null) {
 297                     x509Possession = (X509Possession)poss;
 298                     break;
 299                 }
 300             }
 301 
 302             if (x509Possession == null || ecdheCredentials == null) {
 303                 shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 304                     "No sufficient ECDHE key agreement parameters negotiated");
 305             }
 306 
 307             return new KAKeyDerivation("ECDH", shc,
 308                 x509Possession.popPrivateKey, ecdheCredentials.popPublicKey);
 309         }
 310 
 311         private SSLKeyDerivation createClientKeyDerivation(
 312                 ClientHandshakeContext chc) throws IOException {
 313             ECDHEPossession ecdhePossession = null;
 314             X509Credentials x509Credentials = null;
 315             for (SSLPossession poss : chc.handshakePossessions) {
 316                 if (!(poss instanceof ECDHEPossession)) {
 317                     continue;
 318                 }
 319 
 320                 NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
 321                 for (SSLCredentials cred : chc.handshakeCredentials) {
 322                     if (!(cred instanceof X509Credentials)) {
 323                         continue;
 324                     }
 325 
 326                     PublicKey publicKey = ((X509Credentials)cred).popPublicKey;
 327                     if (!publicKey.getAlgorithm().equals("EC")) {
 328                         continue;
 329                     }
 330                     ECParameterSpec params =
 331                             ((ECPublicKey)publicKey).getParams();
 332                     NamedGroup namedGroup = NamedGroup.valueOf(params);
 333                     if (namedGroup == null) {
 334                         // unlikely, should have been checked previously
 335                         chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 336                             "Unsupported EC server cert for ECDH key exchange");
 337                     }
 338 
 339                     if (ng.equals(namedGroup)) {
 340                         x509Credentials = (X509Credentials)cred;
 341                         break;
 342                     }
 343                 }
 344 
 345                 if (x509Credentials != null) {
 346                     ecdhePossession = (ECDHEPossession)poss;
 347                     break;
 348                 }
 349             }
 350 
 351             if (ecdhePossession == null || x509Credentials == null) {
 352                 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 353                     "No sufficient ECDH key agreement parameters negotiated");
 354             }
 355 
 356             return new KAKeyDerivation("ECDH", chc,
 357                 ecdhePossession.privateKey, x509Credentials.popPublicKey);
 358         }
 359     }
 360 
 361     private static final
 362             class ECDHEKAGenerator implements SSLKeyAgreementGenerator {
 363         // Prevent instantiation of this class.
 364         private ECDHEKAGenerator() {
 365             // blank
 366         }
 367 
 368         @Override
 369         public SSLKeyDerivation createKeyDerivation(
 370                 HandshakeContext context) throws IOException {
 371             ECDHEPossession ecdhePossession = null;
 372             ECDHECredentials ecdheCredentials = null;
 373             for (SSLPossession poss : context.handshakePossessions) {
 374                 if (!(poss instanceof ECDHEPossession)) {
 375                     continue;
 376                 }
 377 
 378                 NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
 379                 for (SSLCredentials cred : context.handshakeCredentials) {
 380                     if (!(cred instanceof ECDHECredentials)) {
 381                         continue;
 382                     }
 383                     if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
 384                         ecdheCredentials = (ECDHECredentials)cred;
 385                         break;
 386                     }
 387                 }
 388 
 389                 if (ecdheCredentials != null) {
 390                     ecdhePossession = (ECDHEPossession)poss;
 391                     break;
 392                 }
 393             }
 394 
 395             if (ecdhePossession == null || ecdheCredentials == null) {
 396                 context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 397                     "No sufficient ECDHE key agreement parameters negotiated");
 398             }
 399 
 400             return new KAKeyDerivation("ECDH", context,
 401                 ecdhePossession.privateKey, ecdheCredentials.popPublicKey);
 402         }
 403     }
 404 }