1 /*
2  * Copyright (c) 2015, 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.nio.ByteBuffer;
30 import java.security.CryptoPrimitive;
31 import java.security.GeneralSecurityException;
32 import java.security.InvalidAlgorithmParameterException;
33 import java.security.InvalidKeyException;
34 import java.security.Key;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.PrivateKey;
37 import java.security.PublicKey;
38 import java.security.Signature;
39 import java.security.SignatureException;
40 import java.text.MessageFormat;
41 import java.util.EnumSet;
42 import java.util.Locale;
43 import java.util.Map;
44 import sun.security.ssl.NamedGroup.NamedGroupSpec;
45 import sun.security.ssl.SSLHandshake.HandshakeMessage;
46 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
47 import sun.security.ssl.X509Authentication.X509Credentials;
48 import sun.security.ssl.X509Authentication.X509Possession;
49 import sun.security.util.HexDumpEncoder;
50 
51 /**
52  * Pack of the ServerKeyExchange handshake message.
53  */
54 final class ECDHServerKeyExchange {
55     static final SSLConsumer ecdheHandshakeConsumer =
56             new ECDHServerKeyExchangeConsumer();
57     static final HandshakeProducer ecdheHandshakeProducer =
58             new ECDHServerKeyExchangeProducer();
59 
60     /**
61      * The ECDH ServerKeyExchange handshake message.
62      */
63     private static final
64             class ECDHServerKeyExchangeMessage extends HandshakeMessage {
65         private static final byte CURVE_NAMED_CURVE = (byte)0x03;
66 
67         // id of the named curve
68         private final NamedGroup namedGroup;
69 
70         // encoded public point
71         private final byte[] publicPoint;
72 
73         // signature bytes, or null if anonymous
74         private final byte[] paramsSignature;
75 
76         private final boolean useExplicitSigAlgorithm;
77 
78         // the signature algorithm used by this ServerKeyExchange message
79         private final SignatureScheme signatureScheme;
80 
81         // the parsed credential object
82         private SSLCredentials sslCredentials;
83 
ECDHServerKeyExchangeMessage( HandshakeContext handshakeContext)84         ECDHServerKeyExchangeMessage(
85                 HandshakeContext handshakeContext) throws IOException {
86             super(handshakeContext);
87 
88             // This happens in server side only.
89             ServerHandshakeContext shc =
90                     (ServerHandshakeContext)handshakeContext;
91 
92             // Find the Possessions needed
93             NamedGroupPossession namedGroupPossession = null;
94             X509Possession x509Possession = null;
95             for (SSLPossession possession : shc.handshakePossessions) {
96                 if (possession instanceof NamedGroupPossession) {
97                     namedGroupPossession = (NamedGroupPossession)possession;
98                     if (x509Possession != null) {
99                         break;
100                     }
101                 } else if (possession instanceof X509Possession) {
102                     x509Possession = (X509Possession)possession;
103                     if (namedGroupPossession != null) {
104                         break;
105                     }
106                 }
107             }
108 
109             if (namedGroupPossession == null) {
110                 // unlikely
111                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
112                     "No ECDHE credentials negotiated for server key exchange");
113             }
114 
115             // Find the NamedGroup used for the ephemeral keys.
116             namedGroup = namedGroupPossession.getNamedGroup();
117             if ((namedGroup == null) || (!namedGroup.isAvailable)) {
118                 // unlikely
119                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
120                     "Missing or improper named group: " + namedGroup);
121             }
122 
123             publicPoint = namedGroup.encodePossessionPublicKey(
124                     namedGroupPossession);
125             if (publicPoint == null) {
126                 // unlikely
127                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
128                     "Missing public point for named group: " + namedGroup);
129             }
130 
131             if (x509Possession == null) {
132                 // anonymous, no authentication, no signature
133                 paramsSignature = null;
134                 signatureScheme = null;
135                 useExplicitSigAlgorithm = false;
136             } else {
137                 useExplicitSigAlgorithm =
138                         shc.negotiatedProtocol.useTLS12PlusSpec();
139                 Signature signer = null;
140                 if (useExplicitSigAlgorithm) {
141                     Map.Entry<SignatureScheme, Signature> schemeAndSigner =
142                             SignatureScheme.getSignerOfPreferableAlgorithm(
143                                 shc.algorithmConstraints,
144                                 shc.peerRequestedSignatureSchemes,
145                                 x509Possession,
146                                 shc.negotiatedProtocol);
147                     if (schemeAndSigner == null) {
148                         // Unlikely, the credentials generator should have
149                         // selected the preferable signature algorithm properly.
150                         throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
151                                 "No supported signature algorithm for " +
152                                 x509Possession.popPrivateKey.getAlgorithm() +
153                                 "  key");
154                     } else {
155                         signatureScheme = schemeAndSigner.getKey();
156                         signer = schemeAndSigner.getValue();
157                     }
158                 } else {
159                     signatureScheme = null;
160                     try {
161                         signer = getSignature(
162                                 x509Possession.popPrivateKey.getAlgorithm(),
163                                 x509Possession.popPrivateKey);
164                     } catch (NoSuchAlgorithmException | InvalidKeyException e) {
165                         throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
166                             "Unsupported signature algorithm: " +
167                             x509Possession.popPrivateKey.getAlgorithm(), e);
168                     }
169                 }
170 
171                 byte[] signature = null;
172                 try {
173                     updateSignature(signer, shc.clientHelloRandom.randomBytes,
174                             shc.serverHelloRandom.randomBytes,
175                             namedGroup.id, publicPoint);
176                     signature = signer.sign();
177                 } catch (SignatureException ex) {
178                     throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
179                         "Failed to sign ecdhe parameters: " +
180                         x509Possession.popPrivateKey.getAlgorithm(), ex);
181                 }
182                 paramsSignature = signature;
183             }
184         }
185 
ECDHServerKeyExchangeMessage(HandshakeContext handshakeContext, ByteBuffer m)186         ECDHServerKeyExchangeMessage(HandshakeContext handshakeContext,
187                 ByteBuffer m) throws IOException {
188             super(handshakeContext);
189 
190             // This happens in client side only.
191             ClientHandshakeContext chc =
192                     (ClientHandshakeContext)handshakeContext;
193 
194             byte curveType = (byte)Record.getInt8(m);
195             if (curveType != CURVE_NAMED_CURVE) {
196                 // Unlikely as only the named curves should be negotiated.
197                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
198                     "Unsupported ECCurveType: " + curveType);
199             }
200 
201             int namedGroupId = Record.getInt16(m);
202             this.namedGroup = NamedGroup.valueOf(namedGroupId);
203             if (namedGroup == null) {
204                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
205                     "Unknown named group ID: " + namedGroupId);
206             }
207 
208             if (!SupportedGroups.isSupported(namedGroup)) {
209                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
210                     "Unsupported named group: " + namedGroup);
211             }
212 
213             publicPoint = Record.getBytes8(m);
214             if (publicPoint.length == 0) {
215                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
216                     "Insufficient Point data: " + namedGroup);
217             }
218 
219             try {
220                 sslCredentials =
221                         namedGroup.decodeCredentials(publicPoint);
222                 if (handshakeContext.algorithmConstraints != null &&
223                         sslCredentials instanceof NamedGroupCredentials) {
224                     NamedGroupCredentials namedGroupCredentials =
225                             (NamedGroupCredentials) sslCredentials;
226                     if (!handshakeContext.algorithmConstraints.permits(
227                             EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
228                             namedGroupCredentials.getPublicKey())) {
229                         chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
230                             "ServerKeyExchange for " + namedGroup +
231                             " does not comply with algorithm constraints");
232                     }
233                 }
234             } catch (GeneralSecurityException ex) {
235                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
236                         "Cannot decode named group: " +
237                         NamedGroup.nameOf(namedGroupId));
238             }
239 
240             X509Credentials x509Credentials = null;
241             for (SSLCredentials cd : chc.handshakeCredentials) {
242                 if (cd instanceof X509Credentials) {
243                     x509Credentials = (X509Credentials)cd;
244                     break;
245                 }
246             }
247 
248             if (x509Credentials == null) {
249                 // anonymous, no authentication, no signature
250                 if (m.hasRemaining()) {
251                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
252                         "Invalid DH ServerKeyExchange: unknown extra data");
253                 }
254                 this.signatureScheme = null;
255                 this.paramsSignature = null;
256                 this.useExplicitSigAlgorithm = false;
257 
258                 return;
259             }
260 
261             this.useExplicitSigAlgorithm =
262                     chc.negotiatedProtocol.useTLS12PlusSpec();
263             if (useExplicitSigAlgorithm) {
264                 int ssid = Record.getInt16(m);
265                 signatureScheme = SignatureScheme.valueOf(ssid);
266                 if (signatureScheme == null) {
267                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
268                         "Invalid signature algorithm (" + ssid +
269                         ") used in ECDH ServerKeyExchange handshake message");
270                 }
271 
272                 if (!chc.localSupportedSignAlgs.contains(signatureScheme)) {
273                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
274                         "Unsupported signature algorithm (" +
275                         signatureScheme.name +
276                         ") used in ECDH ServerKeyExchange handshake message");
277                 }
278             } else {
279                 signatureScheme = null;
280             }
281 
282             // read and verify the signature
283             paramsSignature = Record.getBytes16(m);
284             Signature signer;
285             if (useExplicitSigAlgorithm) {
286                 try {
287                     signer = signatureScheme.getVerifier(
288                             x509Credentials.popPublicKey);
289                 } catch (NoSuchAlgorithmException | InvalidKeyException |
290                         InvalidAlgorithmParameterException nsae) {
291                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
292                         "Unsupported signature algorithm: " +
293                         signatureScheme.name, nsae);
294                 }
295             } else {
296                 try {
297                     signer = getSignature(
298                             x509Credentials.popPublicKey.getAlgorithm(),
299                             x509Credentials.popPublicKey);
300                 } catch (NoSuchAlgorithmException | InvalidKeyException e) {
301                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
302                         "Unsupported signature algorithm: " +
303                         x509Credentials.popPublicKey.getAlgorithm(), e);
304                 }
305             }
306 
307             try {
308                 updateSignature(signer,
309                         chc.clientHelloRandom.randomBytes,
310                         chc.serverHelloRandom.randomBytes,
311                         namedGroup.id, publicPoint);
312 
313                 if (!signer.verify(paramsSignature)) {
314                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
315                         "Invalid ECDH ServerKeyExchange signature");
316                 }
317             } catch (SignatureException ex) {
318                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
319                         "Cannot verify ECDH ServerKeyExchange signature", ex);
320             }
321         }
322 
323         @Override
handshakeType()324         public SSLHandshake handshakeType() {
325             return SSLHandshake.SERVER_KEY_EXCHANGE;
326         }
327 
328         @Override
messageLength()329         public int messageLength() {
330             int sigLen = 0;
331             if (paramsSignature != null) {
332                 sigLen = 2 + paramsSignature.length;
333                 if (useExplicitSigAlgorithm) {
334                     sigLen += SignatureScheme.sizeInRecord();
335                 }
336             }
337 
338             return 4 + publicPoint.length + sigLen;
339         }
340 
341         @Override
send(HandshakeOutStream hos)342         public void send(HandshakeOutStream hos) throws IOException {
343             hos.putInt8(CURVE_NAMED_CURVE);
344             hos.putInt16(namedGroup.id);
345             hos.putBytes8(publicPoint);
346             if (paramsSignature != null) {
347                 if (useExplicitSigAlgorithm) {
348                     hos.putInt16(signatureScheme.id);
349                 }
350 
351                 hos.putBytes16(paramsSignature);
352             }
353         }
354 
355         @Override
toString()356         public String toString() {
357             if (useExplicitSigAlgorithm) {
358                 MessageFormat messageFormat = new MessageFormat(
359                     "\"ECDH ServerKeyExchange\": '{'\n" +
360                     "  \"parameters\": '{'\n" +
361                     "    \"named group\": \"{0}\"\n" +
362                     "    \"ecdh public\": '{'\n" +
363                     "{1}\n" +
364                     "    '}',\n" +
365                     "  '}',\n" +
366                     "  \"digital signature\":  '{'\n" +
367                     "    \"signature algorithm\": \"{2}\"\n" +
368                     "    \"signature\": '{'\n" +
369                     "{3}\n" +
370                     "    '}',\n" +
371                     "  '}'\n" +
372                     "'}'",
373                     Locale.ENGLISH);
374 
375                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
376                 Object[] messageFields = {
377                     namedGroup.name,
378                     Utilities.indent(
379                             hexEncoder.encodeBuffer(publicPoint), "      "),
380                     signatureScheme.name,
381                     Utilities.indent(
382                             hexEncoder.encodeBuffer(paramsSignature), "      ")
383                 };
384                 return messageFormat.format(messageFields);
385             } else if (paramsSignature != null) {
386                 MessageFormat messageFormat = new MessageFormat(
387                     "\"ECDH ServerKeyExchange\": '{'\n" +
388                     "  \"parameters\":  '{'\n" +
389                     "    \"named group\": \"{0}\"\n" +
390                     "    \"ecdh public\": '{'\n" +
391                     "{1}\n" +
392                     "    '}',\n" +
393                     "  '}',\n" +
394                     "  \"signature\": '{'\n" +
395                     "{2}\n" +
396                     "  '}'\n" +
397                     "'}'",
398                     Locale.ENGLISH);
399 
400                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
401                 Object[] messageFields = {
402                     namedGroup.name,
403                     Utilities.indent(
404                             hexEncoder.encodeBuffer(publicPoint), "      "),
405                     Utilities.indent(
406                             hexEncoder.encodeBuffer(paramsSignature), "    ")
407                 };
408 
409                 return messageFormat.format(messageFields);
410             } else {    // anonymous
411                 MessageFormat messageFormat = new MessageFormat(
412                     "\"ECDH ServerKeyExchange\": '{'\n" +
413                     "  \"parameters\":  '{'\n" +
414                     "    \"named group\": \"{0}\"\n" +
415                     "    \"ecdh public\": '{'\n" +
416                     "{1}\n" +
417                     "    '}',\n" +
418                     "  '}'\n" +
419                     "'}'",
420                     Locale.ENGLISH);
421 
422                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
423                 Object[] messageFields = {
424                     namedGroup.name,
425                     Utilities.indent(
426                             hexEncoder.encodeBuffer(publicPoint), "      "),
427                 };
428 
429                 return messageFormat.format(messageFields);
430             }
431         }
432 
getSignature(String keyAlgorithm, Key key)433         private static Signature getSignature(String keyAlgorithm,
434                 Key key) throws NoSuchAlgorithmException, InvalidKeyException {
435             Signature signer = null;
436             switch (keyAlgorithm) {
437                 case "EC":
438                     signer = JsseJce.getSignature(JsseJce.SIGNATURE_ECDSA);
439                     break;
440                 case "RSA":
441                     signer = RSASignature.getInstance();
442                     break;
443                 default:
444                     throw new NoSuchAlgorithmException(
445                         "neither an RSA or a EC key : " + keyAlgorithm);
446             }
447 
448             if (signer != null) {
449                 if (key instanceof PublicKey) {
450                     signer.initVerify((PublicKey)(key));
451                 } else {
452                     signer.initSign((PrivateKey)key);
453                 }
454             }
455 
456             return signer;
457         }
458 
updateSignature(Signature sig, byte[] clntNonce, byte[] svrNonce, int namedGroupId, byte[] publicPoint)459         private static void updateSignature(Signature sig,
460                 byte[] clntNonce, byte[] svrNonce, int namedGroupId,
461                 byte[] publicPoint) throws SignatureException {
462             sig.update(clntNonce);
463             sig.update(svrNonce);
464 
465             sig.update(CURVE_NAMED_CURVE);
466             sig.update((byte)((namedGroupId >> 8) & 0xFF));
467             sig.update((byte)(namedGroupId & 0xFF));
468             sig.update((byte)publicPoint.length);
469             sig.update(publicPoint);
470         }
471     }
472 
473     /**
474      * The ECDH "ServerKeyExchange" handshake message producer.
475      */
476     private static final
477             class ECDHServerKeyExchangeProducer implements HandshakeProducer {
478         // Prevent instantiation of this class.
ECDHServerKeyExchangeProducer()479         private ECDHServerKeyExchangeProducer() {
480             // blank
481         }
482 
483         @Override
produce(ConnectionContext context, HandshakeMessage message)484         public byte[] produce(ConnectionContext context,
485                 HandshakeMessage message) throws IOException {
486             // The producing happens in server side only.
487             ServerHandshakeContext shc = (ServerHandshakeContext)context;
488             ECDHServerKeyExchangeMessage skem =
489                     new ECDHServerKeyExchangeMessage(shc);
490             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
491                 SSLLogger.fine(
492                     "Produced ECDH ServerKeyExchange handshake message", skem);
493             }
494 
495             // Output the handshake message.
496             skem.write(shc.handshakeOutput);
497             shc.handshakeOutput.flush();
498 
499             // The handshake message has been delivered.
500             return null;
501         }
502     }
503 
504     /**
505      * The ECDH "ServerKeyExchange" handshake message consumer.
506      */
507     private static final
508             class ECDHServerKeyExchangeConsumer implements SSLConsumer {
509         // Prevent instantiation of this class.
ECDHServerKeyExchangeConsumer()510         private ECDHServerKeyExchangeConsumer() {
511             // blank
512         }
513 
514         @Override
consume(ConnectionContext context, ByteBuffer message)515         public void consume(ConnectionContext context,
516                 ByteBuffer message) throws IOException {
517             // The consuming happens in client side only.
518             ClientHandshakeContext chc = (ClientHandshakeContext)context;
519 
520             // AlgorithmConstraints are checked during decoding
521             ECDHServerKeyExchangeMessage skem =
522                     new ECDHServerKeyExchangeMessage(chc, message);
523             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
524                 SSLLogger.fine(
525                     "Consuming ECDH ServerKeyExchange handshake message", skem);
526             }
527 
528             //
529             // update
530             //
531             chc.handshakeCredentials.add(skem.sslCredentials);
532 
533             //
534             // produce
535             //
536             // Need no new handshake message producers here.
537         }
538     }
539 }
540 
541