src/java.base/share/classes/sun/security/ssl/ECDHKeyExchange.java
changeset 50768 68fa3d4026ea
child 53064 103ed9569fc8
child 56858 829e9b5ace08
equal deleted inserted replaced
50767:356eaea05bf0 50768:68fa3d4026ea
       
     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                 if (mskd == null) {
       
   437                     // unlikely
       
   438                     throw new SSLHandshakeException(
       
   439                             "No expected master key derivation for protocol: " +
       
   440                             context.negotiatedProtocol.name);
       
   441                 }
       
   442                 SSLKeyDerivation kd = mskd.createKeyDerivation(
       
   443                         context, preMasterSecret);
       
   444                 return kd.deriveKey("MasterSecret", params);
       
   445             } catch (GeneralSecurityException gse) {
       
   446                 throw (SSLHandshakeException) new SSLHandshakeException(
       
   447                     "Could not generate secret").initCause(gse);
       
   448             }
       
   449         }
       
   450 
       
   451         private SecretKey t13DeriveKey(String algorithm,
       
   452                 AlgorithmParameterSpec params) throws IOException {
       
   453             try {
       
   454                 KeyAgreement ka = JsseJce.getKeyAgreement("ECDH");
       
   455                 ka.init(localPrivateKey);
       
   456                 ka.doPhase(peerPublicKey, true);
       
   457                 SecretKey sharedSecret =
       
   458                         ka.generateSecret("TlsPremasterSecret");
       
   459 
       
   460                 HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg;
       
   461                 SSLKeyDerivation kd = context.handshakeKeyDerivation;
       
   462                 HKDF hkdf = new HKDF(hashAlg.name);
       
   463                 if (kd == null) {   // No PSK is in use.
       
   464                     // If PSK is not in use Early Secret will still be
       
   465                     // HKDF-Extract(0, 0).
       
   466                     byte[] zeros = new byte[hashAlg.hashLength];
       
   467                     SecretKeySpec ikm =
       
   468                             new SecretKeySpec(zeros, "TlsPreSharedSecret");
       
   469                     SecretKey earlySecret =
       
   470                             hkdf.extract(zeros, ikm, "TlsEarlySecret");
       
   471                     kd = new SSLSecretDerivation(context, earlySecret);
       
   472                 }
       
   473 
       
   474                 // derive salt secret
       
   475                 SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null);
       
   476 
       
   477                 // derive handshake secret
       
   478                 return hkdf.extract(saltSecret, sharedSecret, algorithm);
       
   479             } catch (GeneralSecurityException gse) {
       
   480                 throw (SSLHandshakeException) new SSLHandshakeException(
       
   481                     "Could not generate secret").initCause(gse);
       
   482             }
       
   483         }
       
   484     }
       
   485 }