rev 928 : Summary: Added support for Server Name Indication (SNI) hello extension to SSL client. Contributed-by: Michael Tandy <michaeltandy at googlemail dot com>
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 * This file contains all the classes relevant to TLS Extensions for the 38 * ClientHello and ServerHello messages. The extension mechanism and 39 * several extensions are defined in RFC 3546. Additional extensions are 40 * defined in the ECC RFC 4492. 41 * 42 * Currently, only the two ECC extensions are fully supported. 43 * 44 * The classes contained in this file are: 45 * . HelloExtensions: a List of extensions as used in the client hello 46 * and server hello messages. 47 * . ExtensionType: an enum style class for the extension type 48 * . HelloExtension: abstract base class for all extensions. All subclasses 49 * must be immutable. 50 * 51 * . UnknownExtension: used to represent all parsed extensions that we do not 52 * explicitly support. 53 * . ServerNameExtension: partially implemented server_name extension. 54 * . SupportedEllipticCurvesExtension: the ECC supported curves extension. 55 * . SupportedEllipticPointFormatsExtension: the ECC supported point formats 56 * (compressed/uncompressed) extension. 57 * 58 * @since 1.6 59 * @author Andreas Sterbenz 60 */ 61 final class HelloExtensions { 62 63 private List<HelloExtension> extensions; 64 private int encodedLength; 65 66 HelloExtensions() { 67 extensions = Collections.emptyList(); 68 } 69 70 HelloExtensions(HandshakeInStream s) throws IOException { 71 int len = s.getInt16(); 72 extensions = new ArrayList<HelloExtension>(); 73 encodedLength = len + 2; 74 while (len > 0) { 75 int type = s.getInt16(); 76 int extlen = s.getInt16(); 77 ExtensionType extType = ExtensionType.get(type); 78 HelloExtension extension; 79 if (extType == ExtensionType.EXT_SERVER_NAME) { 80 extension = new ServerNameExtension(s, extlen); 81 } else if (extType == ExtensionType.EXT_ELLIPTIC_CURVES) { 82 extension = new SupportedEllipticCurvesExtension(s, extlen); 83 } else if (extType == ExtensionType.EXT_EC_POINT_FORMATS) { 84 extension = new SupportedEllipticPointFormatsExtension(s, extlen); 85 } else { 86 extension = new UnknownExtension(s, extlen, extType); 87 } 88 extensions.add(extension); 89 len -= extlen + 4; 90 } 91 if (len != 0) { 92 throw new SSLProtocolException("Error parsing extensions: extra data"); 93 } 94 } 95 96 // Return the List of extensions. Must not be modified by the caller. 97 List<HelloExtension> list() { 98 return extensions; 99 } 100 101 void add(HelloExtension ext) { 102 if (extensions.isEmpty()) { 103 extensions = new ArrayList<HelloExtension>(); 104 } 105 extensions.add(ext); 106 encodedLength = -1; 107 } 108 109 HelloExtension get(ExtensionType type) { 110 for (HelloExtension ext : extensions) { 111 if (ext.type == type) { 112 return ext; 113 } 114 } 115 return null; 116 } 117 118 int length() { 119 if (encodedLength >= 0) { 120 return encodedLength; 121 } 122 if (extensions.isEmpty()) { 123 encodedLength = 0; 124 } else { 125 encodedLength = 2; 126 for (HelloExtension ext : extensions) { 127 encodedLength += ext.length(); 128 } 129 } 130 return encodedLength; 131 } 132 133 void send(HandshakeOutStream s) throws IOException { 134 int length = length(); 135 if (length == 0) { 136 return; 137 } 138 s.putInt16(length - 2); 139 for (HelloExtension ext : extensions) { 140 ext.send(s); 141 } 142 } 143 144 void print(PrintStream s) throws IOException { 145 for (HelloExtension ext : extensions) { 146 s.println(ext.toString()); 147 } 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 static class ServerName { 265 final int length; 266 final int type; 267 final byte[] data; 268 final String hostname; 269 270 ServerName(HandshakeInStream s) throws IOException { 271 length = s.getInt16(); 272 type = s.getInt8(); 273 data = s.getBytes16(); 274 if (type == NAME_HOST_NAME) { 275 hostname = new String(data, "UTF8"); 276 } else { 277 hostname = null; 278 } 279 } 280 281 public String toString() { 282 if (type == NAME_HOST_NAME) { 283 return "host_name: " + hostname; 284 } else { 285 return "unknown-" + type + ": " + Debug.toString(data); 286 } 287 } 288 } 289 290 int length() { 291 throw new RuntimeException("not yet supported"); 292 } 293 294 void send(HandshakeOutStream s) throws IOException { 295 throw new RuntimeException("not yet supported"); 296 } 297 298 public String toString() { 299 return "Unsupported extension " + type + ", " + names.toString(); 300 } 301 } 302 303 final class SupportedEllipticCurvesExtension extends HelloExtension { 304 305 // the extension value to send in the ClientHello message 306 static final SupportedEllipticCurvesExtension DEFAULT; 307 308 private static final boolean fips; 309 310 static { 311 int[] ids; 312 fips = SunJSSE.isFIPS(); 313 if (fips == false) { 314 ids = new int[] { 315 // NIST curves first 316 // prefer NIST P-256, rest in order of increasing key length 317 23, 1, 3, 19, 21, 6, 7, 9, 10, 24, 11, 12, 25, 13, 14, 318 // non-NIST curves 319 15, 16, 17, 2, 18, 4, 5, 20, 8, 22, 320 }; 321 } else { 322 ids = new int[] { 323 // same as above, but allow only NIST curves in FIPS mode 324 23, 1, 3, 19, 21, 6, 7, 9, 10, 24, 11, 12, 25, 13, 14, 325 }; 326 } 327 DEFAULT = new SupportedEllipticCurvesExtension(ids); 328 } 329 330 private final int[] curveIds; 331 332 private SupportedEllipticCurvesExtension(int[] curveIds) { 333 super(ExtensionType.EXT_ELLIPTIC_CURVES); 334 this.curveIds = curveIds; 335 } 336 337 SupportedEllipticCurvesExtension(HandshakeInStream s, int len) 338 throws IOException { 339 super(ExtensionType.EXT_ELLIPTIC_CURVES); 340 int k = s.getInt16(); 341 if (((len & 1) != 0) || (k + 2 != len)) { 342 throw new SSLProtocolException("Invalid " + type + " extension"); 343 } 344 curveIds = new int[k >> 1]; 345 for (int i = 0; i < curveIds.length; i++) { 346 curveIds[i] = s.getInt16(); 347 } 348 } 349 350 boolean contains(int index) { 351 for (int curveId : curveIds) { 352 if (index == curveId) { 353 return true; 354 } 355 } 356 return false; 357 } 358 359 // Return a reference to the internal curveIds array. 360 // The caller must NOT modify the contents. 361 int[] curveIds() { 362 return curveIds; 363 } 364 365 int length() { 366 return 6 + (curveIds.length << 1); 367 } 368 369 void send(HandshakeOutStream s) throws IOException { 370 s.putInt16(type.id); 371 int k = curveIds.length << 1; 372 s.putInt16(k + 2); 373 s.putInt16(k); 374 for (int curveId : curveIds) { 375 s.putInt16(curveId); 376 } 377 } 378 379 public String toString() { 380 StringBuilder sb = new StringBuilder(); 381 sb.append("Extension " + type + ", curve names: {"); 382 boolean first = true; 383 for (int curveId : curveIds) { 384 if (first) { 385 first = false; 386 } else { 387 sb.append(", "); 388 } 389 // first check if it is a known named curve, then try other cases. 390 String oid = getCurveOid(curveId); 391 if (oid != null) { 392 ECParameterSpec spec = JsseJce.getECParameterSpec(oid); 393 // this toString() output will look nice for the current 394 // implementation of the ECParameterSpec class in the Sun 395 // provider, but may not look good for other implementations. 396 if (spec != null) { 397 sb.append(spec.toString().split(" ")[0]); 398 } else { 399 sb.append(oid); 400 } 401 } else if (curveId == ARBITRARY_PRIME) { 402 sb.append("arbitrary_explicit_prime_curves"); 403 } else if (curveId == ARBITRARY_CHAR2) { 404 sb.append("arbitrary_explicit_char2_curves"); 405 } else { 406 sb.append("unknown curve " + curveId); 407 } 408 } 409 sb.append("}"); 410 return sb.toString(); 411 } 412 413 // Test whether we support the curve with the given index. 414 static boolean isSupported(int index) { 415 if ((index <= 0) || (index >= NAMED_CURVE_OID_TABLE.length)) { 416 return false; 417 } 418 if (fips == false) { 419 // in non-FIPS mode, we support all valid indices 420 return true; 421 } 422 return DEFAULT.contains(index); 423 } 424 425 static int getCurveIndex(ECParameterSpec params) { 426 String oid = JsseJce.getNamedCurveOid(params); 427 if (oid == null) { 428 return -1; 429 } 430 Integer n = curveIndices.get(oid); 431 return (n == null) ? -1 : n; 432 } 433 434 static String getCurveOid(int index) { 435 if ((index > 0) && (index < NAMED_CURVE_OID_TABLE.length)) { 436 return NAMED_CURVE_OID_TABLE[index]; 437 } 438 return null; 439 } 440 441 private final static int ARBITRARY_PRIME = 0xff01; 442 private final static int ARBITRARY_CHAR2 = 0xff02; 443 444 // See sun.security.ec.NamedCurve for the OIDs 445 private final static String[] NAMED_CURVE_OID_TABLE = new String[] { 446 null, // (0) unused 447 "1.3.132.0.1", // (1) sect163k1, NIST K-163 448 "1.3.132.0.2", // (2) sect163r1 449 "1.3.132.0.15", // (3) sect163r2, NIST B-163 450 "1.3.132.0.24", // (4) sect193r1 451 "1.3.132.0.25", // (5) sect193r2 452 "1.3.132.0.26", // (6) sect233k1, NIST K-233 453 "1.3.132.0.27", // (7) sect233r1, NIST B-233 454 "1.3.132.0.3", // (8) sect239k1 455 "1.3.132.0.16", // (9) sect283k1, NIST K-283 456 "1.3.132.0.17", // (10) sect283r1, NIST B-283 457 "1.3.132.0.36", // (11) sect409k1, NIST K-409 458 "1.3.132.0.37", // (12) sect409r1, NIST B-409 459 "1.3.132.0.38", // (13) sect571k1, NIST K-571 460 "1.3.132.0.39", // (14) sect571r1, NIST B-571 461 "1.3.132.0.9", // (15) secp160k1 462 "1.3.132.0.8", // (16) secp160r1 463 "1.3.132.0.30", // (17) secp160r2 464 "1.3.132.0.31", // (18) secp192k1 465 "1.2.840.10045.3.1.1", // (19) secp192r1, NIST P-192 466 "1.3.132.0.32", // (20) secp224k1 467 "1.3.132.0.33", // (21) secp224r1, NIST P-224 468 "1.3.132.0.10", // (22) secp256k1 469 "1.2.840.10045.3.1.7", // (23) secp256r1, NIST P-256 470 "1.3.132.0.34", // (24) secp384r1, NIST P-384 471 "1.3.132.0.35", // (25) secp521r1, NIST P-521 472 }; 473 474 private final static Map<String,Integer> curveIndices; 475 476 static { 477 curveIndices = new HashMap<String,Integer>(); 478 for (int i = 1; i < NAMED_CURVE_OID_TABLE.length; i++) { 479 curveIndices.put(NAMED_CURVE_OID_TABLE[i], i); 480 } 481 } 482 483 } 484 485 final class SupportedEllipticPointFormatsExtension extends HelloExtension { 486 487 final static int FMT_UNCOMPRESSED = 0; 488 final static int FMT_ANSIX962_COMPRESSED_PRIME = 1; 489 final static int FMT_ANSIX962_COMPRESSED_CHAR2 = 2; 490 491 static final HelloExtension DEFAULT = 492 new SupportedEllipticPointFormatsExtension(new byte[] {FMT_UNCOMPRESSED}); 493 494 private final byte[] formats; 495 496 private SupportedEllipticPointFormatsExtension(byte[] formats) { 497 super(ExtensionType.EXT_EC_POINT_FORMATS); 498 this.formats = formats; 499 } 500 501 SupportedEllipticPointFormatsExtension(HandshakeInStream s, int len) 502 throws IOException { 503 super(ExtensionType.EXT_EC_POINT_FORMATS); 504 formats = s.getBytes8(); 505 // RFC 4492 says uncompressed points must always be supported. 506 // Check just to make sure. 507 boolean uncompressed = false; 508 for (int format : formats) { 509 if (format == FMT_UNCOMPRESSED) { 510 uncompressed = true; 511 break; 512 } 513 } 514 if (uncompressed == false) { 515 throw new SSLProtocolException 516 ("Peer does not support uncompressed points"); 517 } 518 } 519 520 int length() { 521 return 5 + formats.length; 522 } 523 524 void send(HandshakeOutStream s) throws IOException { 525 s.putInt16(type.id); 526 s.putInt16(formats.length + 1); 527 s.putBytes8(formats); 528 } 529 530 private static String toString(byte format) { 531 int f = format & 0xff; 532 switch (f) { 533 case FMT_UNCOMPRESSED: 534 return "uncompressed"; 535 case FMT_ANSIX962_COMPRESSED_PRIME: 536 return "ansiX962_compressed_prime"; 537 case FMT_ANSIX962_COMPRESSED_CHAR2: 538 return "ansiX962_compressed_char2"; 539 default: 540 return "unknown-" + f; 541 } 542 } 543 544 public String toString() { 545 List<String> list = new ArrayList<String>(); 546 for (byte format : formats) { 547 list.add(toString(format)); 548 } 549 return "Extension " + type + ", formats: " + list; 550 } 551 } --- EOF ---