|
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 } |