1 /*
2  * Copyright (c) 2003, 2018, 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.math.BigInteger;
30 import java.nio.ByteBuffer;
31 import java.security.CryptoPrimitive;
32 import java.security.GeneralSecurityException;
33 import java.security.KeyFactory;
34 import java.text.MessageFormat;
35 import java.util.EnumSet;
36 import java.util.Locale;
37 import javax.crypto.SecretKey;
38 import javax.crypto.interfaces.DHPublicKey;
39 import javax.crypto.spec.DHParameterSpec;
40 import javax.crypto.spec.DHPublicKeySpec;
41 import javax.net.ssl.SSLHandshakeException;
42 import sun.security.ssl.DHKeyExchange.DHECredentials;
43 import sun.security.ssl.DHKeyExchange.DHEPossession;
44 import sun.security.ssl.SSLHandshake.HandshakeMessage;
45 import sun.security.util.HexDumpEncoder;
46 
47 /**
48  * Pack of the "ClientKeyExchange" handshake message.
49  */
50 final class DHClientKeyExchange {
51     static final DHClientKeyExchangeConsumer dhHandshakeConsumer =
52             new DHClientKeyExchangeConsumer();
53     static final DHClientKeyExchangeProducer dhHandshakeProducer =
54             new DHClientKeyExchangeProducer();
55 
56     /**
57      * The DiffieHellman ClientKeyExchange handshake message.
58      *
59      * If the client has sent a certificate which contains a suitable
60      * DiffieHellman key (for fixed_dh client authentication), then the
61      * client public value is implicit and does not need to be sent again.
62      * In this case, the client key exchange message will be sent, but it
63      * MUST be empty.
64      *
65      * Currently, we don't support cipher suite that requires implicit public
66      * key of client.
67      */
68     private static final
69             class DHClientKeyExchangeMessage extends HandshakeMessage {
70         private byte[] y;        // 1 to 2^16 - 1 bytes
71 
DHClientKeyExchangeMessage( HandshakeContext handshakeContext)72         DHClientKeyExchangeMessage(
73                 HandshakeContext handshakeContext) throws IOException {
74             super(handshakeContext);
75             // This happens in client side only.
76             ClientHandshakeContext chc =
77                     (ClientHandshakeContext)handshakeContext;
78 
79             DHEPossession dhePossession = null;
80             for (SSLPossession possession : chc.handshakePossessions) {
81                 if (possession instanceof DHEPossession) {
82                     dhePossession = (DHEPossession)possession;
83                     break;
84                 }
85             }
86 
87             if (dhePossession == null) {
88                 // unlikely
89                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
90                     "No DHE credentials negotiated for client key exchange");
91             }
92 
93             DHPublicKey publicKey = dhePossession.publicKey;
94             DHParameterSpec params = publicKey.getParams();
95             this.y = Utilities.toByteArray(publicKey.getY());
96         }
97 
DHClientKeyExchangeMessage(HandshakeContext handshakeContext, ByteBuffer m)98         DHClientKeyExchangeMessage(HandshakeContext handshakeContext,
99                 ByteBuffer m) throws IOException {
100             super(handshakeContext);
101             // This happens in server side only.
102             ServerHandshakeContext shc =
103                     (ServerHandshakeContext)handshakeContext;
104 
105             if (m.remaining() < 3) {
106                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
107                     "Invalid DH ClientKeyExchange message: insufficient data");
108             }
109 
110             this.y = Record.getBytes16(m);
111 
112             if (m.hasRemaining()) {
113                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
114                     "Invalid DH ClientKeyExchange message: unknown extra data");
115             }
116         }
117 
118         @Override
handshakeType()119         public SSLHandshake handshakeType() {
120             return SSLHandshake.CLIENT_KEY_EXCHANGE;
121         }
122 
123         @Override
messageLength()124         public int messageLength() {
125             return y.length + 2;    // 2: length filed
126         }
127 
128         @Override
send(HandshakeOutStream hos)129         public void send(HandshakeOutStream hos) throws IOException {
130             hos.putBytes16(y);
131         }
132 
133         @Override
toString()134         public String toString() {
135             MessageFormat messageFormat = new MessageFormat(
136                 "\"DH ClientKeyExchange\": '{'\n" +
137                 "  \"parameters\": '{'\n" +
138                 "    \"dh_Yc\": '{'\n" +
139                 "{0}\n" +
140                 "    '}',\n" +
141                 "  '}'\n" +
142                 "'}'",
143                 Locale.ENGLISH);
144 
145             HexDumpEncoder hexEncoder = new HexDumpEncoder();
146             Object[] messageFields = {
147                 Utilities.indent(
148                         hexEncoder.encodeBuffer(y), "      "),
149             };
150             return messageFormat.format(messageFields);
151         }
152     }
153 
154     /**
155      * The DiffieHellman "ClientKeyExchange" handshake message producer.
156      */
157     private static final
158             class DHClientKeyExchangeProducer implements HandshakeProducer {
159         // Prevent instantiation of this class.
DHClientKeyExchangeProducer()160         private DHClientKeyExchangeProducer() {
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             DHECredentials dheCredentials = null;
171             for (SSLCredentials cd : chc.handshakeCredentials) {
172                 if (cd instanceof DHECredentials) {
173                     dheCredentials = (DHECredentials)cd;
174                     break;
175                 }
176             }
177 
178             if (dheCredentials == null) {
179                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
180                     "No DHE credentials negotiated for client key exchange");
181             }
182 
183 
184             DHEPossession dhePossession = new DHEPossession(
185                     dheCredentials, chc.sslContext.getSecureRandom());
186             chc.handshakePossessions.add(dhePossession);
187             DHClientKeyExchangeMessage ckem =
188                     new DHClientKeyExchangeMessage(chc);
189             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
190                 SSLLogger.fine(
191                     "Produced DH ClientKeyExchange handshake message", ckem);
192             }
193 
194             // Output the handshake message.
195             ckem.write(chc.handshakeOutput);
196             chc.handshakeOutput.flush();
197 
198             // update the states
199             SSLKeyExchange ke = SSLKeyExchange.valueOf(
200                     chc.negotiatedCipherSuite.keyExchange,
201                     chc.negotiatedProtocol);
202             if (ke == null) {
203                 // unlikely
204                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
205                         "Not supported key exchange type");
206             } else {
207                 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc);
208                 SecretKey masterSecret =
209                         masterKD.deriveKey("MasterSecret", null);
210                 chc.handshakeSession.setMasterSecret(masterSecret);
211 
212                 SSLTrafficKeyDerivation kd =
213                         SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
214                 if (kd == null) {
215                     // unlikely
216                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
217                             "Not supported key derivation: " +
218                             chc.negotiatedProtocol);
219                 } else {
220                     chc.handshakeKeyDerivation =
221                         kd.createKeyDerivation(chc, masterSecret);
222                 }
223             }
224 
225             // The handshake message has been delivered.
226             return null;
227         }
228     }
229 
230     /**
231      * The DiffieHellman "ClientKeyExchange" handshake message consumer.
232      */
233     private static final
234             class DHClientKeyExchangeConsumer implements SSLConsumer {
235         // Prevent instantiation of this class.
DHClientKeyExchangeConsumer()236         private DHClientKeyExchangeConsumer() {
237             // blank
238         }
239 
240         @Override
consume(ConnectionContext context, ByteBuffer message)241         public void consume(ConnectionContext context,
242                 ByteBuffer message) throws IOException {
243             // The consuming happens in server side only.
244             ServerHandshakeContext shc = (ServerHandshakeContext)context;
245 
246             DHEPossession dhePossession = null;
247             for (SSLPossession possession : shc.handshakePossessions) {
248                 if (possession instanceof DHEPossession) {
249                     dhePossession = (DHEPossession)possession;
250                     break;
251                 }
252             }
253 
254             if (dhePossession == null) {
255                 // unlikely
256                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
257                     "No expected DHE possessions for client key exchange");
258             }
259 
260             SSLKeyExchange ke = SSLKeyExchange.valueOf(
261                     shc.negotiatedCipherSuite.keyExchange,
262                     shc.negotiatedProtocol);
263             if (ke == null) {
264                 // unlikely
265                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
266                         "Not supported key exchange type");
267             }
268 
269             DHClientKeyExchangeMessage ckem =
270                     new DHClientKeyExchangeMessage(shc, message);
271             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
272                 SSLLogger.fine(
273                     "Consuming DH ClientKeyExchange handshake message", ckem);
274             }
275 
276             // create the credentials
277             try {
278                 DHParameterSpec params = dhePossession.publicKey.getParams();
279                 DHPublicKeySpec spec = new DHPublicKeySpec(
280                         new BigInteger(1, ckem.y),
281                         params.getP(), params.getG());
282                 KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman");
283                 DHPublicKey peerPublicKey =
284                         (DHPublicKey)kf.generatePublic(spec);
285 
286                 // check constraints of peer DHPublicKey
287                 if (!shc.algorithmConstraints.permits(
288                         EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
289                         peerPublicKey)) {
290                     throw new SSLHandshakeException(
291                         "DHPublicKey does not comply to algorithm constraints");
292                 }
293 
294                 NamedGroup namedGroup = NamedGroup.valueOf(params);
295                 shc.handshakeCredentials.add(
296                         new DHECredentials(peerPublicKey, namedGroup));
297             } catch (GeneralSecurityException | java.io.IOException e) {
298                 throw (SSLHandshakeException)(new SSLHandshakeException(
299                         "Could not generate DHPublicKey").initCause(e));
300             }
301 
302             // update the states
303             SSLKeyDerivation masterKD = ke.createKeyDerivation(shc);
304             SecretKey masterSecret =
305                     masterKD.deriveKey("MasterSecret", null);
306             shc.handshakeSession.setMasterSecret(masterSecret);
307 
308             SSLTrafficKeyDerivation kd =
309                     SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
310             if (kd == null) {
311                 // unlikely
312                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
313                     "Not supported key derivation: " + shc.negotiatedProtocol);
314             } else {
315                 shc.handshakeKeyDerivation =
316                     kd.createKeyDerivation(shc, masterSecret);
317             }
318         }
319     }
320 }
321