1 /*
2  * Copyright (c) 2015, 2019, 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.text.MessageFormat;
31 import java.util.Locale;
32 import javax.net.ssl.SSLProtocolException;
33 import static sun.security.ssl.SSLExtension.CH_EC_POINT_FORMATS;
34 import sun.security.ssl.SSLExtension.ExtensionConsumer;
35 import sun.security.ssl.SSLExtension.SSLExtensionSpec;
36 import sun.security.ssl.SSLHandshake.HandshakeMessage;
37 import sun.security.ssl.NamedGroup.NamedGroupSpec;
38 
39 /**
40  * Pack of the "ec_point_formats" extensions [RFC 4492].
41  */
42 final class ECPointFormatsExtension {
43     static final HandshakeProducer chNetworkProducer =
44             new CHECPointFormatsProducer();
45     static final ExtensionConsumer chOnLoadConsumer =
46             new CHECPointFormatsConsumer();
47 
48     static final ExtensionConsumer shOnLoadConsumer =
49             new SHECPointFormatsConsumer();
50 
51     static final SSLStringizer epfStringizer =
52             new ECPointFormatsStringizer();
53 
54     /**
55      * The "ec_point_formats" extension.
56      */
57     static class ECPointFormatsSpec implements SSLExtensionSpec {
58         static final ECPointFormatsSpec DEFAULT =
59             new ECPointFormatsSpec(new byte[] {ECPointFormat.UNCOMPRESSED.id});
60 
61         final byte[] formats;
62 
ECPointFormatsSpec(byte[] formats)63         ECPointFormatsSpec(byte[] formats) {
64             this.formats = formats;
65         }
66 
ECPointFormatsSpec(ByteBuffer m)67         private ECPointFormatsSpec(ByteBuffer m) throws IOException {
68             if (!m.hasRemaining()) {
69                 throw new SSLProtocolException(
70                     "Invalid ec_point_formats extension: " +
71                     "insufficient data");
72             }
73 
74             this.formats = Record.getBytes8(m);
75         }
76 
hasUncompressedFormat()77         private boolean hasUncompressedFormat() {
78             for (byte format : formats) {
79                 if (format == ECPointFormat.UNCOMPRESSED.id) {
80                     return true;
81                 }
82             }
83 
84             return false;
85         }
86 
87         @Override
toString()88         public String toString() {
89             MessageFormat messageFormat = new MessageFormat(
90                 "\"formats\": '['{0}']'", Locale.ENGLISH);
91             if (formats == null || formats.length ==  0) {
92                 Object[] messageFields = {
93                         "<no EC point format specified>"
94                     };
95                 return messageFormat.format(messageFields);
96             } else {
97                 StringBuilder builder = new StringBuilder(512);
98                 boolean isFirst = true;
99                 for (byte pf : formats) {
100                     if (isFirst) {
101                         isFirst = false;
102                     } else {
103                         builder.append(", ");
104                     }
105 
106                     builder.append(ECPointFormat.nameOf(pf));
107                 }
108 
109                 Object[] messageFields = {
110                         builder.toString()
111                     };
112 
113                 return messageFormat.format(messageFields);
114             }
115         }
116     }
117 
118     private static final class ECPointFormatsStringizer implements SSLStringizer {
119         @Override
toString(ByteBuffer buffer)120         public String toString(ByteBuffer buffer) {
121             try {
122                 return (new ECPointFormatsSpec(buffer)).toString();
123             } catch (IOException ioe) {
124                 // For debug logging only, so please swallow exceptions.
125                 return ioe.getMessage();
126             }
127         }
128     }
129 
130     private static enum ECPointFormat {
131         UNCOMPRESSED                    ((byte)0, "uncompressed"),
132         ANSIX962_COMPRESSED_PRIME       ((byte)1, "ansiX962_compressed_prime"),
133         FMT_ANSIX962_COMPRESSED_CHAR2   ((byte)2, "ansiX962_compressed_char2");
134 
135         final byte id;
136         final String name;
137 
ECPointFormat(byte id, String name)138         private ECPointFormat(byte id, String name) {
139             this.id = id;
140             this.name = name;
141         }
142 
nameOf(int id)143         static String nameOf(int id) {
144             for (ECPointFormat pf: ECPointFormat.values()) {
145                 if (pf.id == id) {
146                     return pf.name;
147                 }
148             }
149             return "UNDEFINED-EC-POINT-FORMAT(" + id + ")";
150         }
151     }
152 
153     /**
154      * Network data producer of a "ec_point_formats" extension in
155      * the ClientHello handshake message.
156      */
157     private static final
158             class CHECPointFormatsProducer implements HandshakeProducer {
159         // Prevent instantiation of this class.
CHECPointFormatsProducer()160         private CHECPointFormatsProducer() {
161             // blank
162         }
163 
164         @Override
produce(ConnectionContext context, HandshakeMessage message)165         public byte[] produce(ConnectionContext context,
166                 HandshakeMessage message) throws IOException {
167             // The producing happens in client side only.
168             ClientHandshakeContext chc = (ClientHandshakeContext)context;
169 
170             // Is it a supported and enabled extension?
171             if (!chc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) {
172                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
173                     SSLLogger.fine(
174                         "Ignore unavailable ec_point_formats extension");
175                 }
176                 return null;
177             }
178 
179             // Produce the extension.
180             //
181             // produce the extension only if EC cipher suite is activated.
182             if (NamedGroupSpec.NAMED_GROUP_ECDHE.isSupported(
183                     chc.activeCipherSuites)) {
184                 // We are using uncompressed ECPointFormat only at present.
185                 byte[] extData = new byte[] {0x01, 0x00};
186 
187                 // Update the context.
188                 chc.handshakeExtensions.put(
189                     CH_EC_POINT_FORMATS, ECPointFormatsSpec.DEFAULT);
190 
191                 return extData;
192             }
193 
194             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
195                 SSLLogger.fine(
196                     "Need no ec_point_formats extension");
197             }
198             return null;
199         }
200     }
201 
202     /**
203      * Network data consumer of a "ec_point_formats" extension in
204      * the ClientHello handshake message.
205      */
206     private static final
207             class CHECPointFormatsConsumer implements ExtensionConsumer {
208         // Prevent instantiation of this class.
CHECPointFormatsConsumer()209         private CHECPointFormatsConsumer() {
210             // blank
211         }
212 
213         @Override
consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer)214         public void consume(ConnectionContext context,
215             HandshakeMessage message, ByteBuffer buffer) throws IOException {
216 
217             // The consuming happens in server side only.
218             ServerHandshakeContext shc = (ServerHandshakeContext)context;
219 
220             // Is it a supported and enabled extension?
221             if (!shc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) {
222                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
223                     SSLLogger.fine(
224                         "Ignore unavailable ec_point_formats extension");
225                 }
226                 return;     // ignore the extension
227             }
228 
229             // Parse the extension.
230             ECPointFormatsSpec spec;
231             try {
232                 spec = new ECPointFormatsSpec(buffer);
233             } catch (IOException ioe) {
234                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
235             }
236 
237             // per RFC 4492, uncompressed points must always be supported.
238             if (!spec.hasUncompressedFormat()) {
239                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
240                     "Invalid ec_point_formats extension data: " +
241                     "peer does not support uncompressed points");
242             }
243 
244             // Update the context.
245             shc.handshakeExtensions.put(CH_EC_POINT_FORMATS, spec);
246 
247             // No impact on session resumption, as only uncompressed points
248             // are supported at present.
249         }
250     }
251 
252     /**
253      * Network data consumer of a "ec_point_formats" extension in
254      * the ServerHello handshake message.
255      */
256     private static final
257             class SHECPointFormatsConsumer implements ExtensionConsumer {
258         // Prevent instantiation of this class.
SHECPointFormatsConsumer()259         private SHECPointFormatsConsumer() {
260             // blank
261         }
262 
263         @Override
consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer)264         public void consume(ConnectionContext context,
265             HandshakeMessage message, ByteBuffer buffer) throws IOException {
266 
267             // The consuming happens in client side only.
268             ClientHandshakeContext chc = (ClientHandshakeContext)context;
269 
270             // In response to "ec_point_formats" extension request only
271             ECPointFormatsSpec requestedSpec = (ECPointFormatsSpec)
272                     chc.handshakeExtensions.get(CH_EC_POINT_FORMATS);
273             if (requestedSpec == null) {
274                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
275                     "Unexpected ec_point_formats extension in ServerHello");
276             }
277 
278             // Parse the extension.
279             ECPointFormatsSpec spec;
280             try {
281                 spec = new ECPointFormatsSpec(buffer);
282             } catch (IOException ioe) {
283                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
284             }
285 
286             // per RFC 4492, uncompressed points must always be supported.
287             if (!spec.hasUncompressedFormat()) {
288                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
289                         "Invalid ec_point_formats extension data: " +
290                         "peer does not support uncompressed points");
291             }
292 
293             // Update the context.
294             chc.handshakeExtensions.put(CH_EC_POINT_FORMATS, spec);
295 
296             // No impact on session resumption, as only uncompressed points
297             // are supported at present.
298         }
299     }
300 }
301