1 /* 2 * Copyright (c) 2015, 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.nio.ByteBuffer; 30 import java.security.CryptoPrimitive; 31 import java.security.InvalidAlgorithmParameterException; 32 import java.security.InvalidKeyException; 33 import java.security.KeyFactory; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.Signature; 36 import java.security.SignatureException; 37 import java.security.interfaces.ECPublicKey; 38 import java.security.spec.ECParameterSpec; 39 import java.security.spec.ECPoint; 40 import java.security.spec.ECPublicKeySpec; 41 import java.security.spec.InvalidKeySpecException; 42 import java.text.MessageFormat; 43 import java.util.EnumSet; 44 import java.util.Locale; 45 import sun.security.ssl.ECDHKeyExchange.ECDHECredentials; 46 import sun.security.ssl.ECDHKeyExchange.ECDHEPossession; 47 import sun.security.ssl.SSLHandshake.HandshakeMessage; 48 import sun.security.ssl.SupportedGroupsExtension.NamedGroup; 49 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups; 50 import sun.security.ssl.X509Authentication.X509Credentials; 51 import sun.security.ssl.X509Authentication.X509Possession; 52 import sun.security.util.HexDumpEncoder; 53 54 /** 55 * Pack of the ServerKeyExchange handshake message. 56 */ 57 final class ECDHServerKeyExchange { 58 static final SSLConsumer ecdheHandshakeConsumer = 59 new ECDHServerKeyExchangeConsumer(); 60 static final HandshakeProducer ecdheHandshakeProducer = 61 new ECDHServerKeyExchangeProducer(); 62 63 /** 64 * The ECDH ServerKeyExchange handshake message. 65 */ 66 private static final 67 class ECDHServerKeyExchangeMessage extends HandshakeMessage { 68 private static final byte CURVE_NAMED_CURVE = (byte)0x03; 69 70 // id of the named curve 71 private final NamedGroup namedGroup; 72 73 // encoded public point 74 private final byte[] publicPoint; 75 76 // signature bytes, or null if anonymous 77 private final byte[] paramsSignature; 78 79 // public key object encapsulated in this message 80 private final ECPublicKey publicKey; 81 82 private final boolean useExplicitSigAlgorithm; 83 84 // the signature algorithm used by this ServerKeyExchange message 85 private final SignatureScheme signatureScheme; 86 87 ECDHServerKeyExchangeMessage( 88 HandshakeContext handshakeContext) throws IOException { 89 super(handshakeContext); 90 91 // This happens in server side only. 92 ServerHandshakeContext shc = 93 (ServerHandshakeContext)handshakeContext; 94 95 ECDHEPossession ecdhePossession = null; 96 X509Possession x509Possession = null; 97 for (SSLPossession possession : shc.handshakePossessions) { 98 if (possession instanceof ECDHEPossession) { 99 ecdhePossession = (ECDHEPossession)possession; 100 if (x509Possession != null) { 101 break; 102 } 103 } else if (possession instanceof X509Possession) { 104 x509Possession = (X509Possession)possession; 105 if (ecdhePossession != null) { 106 break; 107 } 108 } 109 } 110 111 if (ecdhePossession == null) { 112 // unlikely 113 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 114 "No ECDHE credentials negotiated for server key exchange"); 115 } 116 117 publicKey = ecdhePossession.publicKey; 118 ECParameterSpec params = publicKey.getParams(); 119 ECPoint point = publicKey.getW(); 120 publicPoint = JsseJce.encodePoint(point, params.getCurve()); 121 122 this.namedGroup = NamedGroup.valueOf(params); 123 if ((namedGroup == null) || (namedGroup.oid == null) ) { 124 // unlikely 125 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 126 "Unnamed EC parameter spec: " + params); 127 } 128 129 if (x509Possession == null) { 130 // anonymous, no authentication, no signature 131 paramsSignature = null; 132 signatureScheme = null; 133 useExplicitSigAlgorithm = false; 134 } else { 135 useExplicitSigAlgorithm = 136 shc.negotiatedProtocol.useTLS12PlusSpec(); 137 Signature signer = null; 138 if (useExplicitSigAlgorithm) { 139 signatureScheme = SignatureScheme.getPreferableAlgorithm( 140 shc.peerRequestedSignatureSchemes, 141 x509Possession.popPrivateKey, 142 shc.negotiatedProtocol); 143 if (signatureScheme == null) { 144 // Unlikely, the credentials generator should have 145 // selected the preferable signature algorithm properly. 146 shc.conContext.fatal(Alert.INTERNAL_ERROR, 147 "No preferred signature algorithm for " + 148 x509Possession.popPrivateKey.getAlgorithm() + 149 " key"); 150 } 151 try { 152 signer = signatureScheme.getSignature(); 153 } catch (NoSuchAlgorithmException | 154 InvalidAlgorithmParameterException nsae) { 155 shc.conContext.fatal(Alert.INTERNAL_ERROR, 156 "Unsupported signature algorithm: " + 157 signatureScheme.name, nsae); 158 } 159 } else { 160 signatureScheme = null; 161 try { 162 signer = getSignature( 163 x509Possession.popPrivateKey.getAlgorithm()); 164 } catch (NoSuchAlgorithmException e) { 165 shc.conContext.fatal(Alert.INTERNAL_ERROR, 166 "Unsupported signature algorithm: " + 167 x509Possession.popPrivateKey.getAlgorithm(), e); 168 } 169 } 170 171 byte[] signature = null; 172 try { 173 signer.initSign(x509Possession.popPrivateKey); 174 updateSignature(signer, shc.clientHelloRandom.randomBytes, 175 shc.serverHelloRandom.randomBytes, 176 namedGroup.id, publicPoint); 177 signature = signer.sign(); 178 } catch (InvalidKeyException | SignatureException ex) { 179 shc.conContext.fatal(Alert.INTERNAL_ERROR, 180 "Failed to sign ecdhe parameters: " + 181 x509Possession.popPrivateKey.getAlgorithm(), ex); 182 } 183 paramsSignature = signature; 184 } 185 } 186 187 ECDHServerKeyExchangeMessage(HandshakeContext handshakeContext, 188 ByteBuffer m) throws IOException { 189 super(handshakeContext); 190 191 // This happens in client side only. 192 ClientHandshakeContext chc = 193 (ClientHandshakeContext)handshakeContext; 194 195 byte curveType = (byte)Record.getInt8(m); 196 if (curveType != CURVE_NAMED_CURVE) { 197 // Unlikely as only the named curves should be negotiated. 198 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 199 "Unsupported ECCurveType: " + curveType); 200 } 201 202 int namedGroupId = Record.getInt16(m); 203 this.namedGroup = NamedGroup.valueOf(namedGroupId); 204 if (namedGroup == null) { 205 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 206 "Unknown named group ID: " + namedGroupId); 207 } 208 209 if (!SupportedGroups.isSupported(namedGroup)) { 210 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 211 "Unsupported named group: " + namedGroup); 212 } 213 214 if (namedGroup.oid == null) { 215 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 216 "Unknown named EC curve: " + namedGroup); 217 } 218 219 ECParameterSpec parameters = 220 JsseJce.getECParameterSpec(namedGroup.oid); 221 if (parameters == null) { 222 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 223 "No supported EC parameter: " + namedGroup); 224 } 225 226 publicPoint = Record.getBytes8(m); 227 if (publicPoint.length == 0) { 228 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 229 "Insufficient ECPoint data: " + namedGroup); 230 } 231 232 ECPublicKey ecPublicKey = null; 233 try { 234 ECPoint point = 235 JsseJce.decodePoint(publicPoint, parameters.getCurve()); 236 KeyFactory factory = JsseJce.getKeyFactory("EC"); 237 ecPublicKey = (ECPublicKey)factory.generatePublic( 238 new ECPublicKeySpec(point, parameters)); 239 } catch (NoSuchAlgorithmException | 240 InvalidKeySpecException | IOException ex) { 241 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 242 "Invalid ECPoint: " + namedGroup, ex); 243 } 244 245 publicKey = ecPublicKey; 246 247 X509Credentials x509Credentials = null; 248 for (SSLCredentials cd : chc.handshakeCredentials) { 249 if (cd instanceof X509Credentials) { 250 x509Credentials = (X509Credentials)cd; 251 break; 252 } 253 } 254 255 if (x509Credentials == null) { 256 // anonymous, no authentication, no signature 257 if (m.hasRemaining()) { 258 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 259 "Invalid DH ServerKeyExchange: unknown extra data"); 260 } 261 this.signatureScheme = null; 262 this.paramsSignature = null; 263 this.useExplicitSigAlgorithm = false; 264 265 return; 266 } 267 268 this.useExplicitSigAlgorithm = 269 chc.negotiatedProtocol.useTLS12PlusSpec(); 270 if (useExplicitSigAlgorithm) { 271 int ssid = Record.getInt16(m); 272 signatureScheme = SignatureScheme.valueOf(ssid); 273 if (signatureScheme == null) { 274 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 275 "Invalid signature algorithm (" + ssid + 276 ") used in ECDH ServerKeyExchange handshake message"); 277 } 278 279 if (!chc.localSupportedSignAlgs.contains(signatureScheme)) { 280 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 281 "Unsupported signature algorithm (" + 282 signatureScheme.name + 283 ") used in ECDH ServerKeyExchange handshake message"); 284 } 285 } else { 286 signatureScheme = null; 287 } 288 289 // read and verify the signature 290 paramsSignature = Record.getBytes16(m); 291 Signature signer; 292 if (useExplicitSigAlgorithm) { 293 try { 294 signer = signatureScheme.getSignature(); 295 } catch (NoSuchAlgorithmException | 296 InvalidAlgorithmParameterException nsae) { 297 chc.conContext.fatal(Alert.INTERNAL_ERROR, 298 "Unsupported signature algorithm: " + 299 signatureScheme.name, nsae); 300 301 return; // make the compiler happe 302 } 303 } else { 304 try { 305 signer = getSignature( 306 x509Credentials.popPublicKey.getAlgorithm()); 307 } catch (NoSuchAlgorithmException e) { 308 chc.conContext.fatal(Alert.INTERNAL_ERROR, 309 "Unsupported signature algorithm: " + 310 x509Credentials.popPublicKey.getAlgorithm(), e); 311 312 return; // make the compiler happe 313 } 314 } 315 316 try { 317 signer.initVerify(x509Credentials.popPublicKey); 318 updateSignature(signer, 319 chc.clientHelloRandom.randomBytes, 320 chc.serverHelloRandom.randomBytes, 321 namedGroup.id, publicPoint); 322 323 if (!signer.verify(paramsSignature)) { 324 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 325 "Invalid ECDH ServerKeyExchange signature"); 326 } 327 } catch (InvalidKeyException | SignatureException ex) { 328 chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, 329 "Cannot verify ECDH ServerKeyExchange signature", ex); 330 } 331 } 332 333 @Override 334 public SSLHandshake handshakeType() { 335 return SSLHandshake.SERVER_KEY_EXCHANGE; 336 } 337 338 @Override 339 public int messageLength() { 340 int sigLen = 0; 341 if (paramsSignature != null) { 342 sigLen = 2 + paramsSignature.length; 343 if (useExplicitSigAlgorithm) { 344 sigLen += SignatureScheme.sizeInRecord(); 345 } 346 } 347 348 return 4 + publicPoint.length + sigLen; 349 } 350 351 @Override 352 public void send(HandshakeOutStream hos) throws IOException { 353 hos.putInt8(CURVE_NAMED_CURVE); 354 hos.putInt16(namedGroup.id); 355 hos.putBytes8(publicPoint); 356 if (paramsSignature != null) { 357 if (useExplicitSigAlgorithm) { 358 hos.putInt16(signatureScheme.id); 359 } 360 361 hos.putBytes16(paramsSignature); 362 } 363 } 364 365 @Override 366 public String toString() { 367 if (useExplicitSigAlgorithm) { 368 MessageFormat messageFormat = new MessageFormat( 369 "\"ECDH ServerKeyExchange\": '{'\n" + 370 " \"parameters\": '{'\n" + 371 " \"named group\": \"{0}\"\n" + 372 " \"ecdh public\": '{'\n" + 373 "{1}\n" + 374 " '}',\n" + 375 " '}',\n" + 376 " \"digital signature\": '{'\n" + 377 " \"signature algorithm\": \"{2}\"\n" + 378 " \"signature\": '{'\n" + 379 "{3}\n" + 380 " '}',\n" + 381 " '}'\n" + 382 "'}'", 383 Locale.ENGLISH); 384 385 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 386 Object[] messageFields = { 387 namedGroup.name, 388 Utilities.indent( 389 hexEncoder.encodeBuffer(publicPoint), " "), 390 signatureScheme.name, 391 Utilities.indent( 392 hexEncoder.encodeBuffer(paramsSignature), " ") 393 }; 394 return messageFormat.format(messageFields); 395 } else if (paramsSignature != null) { 396 MessageFormat messageFormat = new MessageFormat( 397 "\"ECDH ServerKeyExchange\": '{'\n" + 398 " \"parameters\": '{'\n" + 399 " \"named group\": \"{0}\"\n" + 400 " \"ecdh public\": '{'\n" + 401 "{1}\n" + 402 " '}',\n" + 403 " '}',\n" + 404 " \"signature\": '{'\n" + 405 "{2}\n" + 406 " '}'\n" + 407 "'}'", 408 Locale.ENGLISH); 409 410 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 411 Object[] messageFields = { 412 namedGroup.name, 413 Utilities.indent( 414 hexEncoder.encodeBuffer(publicPoint), " "), 415 Utilities.indent( 416 hexEncoder.encodeBuffer(paramsSignature), " ") 417 }; 418 419 return messageFormat.format(messageFields); 420 } else { // anonymous 421 MessageFormat messageFormat = new MessageFormat( 422 "\"ECDH ServerKeyExchange\": '{'\n" + 423 " \"parameters\": '{'\n" + 424 " \"named group\": \"{0}\"\n" + 425 " \"ecdh public\": '{'\n" + 426 "{1}\n" + 427 " '}',\n" + 428 " '}'\n" + 429 "'}'", 430 Locale.ENGLISH); 431 432 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 433 Object[] messageFields = { 434 namedGroup.name, 435 Utilities.indent( 436 hexEncoder.encodeBuffer(publicPoint), " "), 437 }; 438 439 return messageFormat.format(messageFields); 440 } 441 } 442 443 private static Signature getSignature(String keyAlgorithm) 444 throws NoSuchAlgorithmException { 445 switch (keyAlgorithm) { 446 case "EC": 447 return JsseJce.getSignature(JsseJce.SIGNATURE_ECDSA); 448 case "RSA": 449 return RSASignature.getInstance(); 450 default: 451 throw new NoSuchAlgorithmException( 452 "neither an RSA or a EC key : " + keyAlgorithm); 453 } 454 } 455 456 private static void updateSignature(Signature sig, 457 byte[] clntNonce, byte[] svrNonce, int namedGroupId, 458 byte[] publicPoint) throws SignatureException { 459 sig.update(clntNonce); 460 sig.update(svrNonce); 461 462 sig.update(CURVE_NAMED_CURVE); 463 sig.update((byte)((namedGroupId >> 8) & 0xFF)); 464 sig.update((byte)(namedGroupId & 0xFF)); 465 sig.update((byte)publicPoint.length); 466 sig.update(publicPoint); 467 } 468 } 469 470 /** 471 * The ECDH "ServerKeyExchange" handshake message producer. 472 */ 473 private static final 474 class ECDHServerKeyExchangeProducer implements HandshakeProducer { 475 // Prevent instantiation of this class. 476 private ECDHServerKeyExchangeProducer() { 477 // blank 478 } 479 480 @Override 481 public byte[] produce(ConnectionContext context, 482 HandshakeMessage message) throws IOException { 483 // The producing happens in server side only. 484 ServerHandshakeContext shc = (ServerHandshakeContext)context; 485 ECDHServerKeyExchangeMessage skem = 486 new ECDHServerKeyExchangeMessage(shc); 487 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 488 SSLLogger.fine( 489 "Produced ECDH ServerKeyExchange handshake message", skem); 490 } 491 492 // Output the handshake message. 493 skem.write(shc.handshakeOutput); 494 shc.handshakeOutput.flush(); 495 496 // The handshake message has been delivered. 497 return null; 498 } 499 } 500 501 /** 502 * The ECDH "ServerKeyExchange" handshake message consumer. 503 */ 504 private static final 505 class ECDHServerKeyExchangeConsumer implements SSLConsumer { 506 // Prevent instantiation of this class. 507 private ECDHServerKeyExchangeConsumer() { 508 // blank 509 } 510 511 @Override 512 public void consume(ConnectionContext context, 513 ByteBuffer message) throws IOException { 514 // The consuming happens in client side only. 515 ClientHandshakeContext chc = (ClientHandshakeContext)context; 516 517 ECDHServerKeyExchangeMessage skem = 518 new ECDHServerKeyExchangeMessage(chc, message); 519 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 520 SSLLogger.fine( 521 "Consuming ECDH ServerKeyExchange handshake message", skem); 522 } 523 524 // 525 // validate 526 // 527 // check constraints of EC PublicKey 528 if (!chc.algorithmConstraints.permits( 529 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 530 skem.publicKey)) { 531 chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, 532 "ECDH ServerKeyExchange does not comply " + 533 "to algorithm constraints"); 534 } 535 536 // 537 // update 538 // 539 chc.handshakeCredentials.add( 540 new ECDHECredentials(skem.publicKey, skem.namedGroup)); 541 542 // 543 // produce 544 // 545 // Need no new handshake message producers here. 546 } 547 } 548 } 549