• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 package com.google.crypto.tink.internal;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertThrows;
22 import static org.junit.Assert.assertTrue;
23 
24 import com.google.crypto.tink.InsecureSecretKeyAccess;
25 import com.google.crypto.tink.KeyManager;
26 import com.google.crypto.tink.Mac;
27 import com.google.crypto.tink.PrivateKeyManager;
28 import com.google.crypto.tink.PublicKeySign;
29 import com.google.crypto.tink.PublicKeyVerify;
30 import com.google.crypto.tink.mac.internal.HmacProtoSerialization;
31 import com.google.crypto.tink.proto.EcdsaKeyFormat;
32 import com.google.crypto.tink.proto.EcdsaParams;
33 import com.google.crypto.tink.proto.EcdsaPrivateKey;
34 import com.google.crypto.tink.proto.EcdsaPublicKey;
35 import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
36 import com.google.crypto.tink.proto.EllipticCurveType;
37 import com.google.crypto.tink.proto.HashType;
38 import com.google.crypto.tink.proto.HmacKey;
39 import com.google.crypto.tink.proto.HmacKeyFormat;
40 import com.google.crypto.tink.proto.HmacParams;
41 import com.google.crypto.tink.proto.KeyData;
42 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
43 import com.google.crypto.tink.signature.EcdsaParameters;
44 import com.google.crypto.tink.signature.internal.EcdsaProtoSerialization;
45 import com.google.crypto.tink.subtle.EcdsaSignJce;
46 import com.google.crypto.tink.subtle.EcdsaVerifyJce;
47 import com.google.crypto.tink.subtle.Hex;
48 import com.google.crypto.tink.subtle.PrfMac;
49 import com.google.crypto.tink.util.SecretBigInteger;
50 import com.google.crypto.tink.util.SecretBytes;
51 import com.google.protobuf.ByteString;
52 import com.google.protobuf.ExtensionRegistryLite;
53 import java.math.BigInteger;
54 import java.security.GeneralSecurityException;
55 import java.security.KeyPair;
56 import java.security.KeyPairGenerator;
57 import java.security.interfaces.ECPrivateKey;
58 import java.security.interfaces.ECPublicKey;
59 import java.security.spec.ECPoint;
60 import javax.annotation.Nullable;
61 import org.junit.BeforeClass;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.junit.runners.JUnit4;
65 
66 @RunWith(JUnit4.class)
67 public final class LegacyKeyManagerImplTest {
68 
69   private static KeyManager<Mac> keyManager;
70   private static PrivateKeyManager<PublicKeySign> privateKeyManager;
71 
createHmacKey( com.google.crypto.tink.mac.HmacParameters parameters, @Nullable Integer idRequirement)72   private static com.google.crypto.tink.mac.HmacKey createHmacKey(
73       com.google.crypto.tink.mac.HmacParameters parameters, @Nullable Integer idRequirement)
74       throws GeneralSecurityException {
75     return com.google.crypto.tink.mac.HmacKey.builder()
76         .setParameters(parameters)
77         .setKeyBytes(SecretBytes.randomBytes(parameters.getKeySizeBytes()))
78         .setIdRequirement(idRequirement)
79         .build();
80   }
81 
createEcdsaPrivateKey( EcdsaParameters parameters, @Nullable Integer idRequirement)82   private static com.google.crypto.tink.signature.EcdsaPrivateKey createEcdsaPrivateKey(
83       EcdsaParameters parameters, @Nullable Integer idRequirement) throws GeneralSecurityException {
84     KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
85     keyGen.initialize(parameters.getCurveType().toParameterSpec());
86     KeyPair keyPair = keyGen.generateKeyPair();
87     ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate();
88     ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
89 
90     com.google.crypto.tink.signature.EcdsaPublicKey publicKey =
91         com.google.crypto.tink.signature.EcdsaPublicKey.builder()
92             .setParameters(parameters)
93             .setPublicPoint(ecPublicKey.getW())
94             .setIdRequirement(idRequirement)
95             .build();
96     return com.google.crypto.tink.signature.EcdsaPrivateKey.builder()
97         .setPublicKey(publicKey)
98         .setPrivateValue(
99             SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get()))
100         .build();
101   }
102 
103   @BeforeClass
register()104   public static void register() throws GeneralSecurityException {
105     HmacProtoSerialization.register();
106     MutablePrimitiveRegistry.globalInstance()
107         .registerPrimitiveConstructor(
108             PrimitiveConstructor.create(
109                 PrfMac::create, com.google.crypto.tink.mac.HmacKey.class, Mac.class));
110     MutableKeyCreationRegistry.globalInstance()
111         .add(
112             LegacyKeyManagerImplTest::createHmacKey,
113             com.google.crypto.tink.mac.HmacParameters.class);
114 
115     keyManager =
116         LegacyKeyManagerImpl.create(
117             "type.googleapis.com/google.crypto.tink.HmacKey",
118             Mac.class,
119             KeyMaterialType.SYMMETRIC,
120             HmacKey.parser());
121 
122     EcdsaProtoSerialization.register();
123     MutablePrimitiveRegistry.globalInstance()
124         .registerPrimitiveConstructor(
125             PrimitiveConstructor.create(
126                 EcdsaSignJce::create,
127                 com.google.crypto.tink.signature.EcdsaPrivateKey.class,
128                 PublicKeySign.class));
129     MutableKeyCreationRegistry.globalInstance()
130         .add(LegacyKeyManagerImplTest::createEcdsaPrivateKey, EcdsaParameters.class);
131     privateKeyManager =
132         LegacyKeyManagerImpl.createPrivateKeyManager(
133             "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey",
134             PublicKeySign.class,
135             EcdsaPrivateKey.parser());
136   }
137 
138   @Test
getPrimitive_messageLite_works()139   public void getPrimitive_messageLite_works() throws Exception {
140     HmacKey key =
141         HmacKey.newBuilder()
142             .setVersion(0)
143             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
144             .setKeyValue(
145                 ByteString.copyFrom(
146                     Hex.decode("816aa4c3ee066310ac1e6666cf830c375355c3c8ba18cfe1f50a48c988b46272")))
147             .build();
148 
149     Mac mac = keyManager.getPrimitive(key);
150     byte[] message =
151         Hex.decode(
152             "220248f5e6d7a49335b3f91374f18bb8b0ff5e8b9a5853f3cfb293855d78301d837a0a2eb9e4f056f06c08361"
153                 + "bd07180ee802651e69726c28910d2baef379606815dcbab01d0dc7acb0ba8e65a2928130da0522f2b2b3d05260"
154                 + "885cf1c64f14ca3145313c685b0274bf6a1cb38e4f99895c6a8cc72fbe0e52c01766fede78a1a");
155     byte[] tag = Hex.decode("17cb2e9e98b748b5ae0f7078ea5519e5");
156 
157     mac.verifyMac(tag, message);
158   }
159 
160   @Test
getPrimitive_byteString_works()161   public void getPrimitive_byteString_works() throws Exception {
162     HmacKey key =
163         HmacKey.newBuilder()
164             .setVersion(0)
165             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
166             .setKeyValue(
167                 ByteString.copyFrom(
168                     Hex.decode("816aa4c3ee066310ac1e6666cf830c375355c3c8ba18cfe1f50a48c988b46272")))
169             .build();
170 
171     Mac mac = keyManager.getPrimitive(key.toByteString());
172     byte[] message =
173         Hex.decode(
174             "220248f5e6d7a49335b3f91374f18bb8b0ff5e8b9a5853f3cfb293855d78301d837a0a2eb9e4f056f06c08361"
175                 + "bd07180ee802651e69726c28910d2baef379606815dcbab01d0dc7acb0ba8e65a2928130da0522f2b2b3d05260"
176                 + "885cf1c64f14ca3145313c685b0274bf6a1cb38e4f99895c6a8cc72fbe0e52c01766fede78a1a");
177     byte[] tag = Hex.decode("17cb2e9e98b748b5ae0f7078ea5519e5");
178 
179     mac.verifyMac(tag, message);
180   }
181 
182   @Test
getPrimitive_invalidKey_throws()183   public void getPrimitive_invalidKey_throws() throws Exception {
184     HmacKey key =
185         HmacKey.newBuilder()
186             .setVersion(0)
187             .setParams(HmacParams.newBuilder().setHash(HashType.UNKNOWN_HASH).setTagSize(16))
188             .build();
189 
190     assertThrows(GeneralSecurityException.class, () -> keyManager.getPrimitive(key));
191   }
192 
193   @Test
newKey_byteString_works()194   public void newKey_byteString_works() throws Exception {
195     HmacKeyFormat keyFormat =
196         HmacKeyFormat.newBuilder()
197             .setKeySize(32)
198             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
199             .build();
200 
201     HmacKey key1 = (HmacKey) keyManager.newKey(keyFormat.toByteString());
202     HmacKey key2 = (HmacKey) keyManager.newKey(keyFormat.toByteString());
203     assertThat(key1.getKeyValue().size()).isEqualTo(32);
204     assertThat(key1.getKeyValue()).isNotEqualTo(key2.getKeyValue());
205     assertThat(key1.getParams()).isEqualTo(keyFormat.getParams());
206   }
207 
208   @Test
newKey_messageLite_works()209   public void newKey_messageLite_works() throws Exception {
210     HmacKeyFormat keyFormat =
211         HmacKeyFormat.newBuilder()
212             .setKeySize(32)
213             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
214             .build();
215 
216     HmacKey key1 = (HmacKey) keyManager.newKey(keyFormat);
217     HmacKey key2 = (HmacKey) keyManager.newKey(keyFormat);
218     assertThat(key1.getKeyValue().size()).isEqualTo(32);
219     assertThat(key1.getKeyValue()).isNotEqualTo(key2.getKeyValue());
220     assertThat(key1.getParams()).isEqualTo(keyFormat.getParams());
221   }
222 
223   @Test
newKeyData_works()224   public void newKeyData_works() throws Exception {
225     HmacKeyFormat keyFormat =
226         HmacKeyFormat.newBuilder()
227             .setKeySize(32)
228             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
229             .build();
230 
231     KeyData keyData1 = keyManager.newKeyData(keyFormat.toByteString());
232     KeyData keyData2 = keyManager.newKeyData(keyFormat.toByteString());
233     assertThat(keyData1.getKeyMaterialType()).isEqualTo(KeyMaterialType.SYMMETRIC);
234     assertThat(keyData1.getTypeUrl()).isEqualTo("type.googleapis.com/google.crypto.tink.HmacKey");
235     HmacKey key1 = HmacKey.parseFrom(keyData1.getValue(), ExtensionRegistryLite.getEmptyRegistry());
236     HmacKey key2 = HmacKey.parseFrom(keyData2.getValue(), ExtensionRegistryLite.getEmptyRegistry());
237     assertThat(key1.getParams()).isEqualTo(keyFormat.getParams());
238     assertThat(key1.getKeyValue().size()).isEqualTo(32);
239     assertThat(key1.getKeyValue()).isNotEqualTo(key2.getKeyValue());
240   }
241 
242   @Test
doesSupport_works()243   public void doesSupport_works() throws Exception {
244     assertTrue(keyManager.doesSupport("type.googleapis.com/google.crypto.tink.HmacKey"));
245     assertFalse(keyManager.doesSupport("type.googleapis.com/google.crypto.tink.SomeOtherKey"));
246   }
247 
248   @Test
getKeyType_works()249   public void getKeyType_works() throws Exception {
250     assertThat(keyManager.getKeyType()).isEqualTo("type.googleapis.com/google.crypto.tink.HmacKey");
251   }
252 
253   @Test
getVersion_works()254   public void getVersion_works() throws Exception {
255     assertThat(keyManager.getVersion()).isEqualTo(0);
256   }
257 
getHexX()258   private static String getHexX() {
259     return "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6";
260   }
261 
getHexY()262   private static String getHexY() {
263     return "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299";
264   }
265 
getHexPrivateValue()266   private static String getHexPrivateValue() {
267     return "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721";
268   }
269 
getProtoPrivateKey()270   private static com.google.crypto.tink.proto.EcdsaPrivateKey getProtoPrivateKey() {
271     com.google.crypto.tink.proto.EcdsaPublicKey protoPublicKey =
272         com.google.crypto.tink.proto.EcdsaPublicKey.newBuilder()
273             .setVersion(0)
274             .setX(ByteString.copyFrom(Hex.decode("00" + getHexX())))
275             .setY(ByteString.copyFrom(Hex.decode("00" + getHexY())))
276             .setParams(
277                 EcdsaParams.newBuilder()
278                     .setHashType(HashType.SHA256)
279                     .setCurve(EllipticCurveType.NIST_P256)
280                     .setEncoding(EcdsaSignatureEncoding.IEEE_P1363))
281             .build();
282     return com.google.crypto.tink.proto.EcdsaPrivateKey.newBuilder()
283         .setVersion(0)
284         .setPublicKey(protoPublicKey)
285         // privateValue is currently serialized with an extra zero at the beginning.
286         .setKeyValue(ByteString.copyFrom(Hex.decode("00" + getHexPrivateValue())))
287         .build();
288   }
289 
getPlainJavaPrivateKey()290   private static com.google.crypto.tink.signature.EcdsaPrivateKey getPlainJavaPrivateKey()
291       throws GeneralSecurityException {
292     com.google.crypto.tink.signature.EcdsaPublicKey publicKey =
293         com.google.crypto.tink.signature.EcdsaPublicKey.builder()
294             .setParameters(
295                 EcdsaParameters.builder()
296                     .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
297                     .setCurveType(EcdsaParameters.CurveType.NIST_P256)
298                     .setHashType(EcdsaParameters.HashType.SHA256)
299                     .setVariant(EcdsaParameters.Variant.NO_PREFIX)
300                     .build())
301             .setPublicPoint(
302                 new ECPoint(new BigInteger(getHexX(), 16), new BigInteger(getHexY(), 16)))
303             .build();
304     return com.google.crypto.tink.signature.EcdsaPrivateKey.builder()
305         .setPublicKey(publicKey)
306         .setPrivateValue(
307             SecretBigInteger.fromBigInteger(
308                 new BigInteger(getHexPrivateValue(), 16), InsecureSecretKeyAccess.get()))
309         .build();
310   }
311 
312   @Test
getPrimitiveClass_works()313   public void getPrimitiveClass_works() throws Exception {
314     assertThat(keyManager.getPrimitiveClass()).isEqualTo(Mac.class);
315   }
316 
317   @Test
privateKeyManager_getPrimitive_messageLite_works()318   public void privateKeyManager_getPrimitive_messageLite_works() throws Exception {
319     PublicKeySign signer = privateKeyManager.getPrimitive(getProtoPrivateKey());
320     PublicKeyVerify verifier = EcdsaVerifyJce.create(getPlainJavaPrivateKey().getPublicKey());
321     byte[] message = new byte[] {};
322     verifier.verify(signer.sign(message), message);
323   }
324 
325   @Test
privateKeyManager_getPrimitive_byteString_works()326   public void privateKeyManager_getPrimitive_byteString_works() throws Exception {
327     PublicKeySign signer = privateKeyManager.getPrimitive(getProtoPrivateKey().toByteString());
328     PublicKeyVerify verifier = EcdsaVerifyJce.create(getPlainJavaPrivateKey().getPublicKey());
329     byte[] message = new byte[] {};
330     verifier.verify(signer.sign(message), message);
331   }
332 
333   @Test
privateKeyManager_getPrimitive_invalidKey_throws()334   public void privateKeyManager_getPrimitive_invalidKey_throws() throws Exception {
335     EcdsaPrivateKey key = EcdsaPrivateKey.getDefaultInstance();
336 
337     assertThrows(GeneralSecurityException.class, () -> keyManager.getPrimitive(key));
338   }
339 
340   @Test
privateKeyManager_newKey_byteString_works()341   public void privateKeyManager_newKey_byteString_works() throws Exception {
342     EcdsaKeyFormat keyFormat =
343         EcdsaKeyFormat.newBuilder()
344             .setParams(
345                 EcdsaParams.newBuilder()
346                     .setHashType(HashType.SHA256)
347                     .setCurve(EllipticCurveType.NIST_P256)
348                     .setEncoding(EcdsaSignatureEncoding.IEEE_P1363))
349             .build();
350 
351     EcdsaPrivateKey key1 = (EcdsaPrivateKey) privateKeyManager.newKey(keyFormat.toByteString());
352     EcdsaPrivateKey key2 = (EcdsaPrivateKey) privateKeyManager.newKey(keyFormat.toByteString());
353     assertThat(key1.getKeyValue().size()).isEqualTo(33);
354     assertThat(key1.getKeyValue()).isNotEqualTo(key2.getKeyValue());
355     assertThat(key1.getPublicKey().getParams()).isEqualTo(keyFormat.getParams());
356   }
357 
358   @Test
privateKeyManager_newKey_messageLite_works()359   public void privateKeyManager_newKey_messageLite_works() throws Exception {
360     EcdsaKeyFormat keyFormat =
361         EcdsaKeyFormat.newBuilder()
362             .setParams(
363                 EcdsaParams.newBuilder()
364                     .setHashType(HashType.SHA256)
365                     .setCurve(EllipticCurveType.NIST_P256)
366                     .setEncoding(EcdsaSignatureEncoding.IEEE_P1363))
367             .build();
368 
369     EcdsaPrivateKey key1 = (EcdsaPrivateKey) privateKeyManager.newKey(keyFormat);
370     EcdsaPrivateKey key2 = (EcdsaPrivateKey) privateKeyManager.newKey(keyFormat);
371     assertThat(key1.getKeyValue().size()).isEqualTo(33);
372     assertThat(key1.getKeyValue()).isNotEqualTo(key2.getKeyValue());
373     assertThat(key1.getPublicKey().getParams()).isEqualTo(keyFormat.getParams());
374   }
375 
376   @Test
privateKeyManager_newKeyData_works()377   public void privateKeyManager_newKeyData_works() throws Exception {
378     EcdsaKeyFormat keyFormat =
379         EcdsaKeyFormat.newBuilder()
380             .setParams(
381                 EcdsaParams.newBuilder()
382                     .setHashType(HashType.SHA256)
383                     .setCurve(EllipticCurveType.NIST_P256)
384                     .setEncoding(EcdsaSignatureEncoding.IEEE_P1363))
385             .build();
386 
387     KeyData keyData1 = privateKeyManager.newKeyData(keyFormat.toByteString());
388     KeyData keyData2 = privateKeyManager.newKeyData(keyFormat.toByteString());
389     assertThat(keyData1.getKeyMaterialType()).isEqualTo(KeyMaterialType.ASYMMETRIC_PRIVATE);
390 
391     assertThat(keyData1.getTypeUrl())
392         .isEqualTo("type.googleapis.com/google.crypto.tink.EcdsaPrivateKey");
393     EcdsaPrivateKey protoKey1 =
394         EcdsaPrivateKey.parseFrom(keyData1.getValue(), ExtensionRegistryLite.getEmptyRegistry());
395     EcdsaPrivateKey protoKey2 =
396         EcdsaPrivateKey.parseFrom(keyData2.getValue(), ExtensionRegistryLite.getEmptyRegistry());
397     assertThat(protoKey1.getKeyValue().size()).isEqualTo(33);
398     assertThat(protoKey1.getKeyValue()).isNotEqualTo(protoKey2.getKeyValue());
399     assertThat(protoKey1.getPublicKey().getParams()).isEqualTo(keyFormat.getParams());
400   }
401 
402   @Test
privateKeyManager_doesSupport_works()403   public void privateKeyManager_doesSupport_works() throws Exception {
404     assertTrue(
405         privateKeyManager.doesSupport("type.googleapis.com/google.crypto.tink.EcdsaPrivateKey"));
406     assertFalse(
407         privateKeyManager.doesSupport("type.googleapis.com/google.crypto.tink.SomeOtherKey"));
408   }
409 
410   @Test
privateKeyManager_getKeyType_works()411   public void privateKeyManager_getKeyType_works() throws Exception {
412 
413     assertThat(privateKeyManager.getKeyType())
414         .isEqualTo("type.googleapis.com/google.crypto.tink.EcdsaPrivateKey");
415   }
416 
417   @Test
privateKeyManager_getVersion_works()418   public void privateKeyManager_getVersion_works() throws Exception {
419     assertThat(privateKeyManager.getVersion()).isEqualTo(0);
420   }
421 
422   @Test
privateKeyManager_getPrimitiveClass_works()423   public void privateKeyManager_getPrimitiveClass_works() throws Exception {
424     assertThat(privateKeyManager.getPrimitiveClass()).isEqualTo(PublicKeySign.class);
425   }
426 
427   @Test
privateKeyManager_getPublicKey_works()428   public void privateKeyManager_getPublicKey_works() throws Exception {
429     KeyData publicKeyData = privateKeyManager.getPublicKeyData(getProtoPrivateKey().toByteString());
430     assertThat(publicKeyData.getTypeUrl())
431         .isEqualTo("type.googleapis.com/google.crypto.tink.EcdsaPublicKey");
432     assertThat(publicKeyData.getKeyMaterialType()).isEqualTo(KeyMaterialType.ASYMMETRIC_PUBLIC);
433 
434     EcdsaPublicKey publicKey =
435         EcdsaPublicKey.parseFrom(
436             publicKeyData.getValue(), ExtensionRegistryLite.getEmptyRegistry());
437     assertThat(publicKey).isEqualTo(getProtoPrivateKey().getPublicKey());
438   }
439 
440   @Test
privateKeyManager_getPublicKey_notAPrivateKey_throws()441   public void privateKeyManager_getPublicKey_notAPrivateKey_throws() throws Exception {
442     // To test how LegacyKeyManagerImpl.createPrivateKeyManager fails when we use it with a
443     // symmetric key, we simply use it with the same HmacKey as above.
444     PrivateKeyManager<Mac> privateKeyManager =
445         LegacyKeyManagerImpl.createPrivateKeyManager(
446             "type.googleapis.com/google.crypto.tink.HmacKey", Mac.class, HmacKey.parser());
447 
448     HmacKey key =
449         HmacKey.newBuilder()
450             .setVersion(0)
451             .setParams(HmacParams.newBuilder().setHash(HashType.SHA1).setTagSize(16))
452             .setKeyValue(
453                 ByteString.copyFrom(
454                     Hex.decode("816aa4c3ee066310ac1e6666cf830c375355c3c8ba18cfe1f50a48c988b46272")))
455             .build();
456 
457     GeneralSecurityException exception =
458         assertThrows(
459             GeneralSecurityException.class,
460             () -> privateKeyManager.getPublicKeyData(key.toByteString()));
461     assertThat(exception).hasMessageThat().contains("Key not private key");
462   }
463 }
464