1 /* 2 * Copyright 2006-2008 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package sun.security.ssl; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.util.*; 31 32 import java.security.spec.ECParameterSpec; 33 34 import javax.net.ssl.SSLProtocolException; 35 36 37 /** 38 * This file contains all the classes relevant to TLS Extensions for the 39 * ClientHello and ServerHello messages. The extension mechanism and 40 * several extensions are defined in RFC 3546. Additional extensions are 41 * defined in the ECC RFC 4492. 42 * 43 * Currently, only the two ECC extensions are fully supported. 44 * 45 * The classes contained in this file are: 46 * . HelloExtensions: a List of extensions as used in the client hello 47 * and server hello messages. 48 * . ExtensionType: an enum style class for the extension type 49 * . HelloExtension: abstract base class for all extensions. All subclasses 50 * must be immutable. 51 * 52 * . UnknownExtension: used to represent all parsed extensions that we do not 53 * explicitly support. 54 * . ServerNameExtension: partially implemented server_name extension. 55 * . SupportedEllipticCurvesExtension: the ECC supported curves extension. 56 * . SupportedEllipticPointFormatsExtension: the ECC supported point formats 57 * (compressed/uncompressed) extension. 58 * 59 * @since 1.6 60 * @author Andreas Sterbenz 61 */ 62 final class HelloExtensions { 63 64 private List<HelloExtension> extensions; 65 private int encodedLength; 66 67 HelloExtensions() { 68 extensions = Collections.emptyList(); 69 } 70 71 HelloExtensions(HandshakeInStream s) throws IOException { 72 int len = s.getInt16(); 73 extensions = new ArrayList<HelloExtension>(); 74 encodedLength = len + 2; 75 while (len > 0) { 76 int type = s.getInt16(); 77 int extlen = s.getInt16(); 78 ExtensionType extType = ExtensionType.get(type); 79 HelloExtension extension; 80 if (extType == ExtensionType.EXT_SERVER_NAME) { 81 extension = new ServerNameExtension(s, extlen); 82 } else if (extType == ExtensionType.EXT_ELLIPTIC_CURVES) { 83 extension = new SupportedEllipticCurvesExtension(s, extlen); 84 } else if (extType == ExtensionType.EXT_EC_POINT_FORMATS) { 85 extension = new SupportedEllipticPointFormatsExtension(s, extlen); 86 } else { 87 extension = new UnknownExtension(s, extlen, extType); 88 } 89 extensions.add(extension); 90 len -= extlen + 4; 91 } 92 if (len != 0) { 93 throw new SSLProtocolException("Error parsing extensions: extra data"); 94 } 95 } 96 97 // Return the List of extensions. Must not be modified by the caller. 98 List<HelloExtension> list() { 99 return extensions; 100 } 101 102 void add(HelloExtension ext) { 103 if (extensions.isEmpty()) { 104 extensions = new ArrayList<HelloExtension>(); 105 } 106 extensions.add(ext); 107 encodedLength = -1; 108 } 109 110 HelloExtension get(ExtensionType type) { 111 for (HelloExtension ext : extensions) { 112 if (ext.type == type) { 113 return ext; 114 } 115 } 116 return null; 117 } 118 119 int length() { 120 if (encodedLength >= 0) { 121 return encodedLength; 122 } 123 if (extensions.isEmpty()) { 124 encodedLength = 0; 125 } else { 126 encodedLength = 2; 127 for (HelloExtension ext : extensions) { 128 encodedLength += ext.length(); 129 } 130 } 131 return encodedLength; 132 } 133 134 void send(HandshakeOutStream s) throws IOException { 135 int length = length(); 136 if (length == 0) { 137 return; 138 } 139 s.putInt16(length - 2); 140 for (HelloExtension ext : extensions) { 141 ext.send(s); 142 } 143 } 144 145 void print(PrintStream s) throws IOException { 146 for (HelloExtension ext : extensions) { 147 s.println(ext.toString()); 148 } 149 } 150 } 151 final class ExtensionType { 152 153 final int id; 154 final String name; 155 156 private ExtensionType(int id, String name) { 157 this.id = id; 158 this.name = name; 159 } 160 161 public String toString() { 162 return name; 163 } 164 165 static List<ExtensionType> knownExtensions = new ArrayList<ExtensionType>(8); 166 167 static ExtensionType get(int id) { 168 for (ExtensionType ext : knownExtensions) { 169 if (ext.id == id) { 170 return ext; 171 } 172 } 173 return new ExtensionType(id, "type_" + id); 174 } 175 176 private static ExtensionType e(int id, String name) { 177 ExtensionType ext = new ExtensionType(id, name); 178 knownExtensions.add(ext); 179 return ext; 180 } 181 182 // extensions defined in RFC 3546 183 final static ExtensionType EXT_SERVER_NAME = e( 0, "server_name"); 184 final static ExtensionType EXT_MAX_FRAGMENT_LENGTH = e( 1, "max_fragment_length"); 185 final static ExtensionType EXT_CLIENT_CERTIFICATE_URL = e( 2, "client_certificate_url"); 186 final static ExtensionType EXT_TRUSTED_CA_KEYS = e( 3, "trusted_ca_keys"); 187 final static ExtensionType EXT_TRUNCATED_HMAC = e( 4, "truncated_hmac"); 188 final static ExtensionType EXT_STATUS_REQUEST = e( 5, "status_request"); 189 190 // extensions defined in RFC 4492 (ECC) 191 final static ExtensionType EXT_ELLIPTIC_CURVES = e(10, "elliptic_curves"); 192 final static ExtensionType EXT_EC_POINT_FORMATS = e(11, "ec_point_formats"); 193 194 } 195 196 abstract class HelloExtension { 197 198 final ExtensionType type; 199 200 HelloExtension(ExtensionType type) { 201 this.type = type; 202 } 203 204 // Length of the encoded extension, including the type and length fields 205 abstract int length(); 206 207 abstract void send(HandshakeOutStream s) throws IOException; 208 209 public abstract String toString(); 210 211 } 212 213 final class UnknownExtension extends HelloExtension { 214 215 private final byte[] data; 216 217 UnknownExtension(HandshakeInStream s, int len, ExtensionType type) 218 throws IOException { 219 super(type); 220 data = new byte[len]; 221 // s.read() does not handle 0-length arrays. 222 if (len != 0) { 223 s.read(data); 224 } 225 } 226 227 int length() { 228 return 4 + data.length; 229 } 230 231 void send(HandshakeOutStream s) throws IOException { 232 s.putInt16(type.id); 233 s.putBytes16(data); 234 } 235 236 public String toString() { 237 return "Unsupported extension " + type + ", data: " + Debug.toString(data); 238 } 239 } 240 241 // Support for the server_name extension is incomplete. Parsing is implemented 242 // so that we get nicer debug output, but we neither send it nor do we do 243 // act on it if we receive it. 244 final class ServerNameExtension extends HelloExtension { 245 246 final static int NAME_HOST_NAME = 0; 247 248 private List<ServerName> names; 249 250 ServerNameExtension(HandshakeInStream s, int len) 251 throws IOException { 252 super(ExtensionType.EXT_SERVER_NAME); 253 names = new ArrayList<ServerName>(); 254 while (len > 0) { 255 ServerName name = new ServerName(s); 256 names.add(name); 257 len -= name.length + 2; 258 } 259 if (len != 0) { 260 throw new SSLProtocolException("Invalid server_name extension"); 261 } 262 } 263 264 ServerNameExtension(String serverName) { 265 super(ExtensionType.EXT_SERVER_NAME); 266 names = new ArrayList<ServerName>(1); 267 if ((serverName != null)&&(serverName.length()>0)) { 268 names.add(new ServerName(serverName)); 269 } 270 } 271 272 static class ServerName { 273 final int length; 274 final int type; 275 final byte[] data; 276 final String hostname; 277 278 ServerName(HandshakeInStream s) throws IOException { 279 length = s.getInt16(); 280 type = s.getInt8(); 281 data = s.getBytes16(); 282 if (type == NAME_HOST_NAME) { 283 hostname = new String(data, "UTF8"); 284 } else { 285 hostname = null; 286 } 287 } 288 289 ServerName(String hostname) { 290 this.hostname = hostname; 291 byte[] hostnameAsUTF8 = null; 292 try { 293 hostnameAsUTF8 = hostname.getBytes("UTF-8"); 294 } catch (java.io.UnsupportedEncodingException ex) { 295 // UTF-8 encoding should reliably be present. 296 throw new RuntimeException(ex); 297 } 298 this.data = hostnameAsUTF8; 299 this.type = NAME_HOST_NAME; 300 this.length = this.data.length + 3; 301 } 302 303 public String toString() { 304 if (type == NAME_HOST_NAME) { 305 return "host_name: " + hostname; 306 } else { 307 return "unknown-" + type + ": " + Debug.toString(data); 308 } 309 } 310 } 311 312 int length() { 313 if (names.size() == 0) 314 return 0; 315 int serverNameListLength = 0; 316 for (ServerName sn : names) { 317 serverNameListLength += sn.length + 2; 318 } 319 return serverNameListLength + 4; 320 } 321 322 void send(HandshakeOutStream s) throws IOException { 323 if (names.size() == 0) 324 return; 325 s.putInt16(type.id); 326 int serverNameListLength = 0; 327 for (ServerName sn : names) { 328 serverNameListLength += sn.length + 2; 329 } 330 s.putInt16(serverNameListLength); 331 for (ServerName sn : names) { 332 s.putInt16(sn.length); 333 s.putInt8(sn.type); 334 s.putBytes16(sn.data); 335 } 336 } 337 338 public String toString() { 339 StringBuilder sb = new StringBuilder(); 340 sb.append("Extension " + type + ", host names:\n"); 341 for (ServerName sn : names) { 342 sb.append(" " + sn + "\n"); 343 } 344 return sb.toString(); 345 } 346 } 347 348 final class SupportedEllipticCurvesExtension extends HelloExtension { 349 350 // the extension value to send in the ClientHello message 351 static final SupportedEllipticCurvesExtension DEFAULT; 352 353 private static final boolean fips; 354 355 static { 356 int[] ids; 357 fips = SunJSSE.isFIPS(); 358 if (fips == false) { 359 ids = new int[] { 360 // NIST curves first 361 // prefer NIST P-256, rest in order of increasing key length 362 23, 1, 3, 19, 21, 6, 7, 9, 10, 24, 11, 12, 25, 13, 14, 363 // non-NIST curves 364 15, 16, 17, 2, 18, 4, 5, 20, 8, 22, 365 }; 366 } else { 367 ids = new int[] { 368 // same as above, but allow only NIST curves in FIPS mode 369 23, 1, 3, 19, 21, 6, 7, 9, 10, 24, 11, 12, 25, 13, 14, 370 }; 371 } 372 DEFAULT = new SupportedEllipticCurvesExtension(ids); 373 } 374 375 private final int[] curveIds; 376 377 private SupportedEllipticCurvesExtension(int[] curveIds) { 378 super(ExtensionType.EXT_ELLIPTIC_CURVES); 379 this.curveIds = curveIds; 380 } 381 382 SupportedEllipticCurvesExtension(HandshakeInStream s, int len) 383 throws IOException { 384 super(ExtensionType.EXT_ELLIPTIC_CURVES); 385 int k = s.getInt16(); 386 if (((len & 1) != 0) || (k + 2 != len)) { 387 throw new SSLProtocolException("Invalid " + type + " extension"); 388 } 389 curveIds = new int[k >> 1]; 390 for (int i = 0; i < curveIds.length; i++) { 391 curveIds[i] = s.getInt16(); 392 } 393 } 394 395 boolean contains(int index) { 396 for (int curveId : curveIds) { 397 if (index == curveId) { 398 return true; 399 } 400 } 401 return false; 402 } 403 404 // Return a reference to the internal curveIds array. 405 // The caller must NOT modify the contents. 406 int[] curveIds() { 407 return curveIds; 408 } 409 410 int length() { 411 return 6 + (curveIds.length << 1); 412 } 413 414 void send(HandshakeOutStream s) throws IOException { 415 s.putInt16(type.id); 416 int k = curveIds.length << 1; 417 s.putInt16(k + 2); 418 s.putInt16(k); 419 for (int curveId : curveIds) { 420 s.putInt16(curveId); 421 } 422 } 423 424 public String toString() { 425 StringBuilder sb = new StringBuilder(); 426 sb.append("Extension " + type + ", curve names: {"); 427 boolean first = true; 428 for (int curveId : curveIds) { 429 if (first) { 430 first = false; 431 } else { 432 sb.append(", "); 433 } 434 // first check if it is a known named curve, then try other cases. 435 String oid = getCurveOid(curveId); 436 if (oid != null) { 437 ECParameterSpec spec = JsseJce.getECParameterSpec(oid); 438 // this toString() output will look nice for the current 439 // implementation of the ECParameterSpec class in the Sun 440 // provider, but may not look good for other implementations. 441 if (spec != null) { 442 sb.append(spec.toString().split(" ")[0]); 443 } else { 444 sb.append(oid); 445 } 446 } else if (curveId == ARBITRARY_PRIME) { 447 sb.append("arbitrary_explicit_prime_curves"); 448 } else if (curveId == ARBITRARY_CHAR2) { 449 sb.append("arbitrary_explicit_char2_curves"); 450 } else { 451 sb.append("unknown curve " + curveId); 452 } 453 } 454 sb.append("}"); 455 return sb.toString(); 456 } 457 458 // Test whether we support the curve with the given index. 459 static boolean isSupported(int index) { 460 if ((index <= 0) || (index >= NAMED_CURVE_OID_TABLE.length)) { 461 return false; 462 } 463 if (fips == false) { 464 // in non-FIPS mode, we support all valid indices 465 return true; 466 } 467 return DEFAULT.contains(index); 468 } 469 470 static int getCurveIndex(ECParameterSpec params) { 471 String oid = JsseJce.getNamedCurveOid(params); 472 if (oid == null) { 473 return -1; 474 } 475 Integer n = curveIndices.get(oid); 476 return (n == null) ? -1 : n; 477 } 478 479 static String getCurveOid(int index) { 480 if ((index > 0) && (index < NAMED_CURVE_OID_TABLE.length)) { 481 return NAMED_CURVE_OID_TABLE[index]; 482 } 483 return null; 484 } 485 486 private final static int ARBITRARY_PRIME = 0xff01; 487 private final static int ARBITRARY_CHAR2 = 0xff02; 488 489 // See sun.security.ec.NamedCurve for the OIDs 490 private final static String[] NAMED_CURVE_OID_TABLE = new String[] { 491 null, // (0) unused 492 "1.3.132.0.1", // (1) sect163k1, NIST K-163 493 "1.3.132.0.2", // (2) sect163r1 494 "1.3.132.0.15", // (3) sect163r2, NIST B-163 495 "1.3.132.0.24", // (4) sect193r1 496 "1.3.132.0.25", // (5) sect193r2 497 "1.3.132.0.26", // (6) sect233k1, NIST K-233 498 "1.3.132.0.27", // (7) sect233r1, NIST B-233 499 "1.3.132.0.3", // (8) sect239k1 500 "1.3.132.0.16", // (9) sect283k1, NIST K-283 501 "1.3.132.0.17", // (10) sect283r1, NIST B-283 502 "1.3.132.0.36", // (11) sect409k1, NIST K-409 503 "1.3.132.0.37", // (12) sect409r1, NIST B-409 504 "1.3.132.0.38", // (13) sect571k1, NIST K-571 505 "1.3.132.0.39", // (14) sect571r1, NIST B-571 506 "1.3.132.0.9", // (15) secp160k1 507 "1.3.132.0.8", // (16) secp160r1 508 "1.3.132.0.30", // (17) secp160r2 509 "1.3.132.0.31", // (18) secp192k1 510 "1.2.840.10045.3.1.1", // (19) secp192r1, NIST P-192 511 "1.3.132.0.32", // (20) secp224k1 512 "1.3.132.0.33", // (21) secp224r1, NIST P-224 513 "1.3.132.0.10", // (22) secp256k1 514 "1.2.840.10045.3.1.7", // (23) secp256r1, NIST P-256 515 "1.3.132.0.34", // (24) secp384r1, NIST P-384 516 "1.3.132.0.35", // (25) secp521r1, NIST P-521 517 }; 518 519 private final static Map<String,Integer> curveIndices; 520 521 static { 522 curveIndices = new HashMap<String,Integer>(); 523 for (int i = 1; i < NAMED_CURVE_OID_TABLE.length; i++) { 524 curveIndices.put(NAMED_CURVE_OID_TABLE[i], i); 525 } 526 } 527 528 } 529 530 final class SupportedEllipticPointFormatsExtension extends HelloExtension { 531 532 final static int FMT_UNCOMPRESSED = 0; 533 final static int FMT_ANSIX962_COMPRESSED_PRIME = 1; 534 final static int FMT_ANSIX962_COMPRESSED_CHAR2 = 2; 535 536 static final HelloExtension DEFAULT = 537 new SupportedEllipticPointFormatsExtension(new byte[] {FMT_UNCOMPRESSED}); 538 539 private final byte[] formats; 540 541 private SupportedEllipticPointFormatsExtension(byte[] formats) { 542 super(ExtensionType.EXT_EC_POINT_FORMATS); 543 this.formats = formats; 544 } 545 546 SupportedEllipticPointFormatsExtension(HandshakeInStream s, int len) 547 throws IOException { 548 super(ExtensionType.EXT_EC_POINT_FORMATS); 549 formats = s.getBytes8(); 550 // RFC 4492 says uncompressed points must always be supported. 551 // Check just to make sure. 552 boolean uncompressed = false; 553 for (int format : formats) { 554 if (format == FMT_UNCOMPRESSED) { 555 uncompressed = true; 556 break; 557 } 558 } 559 if (uncompressed == false) { 560 throw new SSLProtocolException 561 ("Peer does not support uncompressed points"); 562 } 563 } 564 565 int length() { 566 return 5 + formats.length; 567 } 568 569 void send(HandshakeOutStream s) throws IOException { 570 s.putInt16(type.id); 571 s.putInt16(formats.length + 1); 572 s.putBytes8(formats); 573 } 574 575 private static String toString(byte format) { 576 int f = format & 0xff; 577 switch (f) { 578 case FMT_UNCOMPRESSED: 579 return "uncompressed"; 580 case FMT_ANSIX962_COMPRESSED_PRIME: 581 return "ansiX962_compressed_prime"; 582 case FMT_ANSIX962_COMPRESSED_CHAR2: 583 return "ansiX962_compressed_char2"; 584 default: 585 return "unknown-" + f; 586 } 587 } 588 589 public String toString() { 590 List<String> list = new ArrayList<String>(); 591 for (byte format : formats) { 592 list.add(toString(format)); 593 } 594 return "Extension " + type + ", formats: " + list; 595 } 596 }