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