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