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