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 SSLKeyAgreementCredentials { 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 @Override 76 public PublicKey getPublicKey() { 77 return popPublicKey; 78 } 79 80 static ECDHECredentials valueOf(NamedGroup namedGroup, 81 byte[] encodedPoint) throws IOException, GeneralSecurityException { 82 83 if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) { 84 throw new RuntimeException( 85 "Credentials decoding: Not ECDHE named group"); 86 } 87 88 if (encodedPoint == null || encodedPoint.length == 0) { 89 return null; 90 } 91 92 ECParameterSpec parameters = 93 JsseJce.getECParameterSpec(namedGroup.oid); 94 if (parameters == null) { 95 return null; 96 } 97 98 ECPoint point = JsseJce.decodePoint( 99 encodedPoint, parameters.getCurve()); 100 KeyFactory factory = JsseJce.getKeyFactory("EC"); 101 ECPublicKey publicKey = (ECPublicKey)factory.generatePublic( 102 new ECPublicKeySpec(point, parameters)); 103 return new ECDHECredentials(publicKey, namedGroup); 104 } 105 } 106 107 static final class ECDHEPossession implements SSLPossession { 108 final PrivateKey privateKey; 109 final ECPublicKey publicKey; 110 final NamedGroup namedGroup; 111 112 ECDHEPossession(NamedGroup namedGroup, SecureRandom random) { 113 try { 114 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC"); 115 ECGenParameterSpec params = 116 (ECGenParameterSpec)namedGroup.getParameterSpec(); 117 kpg.initialize(params, random); 118 KeyPair kp = kpg.generateKeyPair(); 119 privateKey = kp.getPrivate(); 120 publicKey = (ECPublicKey)kp.getPublic(); 121 } catch (GeneralSecurityException e) { 122 throw new RuntimeException( 123 "Could not generate ECDH keypair", e); 124 } 125 126 this.namedGroup = namedGroup; 127 } 128 129 ECDHEPossession(ECDHECredentials credentials, SecureRandom random) { 130 ECParameterSpec params = credentials.popPublicKey.getParams(); 131 try { 132 KeyPairGenerator kpg = JsseJce.getKeyPairGenerator("EC"); 133 kpg.initialize(params, random); 134 KeyPair kp = kpg.generateKeyPair(); 135 privateKey = kp.getPrivate(); 136 publicKey = (ECPublicKey)kp.getPublic(); 137 } catch (GeneralSecurityException e) { 138 throw new RuntimeException( 139 "Could not generate ECDH keypair", e); 140 } 141 142 this.namedGroup = credentials.namedGroup; 143 } 144 145 @Override 146 public byte[] encode() { 147 return ECUtil.encodePoint( 148 publicKey.getW(), publicKey.getParams().getCurve()); 149 } 150 151 // called by ClientHandshaker with either the server's static or 152 // ephemeral public key 153 SecretKey getAgreedSecret( 154 PublicKey peerPublicKey) throws SSLHandshakeException { 155 156 try { 157 KeyAgreement ka = JsseJce.getKeyAgreement("ECDH"); 158 ka.init(privateKey); 159 ka.doPhase(peerPublicKey, true); 160 return ka.generateSecret("TlsPremasterSecret"); 161 } catch (GeneralSecurityException e) { 162 throw (SSLHandshakeException) new SSLHandshakeException( 163 "Could not generate secret").initCause(e); 164 } 165 } 166 167 // called by ServerHandshaker 168 SecretKey getAgreedSecret( 169 byte[] encodedPoint) throws SSLHandshakeException { 170 try { 171 ECParameterSpec params = publicKey.getParams(); 172 ECPoint point = 173 JsseJce.decodePoint(encodedPoint, params.getCurve()); 174 KeyFactory kf = JsseJce.getKeyFactory("EC"); 175 ECPublicKeySpec spec = new ECPublicKeySpec(point, params); 176 PublicKey peerPublicKey = kf.generatePublic(spec); 177 return getAgreedSecret(peerPublicKey); 178 } catch (GeneralSecurityException | java.io.IOException e) { 179 throw (SSLHandshakeException) new SSLHandshakeException( 180 "Could not generate secret").initCause(e); 181 } 182 } 183 184 // Check constraints of the specified EC public key. 185 void checkConstraints(AlgorithmConstraints constraints, 186 byte[] encodedPoint) throws SSLHandshakeException { 187 try { 188 189 ECParameterSpec params = publicKey.getParams(); 190 ECPoint point = 191 JsseJce.decodePoint(encodedPoint, params.getCurve()); 192 ECPublicKeySpec spec = new ECPublicKeySpec(point, params); 193 194 KeyFactory kf = JsseJce.getKeyFactory("EC"); 195 ECPublicKey pubKey = (ECPublicKey)kf.generatePublic(spec); 196 197 // check constraints of ECPublicKey 198 if (!constraints.permits( 199 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), pubKey)) { 200 throw new SSLHandshakeException( 201 "ECPublicKey does not comply to algorithm constraints"); 202 } 203 } catch (GeneralSecurityException | java.io.IOException e) { 204 throw (SSLHandshakeException) new SSLHandshakeException( 205 "Could not generate ECPublicKey").initCause(e); 206 } 207 } 208 } 209 210 private static final 211 class ECDHEPossessionGenerator implements SSLPossessionGenerator { 212 // Prevent instantiation of this class. 213 private ECDHEPossessionGenerator() { 214 // blank 215 } 216 217 @Override 218 public SSLPossession createPossession(HandshakeContext context) { 219 NamedGroup preferableNamedGroup = null; 220 if ((context.clientRequestedNamedGroups != null) && 221 (!context.clientRequestedNamedGroups.isEmpty())) { 222 preferableNamedGroup = SupportedGroups.getPreferredGroup( 223 context.negotiatedProtocol, 224 context.algorithmConstraints, 225 NamedGroupType.NAMED_GROUP_ECDHE, 226 context.clientRequestedNamedGroups); 227 } else { 228 preferableNamedGroup = SupportedGroups.getPreferredGroup( 229 context.negotiatedProtocol, 230 context.algorithmConstraints, 231 NamedGroupType.NAMED_GROUP_ECDHE); 232 } 233 234 if (preferableNamedGroup != null) { 235 return new ECDHEPossession(preferableNamedGroup, 236 context.sslContext.getSecureRandom()); 237 } 238 239 // no match found, cannot use this cipher suite. 240 // 241 return null; 242 } 243 } 244 245 private static final 246 class ECDHKAGenerator implements SSLKeyAgreementGenerator { 247 // Prevent instantiation of this class. 248 private ECDHKAGenerator() { 249 // blank 250 } 251 252 @Override 253 public SSLKeyDerivation createKeyDerivation( 254 HandshakeContext context) throws IOException { 255 if (context instanceof ServerHandshakeContext) { 256 return createServerKeyDerivation( 257 (ServerHandshakeContext)context); 258 } else { 259 return createClientKeyDerivation( 260 (ClientHandshakeContext)context); 261 } 262 } 263 264 private SSLKeyDerivation createServerKeyDerivation( 265 ServerHandshakeContext shc) throws IOException { 266 X509Possession x509Possession = null; 267 ECDHECredentials ecdheCredentials = null; 268 for (SSLPossession poss : shc.handshakePossessions) { 269 if (!(poss instanceof X509Possession)) { 270 continue; 271 } 272 273 PrivateKey privateKey = ((X509Possession)poss).popPrivateKey; 274 if (!privateKey.getAlgorithm().equals("EC")) { 275 continue; 276 } 277 278 ECParameterSpec params = ((ECPrivateKey)privateKey).getParams(); 279 NamedGroup ng = NamedGroup.valueOf(params); 280 if (ng == null) { 281 // unlikely, have been checked during cipher suite negotiation. 282 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 283 "Unsupported EC server cert for ECDH key exchange"); 284 } 285 286 for (SSLCredentials cred : shc.handshakeCredentials) { 287 if (!(cred instanceof ECDHECredentials)) { 288 continue; 289 } 290 if (ng.equals(((ECDHECredentials)cred).namedGroup)) { 291 ecdheCredentials = (ECDHECredentials)cred; 292 break; 293 } 294 } 295 296 if (ecdheCredentials != null) { 297 x509Possession = (X509Possession)poss; 298 break; 299 } 300 } 301 302 if (x509Possession == null || ecdheCredentials == null) { 303 shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 304 "No sufficient ECDHE key agreement parameters negotiated"); 305 } 306 307 return new KAKeyDerivation("ECDH", shc, 308 x509Possession.popPrivateKey, ecdheCredentials.popPublicKey); 309 } 310 311 private SSLKeyDerivation createClientKeyDerivation( 312 ClientHandshakeContext chc) throws IOException { 313 ECDHEPossession ecdhePossession = null; 314 X509Credentials x509Credentials = null; 315 for (SSLPossession poss : chc.handshakePossessions) { 316 if (!(poss instanceof ECDHEPossession)) { 317 continue; 318 } 319 320 NamedGroup ng = ((ECDHEPossession)poss).namedGroup; 321 for (SSLCredentials cred : chc.handshakeCredentials) { 322 if (!(cred instanceof X509Credentials)) { 323 continue; 324 } 325 326 PublicKey publicKey = ((X509Credentials)cred).popPublicKey; 327 if (!publicKey.getAlgorithm().equals("EC")) { 328 continue; 329 } 330 ECParameterSpec params = 331 ((ECPublicKey)publicKey).getParams(); 332 NamedGroup namedGroup = NamedGroup.valueOf(params); 333 if (namedGroup == null) { 334 // unlikely, should have been checked previously 335 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 336 "Unsupported EC server cert for ECDH key exchange"); 337 } 338 339 if (ng.equals(namedGroup)) { 340 x509Credentials = (X509Credentials)cred; 341 break; 342 } 343 } 344 345 if (x509Credentials != null) { 346 ecdhePossession = (ECDHEPossession)poss; 347 break; 348 } 349 } 350 351 if (ecdhePossession == null || x509Credentials == null) { 352 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 353 "No sufficient ECDH key agreement parameters negotiated"); 354 } 355 356 return new KAKeyDerivation("ECDH", chc, 357 ecdhePossession.privateKey, x509Credentials.popPublicKey); 358 } 359 } 360 361 private static final 362 class ECDHEKAGenerator implements SSLKeyAgreementGenerator { 363 // Prevent instantiation of this class. 364 private ECDHEKAGenerator() { 365 // blank 366 } 367 368 @Override 369 public SSLKeyDerivation createKeyDerivation( 370 HandshakeContext context) throws IOException { 371 ECDHEPossession ecdhePossession = null; 372 ECDHECredentials ecdheCredentials = null; 373 for (SSLPossession poss : context.handshakePossessions) { 374 if (!(poss instanceof ECDHEPossession)) { 375 continue; 376 } 377 378 NamedGroup ng = ((ECDHEPossession)poss).namedGroup; 379 for (SSLCredentials cred : context.handshakeCredentials) { 380 if (!(cred instanceof ECDHECredentials)) { 381 continue; 382 } 383 if (ng.equals(((ECDHECredentials)cred).namedGroup)) { 384 ecdheCredentials = (ECDHECredentials)cred; 385 break; 386 } 387 } 388 389 if (ecdheCredentials != null) { 390 ecdhePossession = (ECDHEPossession)poss; 391 break; 392 } 393 } 394 395 if (ecdhePossession == null || ecdheCredentials == null) { 396 context.conContext.fatal(Alert.HANDSHAKE_FAILURE, 397 "No sufficient ECDHE key agreement parameters negotiated"); 398 } 399 400 return new KAKeyDerivation("ECDH", context, 401 ecdhePossession.privateKey, ecdheCredentials.popPublicKey); 402 } 403 } 404 }