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 }