ServerNameExtension.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2006, 2012, 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
26package sun.security.ssl;
27
28import java.io.IOException;
29import java.nio.charset.StandardCharsets;
30import java.util.ArrayList;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.List;
34import java.util.LinkedHashMap;
35import java.util.Map;
36
37import javax.net.ssl.SNIHostName;
38import javax.net.ssl.SNIMatcher;
39import javax.net.ssl.SNIServerName;
40import javax.net.ssl.SSLProtocolException;
41import javax.net.ssl.StandardConstants;
42
43/*
44 * [RFC 4366/6066] To facilitate secure connections to servers that host
45 * multiple 'virtual' servers at a single underlying network address, clients
46 * MAY include an extension of type "server_name" in the (extended) client
47 * hello.  The "extension_data" field of this extension SHALL contain
48 * "ServerNameList" where:
49 *
50 *     struct {
51 *         NameType name_type;
52 *         select (name_type) {
53 *             case host_name: HostName;
54 *         } name;
55 *     } ServerName;
56 *
57 *     enum {
58 *         host_name(0), (255)
59 *     } NameType;
60 *
61 *     opaque HostName<1..2^16-1>;
62 *
63 *     struct {
64 *         ServerName server_name_list<1..2^16-1>
65 *     } ServerNameList;
66 */
67final class ServerNameExtension extends HelloExtension {
68
69    // For backward compatibility, all future data structures associated with
70    // new NameTypes MUST begin with a 16-bit length field.
71    static final int NAME_HEADER_LENGTH = 3;    // NameType: 1 byte
72                                                // Name length: 2 bytes
73    private Map<Integer, SNIServerName> sniMap;
74    private int listLength;     // ServerNameList length
75
76    // constructor for ServerHello
77    ServerNameExtension() throws IOException {
78        super(ExtensionType.EXT_SERVER_NAME);
79
80        listLength = 0;
81        sniMap = Collections.<Integer, SNIServerName>emptyMap();
82    }
83
84    // constructor for ClientHello
85    ServerNameExtension(List<SNIServerName> serverNames)
86            throws IOException {
87        super(ExtensionType.EXT_SERVER_NAME);
88
89        listLength = 0;
90        sniMap = new LinkedHashMap<>();
91        for (SNIServerName serverName : serverNames) {
92            // check for duplicated server name type
93            if (sniMap.put(serverName.getType(), serverName) != null) {
94                // unlikely to happen, but in case ...
95                throw new RuntimeException(
96                    "Duplicated server name of type " + serverName.getType());
97            }
98
99            listLength += serverName.getEncoded().length + NAME_HEADER_LENGTH;
100        }
101
102        // This constructor is used for ClientHello only.  Empty list is
103        // not allowed in client mode.
104        if (listLength == 0) {
105            throw new RuntimeException("The ServerNameList cannot be empty");
106        }
107    }
108
109    // constructor for ServerHello for parsing SNI extension
110    ServerNameExtension(HandshakeInStream s, int len)
111            throws IOException {
112        super(ExtensionType.EXT_SERVER_NAME);
113
114        int remains = len;
115        if (len >= 2) {    // "server_name" extension in ClientHello
116            listLength = s.getInt16();     // ServerNameList length
117            if (listLength == 0 || listLength + 2 != len) {
118                throw new SSLProtocolException(
119                        "Invalid " + type + " extension");
120            }
121
122            remains -= 2;
123            sniMap = new LinkedHashMap<>();
124            while (remains > 0) {
125                int code = s.getInt8();       // NameType
126
127                // HostName (length read in getBytes16);
128                byte[] encoded = s.getBytes16();
129                SNIServerName serverName;
130                switch (code) {
131                    case StandardConstants.SNI_HOST_NAME:
132                        if (encoded.length == 0) {
133                            throw new SSLProtocolException(
134                                "Empty HostName in server name indication");
135                        }
136                        try {
137                            serverName = new SNIHostName(encoded);
138                        } catch (IllegalArgumentException iae) {
139                            SSLProtocolException spe = new SSLProtocolException(
140                                "Illegal server name, type=host_name(" +
141                                code + "), name=" +
142                                (new String(encoded, StandardCharsets.UTF_8)) +
143                                ", value=" + Debug.toString(encoded));
144                            spe.initCause(iae);
145                            throw spe;
146                        }
147                        break;
148                    default:
149                        try {
150                            serverName = new UnknownServerName(code, encoded);
151                        } catch (IllegalArgumentException iae) {
152                            SSLProtocolException spe = new SSLProtocolException(
153                                "Illegal server name, type=(" + code +
154                                "), value=" + Debug.toString(encoded));
155                            spe.initCause(iae);
156                            throw spe;
157                        }
158                }
159                // check for duplicated server name type
160                if (sniMap.put(serverName.getType(), serverName) != null) {
161                    throw new SSLProtocolException(
162                            "Duplicated server name of type " +
163                            serverName.getType());
164                }
165
166                remains -= encoded.length + NAME_HEADER_LENGTH;
167            }
168        } else if (len == 0) {     // "server_name" extension in ServerHello
169            listLength = 0;
170            sniMap = Collections.<Integer, SNIServerName>emptyMap();
171        }
172
173        if (remains != 0) {
174            throw new SSLProtocolException("Invalid server_name extension");
175        }
176    }
177
178    List<SNIServerName> getServerNames() {
179        if (sniMap != null && !sniMap.isEmpty()) {
180            return Collections.<SNIServerName>unmodifiableList(
181                                        new ArrayList<>(sniMap.values()));
182        }
183
184        return Collections.<SNIServerName>emptyList();
185    }
186
187    /*
188     * Is the extension recognized by the corresponding matcher?
189     *
190     * This method is used to check whether the server name indication can
191     * be recognized by the server name matchers.
192     *
193     * Per RFC 6066, if the server understood the ClientHello extension but
194     * does not recognize the server name, the server SHOULD take one of two
195     * actions: either abort the handshake by sending a fatal-level
196     * unrecognized_name(112) alert or continue the handshake.
197     *
198     * If there is an instance of SNIMatcher defined for a particular name
199     * type, it must be used to perform match operations on the server name.
200     */
201    boolean isMatched(Collection<SNIMatcher> matchers) {
202        if (sniMap != null && !sniMap.isEmpty()) {
203            for (SNIMatcher matcher : matchers) {
204                SNIServerName sniName = sniMap.get(matcher.getType());
205                if (sniName != null && (!matcher.matches(sniName))) {
206                    return false;
207                }
208            }
209        }
210
211        return true;
212    }
213
214    /*
215     * Is the extension is identical to a server name list?
216     *
217     * This method is used to check the server name indication during session
218     * resumption.
219     *
220     * Per RFC 6066, when the server is deciding whether or not to accept a
221     * request to resume a session, the contents of a server_name extension
222     * MAY be used in the lookup of the session in the session cache.  The
223     * client SHOULD include the same server_name extension in the session
224     * resumption request as it did in the full handshake that established
225     * the session.  A server that implements this extension MUST NOT accept
226     * the request to resume the session if the server_name extension contains
227     * a different name.  Instead, it proceeds with a full handshake to
228     * establish a new session.  When resuming a session, the server MUST NOT
229     * include a server_name extension in the server hello.
230     */
231    boolean isIdentical(List<SNIServerName> other) {
232        if (other.size() == sniMap.size()) {
233            for(SNIServerName sniInOther : other) {
234                SNIServerName sniName = sniMap.get(sniInOther.getType());
235                if (sniName == null || !sniInOther.equals(sniName)) {
236                    return false;
237                }
238            }
239
240            return true;
241        }
242
243        return false;
244    }
245
246    @Override
247    int length() {
248        return listLength == 0 ? 4 : 6 + listLength;
249    }
250
251    @Override
252    void send(HandshakeOutStream s) throws IOException {
253        s.putInt16(type.id);
254        if (listLength == 0) {
255            s.putInt16(listLength);     // in ServerHello, empty extension_data
256        } else {
257            s.putInt16(listLength + 2); // length of extension_data
258            s.putInt16(listLength);     // length of ServerNameList
259
260            for (SNIServerName sniName : sniMap.values()) {
261                s.putInt8(sniName.getType());         // server name type
262                s.putBytes16(sniName.getEncoded());   // server name value
263            }
264        }
265    }
266
267    @Override
268    public String toString() {
269        StringBuilder sb = new StringBuilder();
270        for (SNIServerName sniName : sniMap.values()) {
271            sb.append("[" + sniName + "]");
272        }
273
274        return "Extension " + type + ", server_name: " + sb;
275    }
276
277    private static class UnknownServerName extends SNIServerName {
278        UnknownServerName(int code, byte[] encoded) {
279            super(code, encoded);
280        }
281    }
282
283}
284