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