src/java.base/share/classes/sun/security/ssl/DHKeyExchange.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
child 56603 f103e0c2be1e
child 56855 ee6aa4c74a4b
equal deleted inserted replaced
56541:92cbbfc996f3 56542:56aaa6cb3693
       
     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.math.BigInteger;
       
    30 import java.security.GeneralSecurityException;
       
    31 import java.security.InvalidKeyException;
       
    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.spec.AlgorithmParameterSpec;
       
    39 import javax.crypto.KeyAgreement;
       
    40 import javax.crypto.SecretKey;
       
    41 import javax.crypto.interfaces.DHPublicKey;
       
    42 import javax.crypto.spec.DHParameterSpec;
       
    43 import javax.crypto.spec.DHPublicKeySpec;
       
    44 import javax.crypto.spec.SecretKeySpec;
       
    45 import javax.net.ssl.SSLHandshakeException;
       
    46 import sun.security.action.GetPropertyAction;
       
    47 import sun.security.ssl.CipherSuite.HashAlg;
       
    48 import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
       
    49 import sun.security.ssl.SupportedGroupsExtension.NamedGroupType;
       
    50 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
       
    51 import sun.security.ssl.X509Authentication.X509Possession;
       
    52 import sun.security.util.KeyUtil;
       
    53 
       
    54 final class DHKeyExchange {
       
    55     static final SSLPossessionGenerator poGenerator =
       
    56             new DHEPossessionGenerator(false);
       
    57     static final SSLPossessionGenerator poExportableGenerator =
       
    58             new DHEPossessionGenerator(true);
       
    59     static final SSLKeyAgreementGenerator kaGenerator =
       
    60             new DHEKAGenerator();
       
    61 
       
    62     static final class DHECredentials implements SSLCredentials {
       
    63         final DHPublicKey popPublicKey;
       
    64         final NamedGroup namedGroup;
       
    65 
       
    66         DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) {
       
    67             this.popPublicKey = popPublicKey;
       
    68             this.namedGroup = namedGroup;
       
    69         }
       
    70 
       
    71         static DHECredentials valueOf(NamedGroup ng,
       
    72             byte[] encodedPublic) throws IOException, GeneralSecurityException {
       
    73 
       
    74             if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) {
       
    75                 throw new RuntimeException(
       
    76                         "Credentials decoding:  Not FFDHE named group");
       
    77             }
       
    78 
       
    79             if (encodedPublic == null || encodedPublic.length == 0) {
       
    80                 return null;
       
    81             }
       
    82 
       
    83             DHParameterSpec params = (DHParameterSpec)ng.getParameterSpec();
       
    84             if (params == null) {
       
    85                 return null;
       
    86             }
       
    87 
       
    88             KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman");
       
    89             DHPublicKeySpec spec = new DHPublicKeySpec(
       
    90                     new BigInteger(1, encodedPublic),
       
    91                     params.getP(), params.getG());
       
    92             DHPublicKey publicKey =
       
    93                     (DHPublicKey)kf.generatePublic(spec);
       
    94 
       
    95             return new DHECredentials(publicKey, ng);
       
    96         }
       
    97     }
       
    98 
       
    99     static final class DHEPossession implements SSLPossession {
       
   100         final PrivateKey privateKey;
       
   101         final DHPublicKey publicKey;
       
   102         final NamedGroup namedGroup;
       
   103 
       
   104         DHEPossession(NamedGroup namedGroup, SecureRandom random) {
       
   105             try {
       
   106                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DH");
       
   107                 DHParameterSpec params =
       
   108                         (DHParameterSpec)namedGroup.getParameterSpec();
       
   109                 kpg.initialize(params, random);
       
   110                 KeyPair kp = generateDHKeyPair(kpg);
       
   111                 if (kp == null) {
       
   112                     throw new RuntimeException("Could not generate DH keypair");
       
   113                 }
       
   114                 privateKey = kp.getPrivate();
       
   115                 publicKey = (DHPublicKey)kp.getPublic();
       
   116             } catch (GeneralSecurityException gse) {
       
   117                 throw new RuntimeException(
       
   118                         "Could not generate DH keypair", gse);
       
   119             }
       
   120 
       
   121             this.namedGroup = namedGroup;
       
   122         }
       
   123 
       
   124         DHEPossession(int keyLength, SecureRandom random) {
       
   125             DHParameterSpec params =
       
   126                     PredefinedDHParameterSpecs.definedParams.get(keyLength);
       
   127             try {
       
   128                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DiffieHellman");
       
   129                 if (params != null) {
       
   130                     kpg.initialize(params, random);
       
   131                 } else {
       
   132                     kpg.initialize(keyLength, random);
       
   133                 }
       
   134 
       
   135                 KeyPair kp = generateDHKeyPair(kpg);
       
   136                 if (kp == null) {
       
   137                     throw new RuntimeException(
       
   138                             "Could not generate DH keypair of " +
       
   139                             keyLength + " bits");
       
   140                 }
       
   141                 privateKey = kp.getPrivate();
       
   142                 publicKey = (DHPublicKey)kp.getPublic();
       
   143             } catch (GeneralSecurityException gse) {
       
   144                 throw new RuntimeException(
       
   145                         "Could not generate DH keypair", gse);
       
   146             }
       
   147 
       
   148             this.namedGroup = NamedGroup.valueOf(publicKey.getParams());
       
   149         }
       
   150 
       
   151         DHEPossession(DHECredentials credentials, SecureRandom random) {
       
   152             try {
       
   153                 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("DH");
       
   154                 kpg.initialize(credentials.popPublicKey.getParams(), random);
       
   155                 KeyPair kp = generateDHKeyPair(kpg);
       
   156                 if (kp == null) {
       
   157                     throw new RuntimeException("Could not generate DH keypair");
       
   158                 }
       
   159                 privateKey = kp.getPrivate();
       
   160                 publicKey = (DHPublicKey)kp.getPublic();
       
   161             } catch (GeneralSecurityException gse) {
       
   162                 throw new RuntimeException(
       
   163                         "Could not generate DH keypair", gse);
       
   164             }
       
   165 
       
   166             this.namedGroup = credentials.namedGroup;
       
   167         }
       
   168 
       
   169         // Generate and validate DHPublicKeySpec
       
   170         private KeyPair generateDHKeyPair(
       
   171                 KeyPairGenerator kpg) throws GeneralSecurityException {
       
   172             boolean doExtraValiadtion =
       
   173                     (!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName()));
       
   174             boolean isRecovering = false;
       
   175             for (int i = 0; i <= 2; i++) {      // Try to recove from failure.
       
   176                 KeyPair kp = kpg.generateKeyPair();
       
   177                 // validate the Diffie-Hellman public key
       
   178                 if (doExtraValiadtion) {
       
   179                     DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic());
       
   180                     try {
       
   181                         KeyUtil.validate(spec);
       
   182                     } catch (InvalidKeyException ivke) {
       
   183                         if (isRecovering) {
       
   184                             throw ivke;
       
   185                         }
       
   186                         // otherwise, ignore the exception and try again
       
   187                         continue;
       
   188                     }
       
   189                 }
       
   190 
       
   191                 return kp;
       
   192             }
       
   193 
       
   194             return null;
       
   195         }
       
   196 
       
   197         private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) {
       
   198             if (key instanceof DHPublicKey) {
       
   199                 DHPublicKey dhKey = (DHPublicKey)key;
       
   200                 DHParameterSpec params = dhKey.getParams();
       
   201                 return new DHPublicKeySpec(dhKey.getY(),
       
   202                                         params.getP(), params.getG());
       
   203             }
       
   204             try {
       
   205                 KeyFactory factory = JsseJce.getKeyFactory("DiffieHellman");
       
   206                 return factory.getKeySpec(key, DHPublicKeySpec.class);
       
   207             } catch (Exception e) {
       
   208                 throw new RuntimeException(e);
       
   209             }
       
   210         }
       
   211 
       
   212         @Override
       
   213         public byte[] encode() {
       
   214             // TODO: cannonical the return byte array length.
       
   215             return publicKey.getY().toByteArray();
       
   216         }
       
   217     }
       
   218 
       
   219     private static final class
       
   220             DHEPossessionGenerator implements SSLPossessionGenerator {
       
   221         // Flag to use smart ephemeral DH key which size matches the corresponding
       
   222         // authentication key
       
   223         private static final boolean useSmartEphemeralDHKeys;
       
   224 
       
   225         // Flag to use legacy ephemeral DH key which size is 512 bits for
       
   226         // exportable cipher suites, and 768 bits for others
       
   227         private static final boolean useLegacyEphemeralDHKeys;
       
   228 
       
   229         // The customized ephemeral DH key size for non-exportable cipher suites.
       
   230         private static final int customizedDHKeySize;
       
   231 
       
   232         // Is it for exportable cipher suite?
       
   233         private final boolean exportable;
       
   234 
       
   235         static {
       
   236             String property = GetPropertyAction.privilegedGetProperty(
       
   237                     "jdk.tls.ephemeralDHKeySize");
       
   238             if (property == null || property.length() == 0) {
       
   239                 useLegacyEphemeralDHKeys = false;
       
   240                 useSmartEphemeralDHKeys = false;
       
   241                 customizedDHKeySize = -1;
       
   242             } else if ("matched".equals(property)) {
       
   243                 useLegacyEphemeralDHKeys = false;
       
   244                 useSmartEphemeralDHKeys = true;
       
   245                 customizedDHKeySize = -1;
       
   246             } else if ("legacy".equals(property)) {
       
   247                 useLegacyEphemeralDHKeys = true;
       
   248                 useSmartEphemeralDHKeys = false;
       
   249                 customizedDHKeySize = -1;
       
   250             } else {
       
   251                 useLegacyEphemeralDHKeys = false;
       
   252                 useSmartEphemeralDHKeys = false;
       
   253 
       
   254                 try {
       
   255                     // DH parameter generation can be extremely slow, best to
       
   256                     // use one of the supported pre-computed DH parameters
       
   257                     // (see DHCrypt class).
       
   258                     customizedDHKeySize = Integer.parseUnsignedInt(property);
       
   259                     if (customizedDHKeySize < 1024 ||
       
   260                             customizedDHKeySize > 8192 ||
       
   261                             (customizedDHKeySize & 0x3f) != 0) {
       
   262                         throw new IllegalArgumentException(
       
   263                             "Unsupported customized DH key size: " +
       
   264                             customizedDHKeySize + ". " +
       
   265                             "The key size must be multiple of 64, " +
       
   266                             "and range from 1024 to 8192 (inclusive)");
       
   267                     }
       
   268                 } catch (NumberFormatException nfe) {
       
   269                     throw new IllegalArgumentException(
       
   270                         "Invalid system property jdk.tls.ephemeralDHKeySize");
       
   271                 }
       
   272             }
       
   273         }
       
   274 
       
   275         // Prevent instantiation of this class.
       
   276         private DHEPossessionGenerator(boolean exportable) {
       
   277             this.exportable = exportable;
       
   278         }
       
   279 
       
   280         // Used for ServerKeyExchange, TLS 1.2 and prior versions.
       
   281         @Override
       
   282         public SSLPossession createPossession(HandshakeContext context) {
       
   283             NamedGroup preferableNamedGroup = null;
       
   284             if (!useLegacyEphemeralDHKeys &&
       
   285                     (context.clientRequestedNamedGroups != null) &&
       
   286                     (!context.clientRequestedNamedGroups.isEmpty())) {
       
   287                 preferableNamedGroup =
       
   288                         SupportedGroups.getPreferredGroup(
       
   289                                 context.negotiatedProtocol,
       
   290                                 context.algorithmConstraints,
       
   291                                 NamedGroupType.NAMED_GROUP_FFDHE,
       
   292                                 context.clientRequestedNamedGroups);
       
   293                 if (preferableNamedGroup != null) {
       
   294                     return new DHEPossession(preferableNamedGroup,
       
   295                                 context.sslContext.getSecureRandom());
       
   296                 }
       
   297             }
       
   298 
       
   299             /*
       
   300              * 768 bits ephemeral DH private keys were used to be used in
       
   301              * ServerKeyExchange except that exportable ciphers max out at 512
       
   302              * bits modulus values. We still adhere to this behavior in legacy
       
   303              * mode (system property "jdk.tls.ephemeralDHKeySize" is defined
       
   304              * as "legacy").
       
   305              *
       
   306              * Old JDK (JDK 7 and previous) releases don't support DH keys
       
   307              * bigger than 1024 bits. We have to consider the compatibility
       
   308              * requirement. 1024 bits DH key is always used for non-exportable
       
   309              * cipher suites in default mode (system property
       
   310              * "jdk.tls.ephemeralDHKeySize" is not defined).
       
   311              *
       
   312              * However, if applications want more stronger strength, setting
       
   313              * system property "jdk.tls.ephemeralDHKeySize" to "matched"
       
   314              * is a workaround to use ephemeral DH key which size matches the
       
   315              * corresponding authentication key. For example, if the public key
       
   316              * size of an authentication certificate is 2048 bits, then the
       
   317              * ephemeral DH key size should be 2048 bits accordingly unless
       
   318              * the cipher suite is exportable.  This key sizing scheme keeps
       
   319              * the cryptographic strength consistent between authentication
       
   320              * keys and key-exchange keys.
       
   321              *
       
   322              * Applications may also want to customize the ephemeral DH key
       
   323              * size to a fixed length for non-exportable cipher suites. This
       
   324              * can be approached by setting system property
       
   325              * "jdk.tls.ephemeralDHKeySize" to a valid positive integer between
       
   326              * 1024 and 8192 bits, inclusive.
       
   327              *
       
   328              * Note that the minimum acceptable key size is 1024 bits except
       
   329              * exportable cipher suites or legacy mode.
       
   330              *
       
   331              * Note that per RFC 2246, the key size limit of DH is 512 bits for
       
   332              * exportable cipher suites.  Because of the weakness, exportable
       
   333              * cipher suites are deprecated since TLS v1.1 and they are not
       
   334              * enabled by default in Oracle provider. The legacy behavior is
       
   335              * reserved and 512 bits DH key is always used for exportable
       
   336              * cipher suites.
       
   337              */
       
   338             int keySize = exportable ? 512 : 1024;           // default mode
       
   339             if (!exportable) {
       
   340                 if (useLegacyEphemeralDHKeys) {          // legacy mode
       
   341                     keySize = 768;
       
   342                 } else if (useSmartEphemeralDHKeys) {    // matched mode
       
   343                     PrivateKey key = null;
       
   344                     ServerHandshakeContext shc =
       
   345                             (ServerHandshakeContext)context;
       
   346                     if (shc.interimAuthn instanceof X509Possession) {
       
   347                         key = ((X509Possession)shc.interimAuthn).popPrivateKey;
       
   348                     }
       
   349 
       
   350                     if (key != null) {
       
   351                         int ks = KeyUtil.getKeySize(key);
       
   352 
       
   353                         // DH parameter generation can be extremely slow, make
       
   354                         // sure to use one of the supported pre-computed DH
       
   355                         // parameters.
       
   356                         //
       
   357                         // Old deployed applications may not be ready to
       
   358                         // support DH key sizes bigger than 2048 bits.  Please
       
   359                         // DON'T use value other than 1024 and 2048 at present.
       
   360                         // May improve the underlying providers and key size
       
   361                         // limit in the future when the compatibility and
       
   362                         // interoperability impact is limited.
       
   363                         keySize = ks <= 1024 ? 1024 : 2048;
       
   364                     } // Otherwise, anonymous cipher suites, 1024-bit is used.
       
   365                 } else if (customizedDHKeySize > 0) {    // customized mode
       
   366                     keySize = customizedDHKeySize;
       
   367                 }
       
   368             }
       
   369 
       
   370             return new DHEPossession(
       
   371                     keySize, context.sslContext.getSecureRandom());
       
   372         }
       
   373     }
       
   374 
       
   375     private static final
       
   376             class DHEKAGenerator implements SSLKeyAgreementGenerator {
       
   377         static private DHEKAGenerator instance = new DHEKAGenerator();
       
   378 
       
   379         // Prevent instantiation of this class.
       
   380         private DHEKAGenerator() {
       
   381             // blank
       
   382         }
       
   383 
       
   384         @Override
       
   385         public SSLKeyDerivation createKeyDerivation(
       
   386                 HandshakeContext context) throws IOException {
       
   387             DHEPossession dhePossession = null;
       
   388             DHECredentials dheCredentials = null;
       
   389             for (SSLPossession poss : context.handshakePossessions) {
       
   390                 if (!(poss instanceof DHEPossession)) {
       
   391                     continue;
       
   392                 }
       
   393 
       
   394                 DHEPossession dhep = (DHEPossession)poss;
       
   395                 for (SSLCredentials cred : context.handshakeCredentials) {
       
   396                     if (!(cred instanceof DHECredentials)) {
       
   397                         continue;
       
   398                     }
       
   399                     DHECredentials dhec = (DHECredentials)cred;
       
   400                     if (dhep.namedGroup != null && dhec.namedGroup != null) {
       
   401                         if (dhep.namedGroup.equals(dhec.namedGroup)) {
       
   402                             dheCredentials = (DHECredentials)cred;
       
   403                             break;
       
   404                         }
       
   405                     } else {
       
   406                         DHParameterSpec pps = dhep.publicKey.getParams();
       
   407                         DHParameterSpec cps = dhec.popPublicKey.getParams();
       
   408                         if (pps.getP().equals(cps.getP()) &&
       
   409                                 pps.getG().equals(cps.getG())) {
       
   410                             dheCredentials = (DHECredentials)cred;
       
   411                             break;
       
   412                         }
       
   413                     }
       
   414                 }
       
   415 
       
   416                 if (dheCredentials != null) {
       
   417                     dhePossession = (DHEPossession)poss;
       
   418                     break;
       
   419                 }
       
   420             }
       
   421 
       
   422             if (dhePossession == null || dheCredentials == null) {
       
   423                 context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
       
   424                     "No sufficient DHE key agreement parameters negotiated");
       
   425             }
       
   426 
       
   427             return new DHEKAKeyDerivation(context,
       
   428                     dhePossession.privateKey, dheCredentials.popPublicKey);
       
   429         }
       
   430 
       
   431         private static final
       
   432                 class DHEKAKeyDerivation implements SSLKeyDerivation {
       
   433             private final HandshakeContext context;
       
   434             private final PrivateKey localPrivateKey;
       
   435             private final PublicKey peerPublicKey;
       
   436 
       
   437             DHEKAKeyDerivation(HandshakeContext context,
       
   438                     PrivateKey localPrivateKey,
       
   439                     PublicKey peerPublicKey) {
       
   440                 this.context = context;
       
   441                 this.localPrivateKey = localPrivateKey;
       
   442                 this.peerPublicKey = peerPublicKey;
       
   443             }
       
   444 
       
   445             @Override
       
   446             public SecretKey deriveKey(String algorithm,
       
   447                     AlgorithmParameterSpec params) throws IOException {
       
   448                 if (!context.negotiatedProtocol.useTLS13PlusSpec()) {
       
   449                     return t12DeriveKey(algorithm, params);
       
   450                 } else {
       
   451                     return t13DeriveKey(algorithm, params);
       
   452                 }
       
   453             }
       
   454 
       
   455             private SecretKey t12DeriveKey(String algorithm,
       
   456                     AlgorithmParameterSpec params) throws IOException {
       
   457                 try {
       
   458                     KeyAgreement ka = JsseJce.getKeyAgreement("DH");
       
   459                     ka.init(localPrivateKey);
       
   460                     ka.doPhase(peerPublicKey, true);
       
   461                     SecretKey preMasterSecret =
       
   462                             ka.generateSecret("TlsPremasterSecret");
       
   463                     SSLMasterKeyDerivation mskd =
       
   464                             SSLMasterKeyDerivation.valueOf(
       
   465                                     context.negotiatedProtocol);
       
   466                     SSLKeyDerivation kd = mskd.createKeyDerivation(
       
   467                             context, preMasterSecret);
       
   468                     return kd.deriveKey("TODO", params);
       
   469                 } catch (GeneralSecurityException gse) {
       
   470                     throw (SSLHandshakeException) new SSLHandshakeException(
       
   471                         "Could not generate secret").initCause(gse);
       
   472                 }
       
   473             }
       
   474 
       
   475             private SecretKey t13DeriveKey(String algorithm,
       
   476                     AlgorithmParameterSpec params) throws IOException {
       
   477                 try {
       
   478                     KeyAgreement ka = JsseJce.getKeyAgreement("DH");
       
   479                     ka.init(localPrivateKey);
       
   480                     ka.doPhase(peerPublicKey, true);
       
   481                     SecretKey sharedSecret =
       
   482                             ka.generateSecret("TlsPremasterSecret");
       
   483 
       
   484                     HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg;
       
   485                     SSLKeyDerivation kd = context.handshakeKeyDerivation;
       
   486                     HKDF hkdf = new HKDF(hashAlg.name);
       
   487                     if (kd == null) {   // No PSK is in use.
       
   488                         // If PSK is not in use Early Secret will still be
       
   489                         // HKDF-Extract(0, 0).
       
   490                         byte[] zeros = new byte[hashAlg.hashLength];
       
   491                         SecretKeySpec ikm =
       
   492                                 new SecretKeySpec(zeros, "TlsPreSharedSecret");
       
   493                         SecretKey earlySecret =
       
   494                                 hkdf.extract(zeros, ikm, "TlsEarlySecret");
       
   495                         kd = new SSLSecretDerivation(context, earlySecret);
       
   496                     }
       
   497 
       
   498                     // derive salt secret
       
   499                     SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null);
       
   500 
       
   501                     // derive handshake secret
       
   502                     return hkdf.extract(saltSecret, sharedSecret, algorithm);
       
   503                 } catch (GeneralSecurityException gse) {
       
   504                     throw (SSLHandshakeException) new SSLHandshakeException(
       
   505                         "Could not generate secret").initCause(gse);
       
   506                 }
       
   507             }
       
   508         }
       
   509     }
       
   510 }