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