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 SSLKeyDerivation kd = mskd.createKeyDerivation( 437 context, preMasterSecret); 438 return kd.deriveKey("TODO", params); 439 } catch (GeneralSecurityException gse) { 440 throw (SSLHandshakeException) new SSLHandshakeException( 441 "Could not generate secret").initCause(gse); 442 } 443 } 444 445 private SecretKey t13DeriveKey(String algorithm, 446 AlgorithmParameterSpec params) throws IOException { 447 try { 448 KeyAgreement ka = JsseJce.getKeyAgreement("ECDH"); 449 ka.init(localPrivateKey); 450 ka.doPhase(peerPublicKey, true); 451 SecretKey sharedSecret = 452 ka.generateSecret("TlsPremasterSecret"); 453 454 HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; 455 SSLKeyDerivation kd = context.handshakeKeyDerivation; 456 HKDF hkdf = new HKDF(hashAlg.name); 457 if (kd == null) { // No PSK is in use. 458 // If PSK is not in use Early Secret will still be 459 // HKDF-Extract(0, 0). 460 byte[] zeros = new byte[hashAlg.hashLength]; 461 SecretKeySpec ikm = 462 new SecretKeySpec(zeros, "TlsPreSharedSecret"); 463 SecretKey earlySecret = 464 hkdf.extract(zeros, ikm, "TlsEarlySecret"); 465 kd = new SSLSecretDerivation(context, earlySecret); 466 } 467 468 // derive salt secret 469 SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); 470 471 // derive handshake secret 472 return hkdf.extract(saltSecret, sharedSecret, algorithm); 473 } catch (GeneralSecurityException gse) { 474 throw (SSLHandshakeException) new SSLHandshakeException( 475 "Could not generate secret").initCause(gse); 476 } 477 } 478 } 479 }