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.AlgorithmConstraints; 31 import java.security.AlgorithmParameters; 32 import java.security.CryptoPrimitive; 33 import java.security.spec.ECGenParameterSpec; 34 import java.security.spec.InvalidParameterSpecException; 35 import java.text.MessageFormat; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.EnumSet; 39 import java.util.LinkedList; 40 import java.util.List; 41 import java.util.Locale; 42 import javax.net.ssl.SSLProtocolException; 43 import sun.security.action.GetPropertyAction; 44 import sun.security.ssl.NamedGroup.NamedGroupType; 45 import static sun.security.ssl.SSLExtension.CH_SUPPORTED_GROUPS; 46 import static sun.security.ssl.SSLExtension.EE_SUPPORTED_GROUPS; 47 import sun.security.ssl.SSLExtension.ExtensionConsumer; 48 import sun.security.ssl.SSLExtension.SSLExtensionSpec; 49 import sun.security.ssl.SSLHandshake.HandshakeMessage; 50 51 /** 52 * Pack of the "supported_groups" extensions [RFC 4492/7919]. 53 */ 54 final class SupportedGroupsExtension { 55 static final HandshakeProducer chNetworkProducer = 56 new CHSupportedGroupsProducer(); 57 static final ExtensionConsumer chOnLoadConsumer = 58 new CHSupportedGroupsConsumer(); 59 static final HandshakeAbsence chOnTradAbsence = 60 new CHSupportedGroupsOnTradeAbsence(); 61 static final SSLStringizer sgsStringizer = 62 new SupportedGroupsStringizer(); 63 64 static final HandshakeProducer eeNetworkProducer = 65 new EESupportedGroupsProducer(); 66 static final ExtensionConsumer eeOnLoadConsumer = 67 new EESupportedGroupsConsumer(); 68 69 /** 70 * The "supported_groups" extension. 71 */ 72 static final class SupportedGroupsSpec implements SSLExtensionSpec { 73 final int[] namedGroupsIds; 74 75 private SupportedGroupsSpec(int[] namedGroupsIds) { 76 this.namedGroupsIds = namedGroupsIds; 77 } 78 79 private SupportedGroupsSpec(List<NamedGroup> namedGroups) { 80 this.namedGroupsIds = new int[namedGroups.size()]; 81 int i = 0; 82 for (NamedGroup ng : namedGroups) { 83 namedGroupsIds[i++] = ng.id; 84 } 85 } 86 87 private SupportedGroupsSpec(ByteBuffer m) throws IOException { 88 if (m.remaining() < 2) { // 2: the length of the list 89 throw new SSLProtocolException( 90 "Invalid supported_groups extension: insufficient data"); 91 } 92 93 byte[] ngs = Record.getBytes16(m); 94 if (m.hasRemaining()) { 95 throw new SSLProtocolException( 96 "Invalid supported_groups extension: unknown extra data"); 97 } 98 99 if ((ngs == null) || (ngs.length == 0) || (ngs.length % 2 != 0)) { 100 throw new SSLProtocolException( 101 "Invalid supported_groups extension: incomplete data"); 102 } 103 104 int[] ids = new int[ngs.length / 2]; 105 for (int i = 0, j = 0; i < ngs.length;) { 106 ids[j++] = ((ngs[i++] & 0xFF) << 8) | (ngs[i++] & 0xFF); 107 } 108 109 this.namedGroupsIds = ids; 110 } 111 112 @Override 113 public String toString() { 114 MessageFormat messageFormat = new MessageFormat( 115 "\"versions\": '['{0}']'", Locale.ENGLISH); 116 117 if (namedGroupsIds == null || namedGroupsIds.length == 0) { 118 Object[] messageFields = { 119 "<no supported named group specified>" 120 }; 121 return messageFormat.format(messageFields); 122 } else { 123 StringBuilder builder = new StringBuilder(512); 124 boolean isFirst = true; 125 for (int ngid : namedGroupsIds) { 126 if (isFirst) { 127 isFirst = false; 128 } else { 129 builder.append(", "); 130 } 131 132 builder.append(NamedGroup.nameOf(ngid)); 133 } 134 135 Object[] messageFields = { 136 builder.toString() 137 }; 138 139 return messageFormat.format(messageFields); 140 } 141 } 142 } 143 144 private static final 145 class SupportedGroupsStringizer implements SSLStringizer { 146 @Override 147 public String toString(ByteBuffer buffer) { 148 try { 149 return (new SupportedGroupsSpec(buffer)).toString(); 150 } catch (IOException ioe) { 151 // For debug logging only, so please swallow exceptions. 152 return ioe.getMessage(); 153 } 154 } 155 } 156 157 static class SupportedGroups { 158 // To switch off the supported_groups extension for DHE cipher suite. 159 static final boolean enableFFDHE = 160 Utilities.getBooleanProperty("jsse.enableFFDHE", true); 161 162 // the supported named groups 163 static final NamedGroup[] supportedNamedGroups; 164 165 static { 166 boolean requireFips = SunJSSE.isFIPS(); 167 168 // The value of the System Property defines a list of enabled named 169 // groups in preference order, separated with comma. For example: 170 // 171 // jdk.tls.namedGroups="secp521r1, secp256r1, ffdhe2048" 172 // 173 // If the System Property is not defined or the value is empty, the 174 // default groups and preferences will be used. 175 String property = GetPropertyAction 176 .privilegedGetProperty("jdk.tls.namedGroups"); 177 if (property != null && !property.isEmpty()) { 178 // remove double quote marks from beginning/end of the property 179 if (property.length() > 1 && property.charAt(0) == '"' && 180 property.charAt(property.length() - 1) == '"') { 181 property = property.substring(1, property.length() - 1); 182 } 183 } 184 185 ArrayList<NamedGroup> groupList; 186 if (property != null && !property.isEmpty()) { 187 String[] groups = property.split(","); 188 groupList = new ArrayList<>(groups.length); 189 for (String group : groups) { 190 group = group.trim(); 191 if (!group.isEmpty()) { 192 NamedGroup namedGroup = NamedGroup.nameOf(group); 193 if (namedGroup != null && 194 (!requireFips || namedGroup.isFips)) { 195 if (isAvailableGroup(namedGroup)) { 196 groupList.add(namedGroup); 197 } 198 } // ignore unknown groups 199 } 200 } 201 202 if (groupList.isEmpty()) { 203 throw new IllegalArgumentException( 204 "System property jdk.tls.namedGroups(" + 205 property + ") contains no supported named groups"); 206 } 207 } else { // default groups 208 NamedGroup[] groups; 209 if (requireFips) { 210 groups = new NamedGroup[] { 211 // only NIST curves in FIPS mode 212 NamedGroup.SECP256_R1, 213 NamedGroup.SECP384_R1, 214 NamedGroup.SECP521_R1, 215 216 // FFDHE (RFC 7919) 217 NamedGroup.FFDHE_2048, 218 NamedGroup.FFDHE_3072, 219 NamedGroup.FFDHE_4096, 220 NamedGroup.FFDHE_6144, 221 NamedGroup.FFDHE_8192, 222 }; 223 } else { 224 groups = new NamedGroup[] { 225 226 // Primary XDH (RFC 7748) curves 227 NamedGroup.X25519, 228 229 // Primary NIST curves (e.g. used in TLSv1.3) 230 NamedGroup.SECP256_R1, 231 NamedGroup.SECP384_R1, 232 NamedGroup.SECP521_R1, 233 234 // Secondary XDH curves 235 NamedGroup.X448, 236 237 // Secondary NIST curves 238 239 // FFDHE 2048 240 NamedGroup.FFDHE_2048, 241 NamedGroup.FFDHE_3072, 242 NamedGroup.FFDHE_4096, 243 NamedGroup.FFDHE_6144, 244 NamedGroup.FFDHE_8192, 245 }; 246 } 247 248 groupList = new ArrayList<>(groups.length); 249 for (NamedGroup group : groups) { 250 if (isAvailableGroup(group)) { 251 groupList.add(group); 252 } 253 } 254 255 if (groupList.isEmpty() && 256 SSLLogger.isOn && SSLLogger.isOn("ssl")) { 257 SSLLogger.warning("No default named groups"); 258 } 259 } 260 261 supportedNamedGroups = new NamedGroup[groupList.size()]; 262 int i = 0; 263 for (NamedGroup namedGroup : groupList) { 264 supportedNamedGroups[i++] = namedGroup; 265 } 266 } 267 268 // check whether the group is supported by the underlying providers 269 private static boolean isAvailableGroup(NamedGroup namedGroup) { 270 return namedGroup.isAvailableGroup(); 271 } 272 273 static ECGenParameterSpec getECGenParamSpec(NamedGroup ng) { 274 if (ng.type != NamedGroupType.NAMED_GROUP_ECDHE) { 275 throw new RuntimeException( 276 "Not a named EC group: " + ng); 277 } 278 279 // parameters are non-null 280 AlgorithmParameters params = ng.getParameters(); 281 try { 282 return params.getParameterSpec(ECGenParameterSpec.class); 283 } catch (InvalidParameterSpecException ipse) { 284 // should be unlikely 285 return new ECGenParameterSpec(ng.oid); 286 } 287 } 288 289 static AlgorithmParameters getParameters(NamedGroup ng) { 290 return ng.getParameters(); 291 } 292 293 // Is there any supported group permitted by the constraints? 294 static boolean isActivatable( 295 AlgorithmConstraints constraints, NamedGroupType type) { 296 297 boolean hasFFDHEGroups = false; 298 for (NamedGroup namedGroup : supportedNamedGroups) { 299 if (namedGroup.type == type) { 300 if (constraints.permits( 301 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 302 namedGroup.algorithm, 303 getParameters(namedGroup))) { 304 305 return true; 306 } 307 308 if (!hasFFDHEGroups && 309 (type == NamedGroupType.NAMED_GROUP_FFDHE)) { 310 hasFFDHEGroups = true; 311 } 312 } 313 } 314 315 // For compatibility, if no FFDHE groups are defined, the non-FFDHE 316 // compatible mode (using DHE cipher suite without FFDHE extension) 317 // is allowed. 318 // 319 // Note that the constraints checking on DHE parameters will be 320 // performed during key exchanging in a handshake. 321 return !hasFFDHEGroups && type == NamedGroupType.NAMED_GROUP_FFDHE; 322 } 323 324 // Is the named group permitted by the constraints? 325 static boolean isActivatable( 326 AlgorithmConstraints constraints, NamedGroup namedGroup) { 327 if (!isSupported(namedGroup)) { 328 return false; 329 } 330 331 return constraints.permits( 332 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 333 namedGroup.algorithm, 334 getParameters(namedGroup)); 335 } 336 337 // Is the named group supported? 338 static boolean isSupported(NamedGroup namedGroup) { 339 for (NamedGroup group : supportedNamedGroups) { 340 if (namedGroup.id == group.id) { 341 return true; 342 } 343 } 344 345 return false; 346 } 347 348 static NamedGroup getPreferredGroup( 349 ProtocolVersion negotiatedProtocol, 350 AlgorithmConstraints constraints, NamedGroupType[] types, 351 List<NamedGroup> requestedNamedGroups) { 352 for (NamedGroup namedGroup : requestedNamedGroups) { 353 if ((NamedGroupType.arrayContains(types, namedGroup.type)) && 354 namedGroup.isAvailable(negotiatedProtocol) && 355 isSupported(namedGroup) && 356 constraints.permits( 357 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 358 namedGroup.algorithm, 359 getParameters(namedGroup))) { 360 return namedGroup; 361 } 362 } 363 364 return null; 365 } 366 367 static NamedGroup getPreferredGroup( 368 ProtocolVersion negotiatedProtocol, 369 AlgorithmConstraints constraints, NamedGroupType[] types) { 370 for (NamedGroup namedGroup : supportedNamedGroups) { 371 if ((NamedGroupType.arrayContains(types, namedGroup.type)) && 372 namedGroup.isAvailable(negotiatedProtocol) && 373 constraints.permits( 374 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 375 namedGroup.algorithm, 376 getParameters(namedGroup))) { 377 return namedGroup; 378 } 379 } 380 381 return null; 382 } 383 } 384 385 /** 386 * Network data producer of a "supported_groups" extension in 387 * the ClientHello handshake message. 388 */ 389 private static final class CHSupportedGroupsProducer 390 extends SupportedGroups implements HandshakeProducer { 391 // Prevent instantiation of this class. 392 private CHSupportedGroupsProducer() { 393 // blank 394 } 395 396 @Override 397 public byte[] produce(ConnectionContext context, 398 HandshakeMessage message) throws IOException { 399 // The producing happens in client side only. 400 ClientHandshakeContext chc = (ClientHandshakeContext)context; 401 402 // Is it a supported and enabled extension? 403 if (!chc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { 404 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 405 SSLLogger.fine( 406 "Ignore unavailable supported_groups extension"); 407 } 408 return null; 409 } 410 411 // Produce the extension. 412 ArrayList<NamedGroup> namedGroups = 413 new ArrayList<>(SupportedGroups.supportedNamedGroups.length); 414 for (NamedGroup ng : SupportedGroups.supportedNamedGroups) { 415 if ((!SupportedGroups.enableFFDHE) && 416 (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) { 417 continue; 418 } 419 420 if (ng.isAvailable(chc.activeProtocols) && 421 ng.isSupported(chc.activeCipherSuites) && 422 chc.algorithmConstraints.permits( 423 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 424 ng.algorithm, getParameters(ng))) { 425 namedGroups.add(ng); 426 } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 427 SSLLogger.fine( 428 "Ignore inactive or disabled named group: " + ng.name); 429 } 430 } 431 432 if (namedGroups.isEmpty()) { 433 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 434 SSLLogger.warning("no available named group"); 435 } 436 437 return null; 438 } 439 440 int vectorLen = namedGroups.size() << 1; 441 byte[] extData = new byte[vectorLen + 2]; 442 ByteBuffer m = ByteBuffer.wrap(extData); 443 Record.putInt16(m, vectorLen); 444 for (NamedGroup namedGroup : namedGroups) { 445 Record.putInt16(m, namedGroup.id); 446 } 447 448 // Update the context. 449 chc.clientRequestedNamedGroups = 450 Collections.<NamedGroup>unmodifiableList(namedGroups); 451 chc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, 452 new SupportedGroupsSpec(namedGroups)); 453 454 return extData; 455 } 456 } 457 458 /** 459 * Network data producer of a "supported_groups" extension in 460 * the ClientHello handshake message. 461 */ 462 private static final 463 class CHSupportedGroupsConsumer implements ExtensionConsumer { 464 // Prevent instantiation of this class. 465 private CHSupportedGroupsConsumer() { 466 // blank 467 } 468 469 @Override 470 public void consume(ConnectionContext context, 471 HandshakeMessage message, ByteBuffer buffer) throws IOException { 472 // The consuming happens in server side only. 473 ServerHandshakeContext shc = (ServerHandshakeContext)context; 474 475 // Is it a supported and enabled extension? 476 if (!shc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { 477 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 478 SSLLogger.fine( 479 "Ignore unavailable supported_groups extension"); 480 } 481 return; // ignore the extension 482 } 483 484 // Parse the extension. 485 SupportedGroupsSpec spec; 486 try { 487 spec = new SupportedGroupsSpec(buffer); 488 } catch (IOException ioe) { 489 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 490 } 491 492 // Update the context. 493 List<NamedGroup> knownNamedGroups = new LinkedList<>(); 494 for (int id : spec.namedGroupsIds) { 495 NamedGroup ng = NamedGroup.valueOf(id); 496 if (ng != null) { 497 knownNamedGroups.add(ng); 498 } 499 } 500 501 shc.clientRequestedNamedGroups = knownNamedGroups; 502 shc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, spec); 503 504 // No impact on session resumption. 505 } 506 } 507 508 /** 509 * The absence processing if the extension is not present in 510 * a ClientHello handshake message. 511 */ 512 private static final class CHSupportedGroupsOnTradeAbsence 513 implements HandshakeAbsence { 514 @Override 515 public void absent(ConnectionContext context, 516 HandshakeMessage message) throws IOException { 517 // The producing happens in server side only. 518 ServerHandshakeContext shc = (ServerHandshakeContext)context; 519 520 // A client is considered to be attempting to negotiate using this 521 // specification if the ClientHello contains a "supported_versions" 522 // extension with 0x0304 contained in its body. Such a ClientHello 523 // message MUST meet the following requirements: 524 // - If containing a "supported_groups" extension, it MUST also 525 // contain a "key_share" extension, and vice versa. An empty 526 // KeyShare.client_shares vector is permitted. 527 if (shc.negotiatedProtocol.useTLS13PlusSpec() && 528 shc.handshakeExtensions.containsKey( 529 SSLExtension.CH_KEY_SHARE)) { 530 throw shc.conContext.fatal(Alert.MISSING_EXTENSION, 531 "No supported_groups extension to work with " + 532 "the key_share extension"); 533 } 534 } 535 } 536 537 /** 538 * Network data producer of a "supported_groups" extension in 539 * the EncryptedExtensions handshake message. 540 */ 541 private static final class EESupportedGroupsProducer 542 extends SupportedGroups implements HandshakeProducer { 543 544 // Prevent instantiation of this class. 545 private EESupportedGroupsProducer() { 546 // blank 547 } 548 549 @Override 550 public byte[] produce(ConnectionContext context, 551 HandshakeMessage message) throws IOException { 552 // The producing happens in server side only. 553 ServerHandshakeContext shc = (ServerHandshakeContext)context; 554 555 // Is it a supported and enabled extension? 556 if (!shc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { 557 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 558 SSLLogger.fine( 559 "Ignore unavailable supported_groups extension"); 560 } 561 return null; 562 } 563 564 // Produce the extension. 565 // 566 // Contains all groups the server supports, regardless of whether 567 // they are currently supported by the client. 568 ArrayList<NamedGroup> namedGroups = new ArrayList<>( 569 SupportedGroups.supportedNamedGroups.length); 570 for (NamedGroup ng : SupportedGroups.supportedNamedGroups) { 571 if ((!SupportedGroups.enableFFDHE) && 572 (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) { 573 continue; 574 } 575 576 if (ng.isAvailable(shc.activeProtocols) && 577 ng.isSupported(shc.activeCipherSuites) && 578 shc.algorithmConstraints.permits( 579 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), 580 ng.algorithm, getParameters(ng))) { 581 namedGroups.add(ng); 582 } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 583 SSLLogger.fine( 584 "Ignore inactive or disabled named group: " + ng.name); 585 } 586 } 587 588 if (namedGroups.isEmpty()) { 589 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 590 SSLLogger.warning("no available named group"); 591 } 592 593 return null; 594 } 595 596 int vectorLen = namedGroups.size() << 1; 597 byte[] extData = new byte[vectorLen + 2]; 598 ByteBuffer m = ByteBuffer.wrap(extData); 599 Record.putInt16(m, vectorLen); 600 for (NamedGroup namedGroup : namedGroups) { 601 Record.putInt16(m, namedGroup.id); 602 } 603 604 // Update the context. 605 shc.conContext.serverRequestedNamedGroups = 606 Collections.<NamedGroup>unmodifiableList(namedGroups); 607 SupportedGroupsSpec spec = new SupportedGroupsSpec(namedGroups); 608 shc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec); 609 610 return extData; 611 } 612 } 613 614 private static final 615 class EESupportedGroupsConsumer implements ExtensionConsumer { 616 // Prevent instantiation of this class. 617 private EESupportedGroupsConsumer() { 618 // blank 619 } 620 621 @Override 622 public void consume(ConnectionContext context, 623 HandshakeMessage message, ByteBuffer buffer) throws IOException { 624 // The consuming happens in client side only. 625 ClientHandshakeContext chc = (ClientHandshakeContext)context; 626 627 // Is it a supported and enabled extension? 628 if (!chc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { 629 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 630 SSLLogger.fine( 631 "Ignore unavailable supported_groups extension"); 632 } 633 return; // ignore the extension 634 } 635 636 // Parse the extension. 637 SupportedGroupsSpec spec; 638 try { 639 spec = new SupportedGroupsSpec(buffer); 640 } catch (IOException ioe) { 641 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 642 } 643 644 // Update the context. 645 List<NamedGroup> knownNamedGroups = 646 new ArrayList<>(spec.namedGroupsIds.length); 647 for (int id : spec.namedGroupsIds) { 648 NamedGroup ng = NamedGroup.valueOf(id); 649 if (ng != null) { 650 knownNamedGroups.add(ng); 651 } 652 } 653 654 chc.conContext.serverRequestedNamedGroups = knownNamedGroups; 655 chc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec); 656 657 // No impact on session resumption. 658 } 659 } 660 }