1 // Copyright 2020 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 // https://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 package com.google.security.cryptauth.lib.securemessage; 16 17 import com.google.protobuf.InvalidProtocolBufferException; 18 import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; 19 import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; 20 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; 21 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header; 22 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody; 23 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; 24 import java.security.KeyFactory; 25 import java.security.KeyPair; 26 import java.security.NoSuchAlgorithmException; 27 import java.security.PrivateKey; 28 import java.security.PublicKey; 29 import java.security.spec.InvalidKeySpecException; 30 import java.security.spec.PKCS8EncodedKeySpec; 31 import java.util.Arrays; 32 import javax.crypto.KeyGenerator; 33 import javax.crypto.SecretKey; 34 import javax.crypto.spec.SecretKeySpec; 35 import junit.framework.TestCase; 36 37 /** 38 * Tests the library against some very basic test vectors, to help ensure wire-format 39 * compatibility is not broken. 40 */ 41 public class SecureMessageSimpleTestVectorTest extends TestCase { 42 43 private static final KeyFactory EC_KEY_FACTORY; 44 static { 45 try { 46 if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { 47 EC_KEY_FACTORY = null; 48 } else { 49 EC_KEY_FACTORY = KeyFactory.getInstance("EC"); 50 } 51 } catch (Exception e) { 52 throw new RuntimeException(e); 53 } 54 } 55 56 private static final byte[] TEST_ASSOCIATED_DATA = { 57 11, 22, 33, 44, 55 58 }; 59 private static final byte[] TEST_METADATA = { 60 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 61 }; 62 private static final byte[] TEST_VKID = { 63 0, 0, 1 64 }; 65 private static final byte[] TEST_DKID = { 66 -1, -1, 0, 67 }; 68 private static final byte[] TEST_MESSAGE = { 69 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90 70 }; 71 72 // The following fields are initialized below, in a static block that contains auto-generated test 73 // vectors. Initialization can't just be done inline due to code that throws checked exceptions. 74 private static final PublicKey TEST_EC_PUBLIC_KEY; 75 private static final PrivateKey TEST_EC_PRIVATE_KEY; 76 private static final SecretKey TEST_KEY1; 77 private static final SecretKey TEST_KEY2; 78 private static final byte[] TEST_VECTOR_ECDSA_ONLY; 79 private static final byte[] TEST_VECTOR_ECDSA_AND_AES; 80 private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS; 81 private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS; 82 testEcdsaOnly()83 public void testEcdsaOnly() throws Exception { 84 if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { 85 // On older Android platforms we can't run this test. 86 return; 87 } 88 SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY); 89 Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); 90 HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage( 91 testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA); 92 assertTrue(Arrays.equals( 93 unverifiedHeader.toByteArray(), 94 headerAndBody.getHeader().toByteArray())); 95 assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); 96 assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); 97 assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); 98 assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); 99 assertFalse(unverifiedHeader.hasDecryptionKeyId()); 100 } 101 testEcdsaAndAes()102 public void testEcdsaAndAes() throws Exception { 103 if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { 104 // On older Android platforms we can't run this test. 105 return; 106 } 107 SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES); 108 Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); 109 HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( 110 testVector, 111 TEST_EC_PUBLIC_KEY, 112 SigType.ECDSA_P256_SHA256, 113 TEST_KEY1, 114 EncType.AES_256_CBC, 115 TEST_ASSOCIATED_DATA); 116 assertTrue(Arrays.equals( 117 unverifiedHeader.toByteArray(), 118 headerAndBody.getHeader().toByteArray())); 119 assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); 120 assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); 121 assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); 122 assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); 123 assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); 124 } 125 testHmacAndAesSameKeys()126 public void testHmacAndAesSameKeys() throws Exception { 127 SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS); 128 Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); 129 130 HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( 131 testVector, 132 TEST_KEY1, 133 SigType.HMAC_SHA256, 134 TEST_KEY1, 135 EncType.AES_256_CBC, 136 TEST_ASSOCIATED_DATA); 137 assertTrue(Arrays.equals( 138 unverifiedHeader.toByteArray(), 139 headerAndBody.getHeader().toByteArray())); 140 assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); 141 assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); 142 assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); 143 assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); 144 assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); 145 } 146 testHmacAndAesDifferentKeys()147 public void testHmacAndAesDifferentKeys() throws Exception { 148 SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS); 149 Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); 150 HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( 151 testVector, 152 TEST_KEY1, 153 SigType.HMAC_SHA256, 154 TEST_KEY2, 155 EncType.AES_256_CBC, 156 TEST_ASSOCIATED_DATA); 157 assertTrue(Arrays.equals( 158 unverifiedHeader.toByteArray(), 159 headerAndBody.getHeader().toByteArray())); 160 assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); 161 assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); 162 assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); 163 assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); 164 assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); 165 } 166 167 /** 168 * This code emits the test vectors to {@code System.out}. It will not generate fresh test 169 * vectors unless an existing test vector is set to {@code null}, but it contains all of the code 170 * used to the generate the test vector values. Ideally, existing test vectors should never be 171 * regenerated, but having this code available should make it easier to add new test vectors. 172 */ testGenerateTestVectorsPseudoTest()173 public void testGenerateTestVectorsPseudoTest() throws Exception { 174 if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { 175 // On older Android platforms we can't run this test. 176 return; 177 } 178 System.out.printf(" static {\n try {\n"); 179 String indent = " "; 180 PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY; 181 PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY; 182 if (testEcPublicKey == null) { 183 KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); 184 testEcPublicKey = testEcKeyPair.getPublic(); 185 testEcPrivateKey = testEcKeyPair.getPrivate(); 186 } 187 System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n", 188 indent, 189 "TEST_EC_PUBLIC_KEY", 190 byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey))); 191 System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n", 192 indent, 193 "TEST_EC_PRIVATE_KEY", 194 byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey))); 195 196 SecretKey testKey1 = TEST_KEY1; 197 if (testKey1 == null) { 198 testKey1 = makeAesKey(); 199 } 200 System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n", 201 indent, 202 "TEST_KEY1", 203 byteArrayToJavaCode(indent, testKey1.getEncoded())); 204 205 SecretKey testKey2 = TEST_KEY2; 206 if (testKey2 == null) { 207 testKey2 = makeAesKey(); 208 } 209 System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n", 210 indent, 211 "TEST_KEY2", 212 byteArrayToJavaCode(indent, testKey2.getEncoded())); 213 214 byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY; 215 if (testVectorEcdsaOnly == null) { 216 testVectorEcdsaOnly = new SecureMessageBuilder() 217 .setAssociatedData(TEST_ASSOCIATED_DATA) 218 .setPublicMetadata(TEST_METADATA) 219 .setVerificationKeyId(TEST_VKID) 220 .buildSignedCleartextMessage( 221 testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray(); 222 } 223 printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly); 224 225 byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES; 226 if (testVectorEcdsaAndAes == null) { 227 testVectorEcdsaAndAes = new SecureMessageBuilder() 228 .setAssociatedData(TEST_ASSOCIATED_DATA) 229 .setDecryptionKeyId(TEST_DKID) 230 .setPublicMetadata(TEST_METADATA) 231 .setVerificationKeyId(TEST_VKID) 232 .buildSignCryptedMessage( 233 testEcPrivateKey, 234 SigType.ECDSA_P256_SHA256, 235 testKey1, 236 EncType.AES_256_CBC, 237 TEST_MESSAGE).toByteArray(); 238 } 239 printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes); 240 241 byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS; 242 if (testVectorHmacAndAesSameKeys == null) { 243 testVectorHmacAndAesSameKeys = new SecureMessageBuilder() 244 .setAssociatedData(TEST_ASSOCIATED_DATA) 245 .setDecryptionKeyId(TEST_DKID) 246 .setPublicMetadata(TEST_METADATA) 247 .setVerificationKeyId(TEST_VKID) 248 .buildSignCryptedMessage( 249 testKey1, 250 SigType.HMAC_SHA256, 251 testKey1, 252 EncType.AES_256_CBC, 253 TEST_MESSAGE).toByteArray(); 254 } 255 printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys); 256 257 byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS; 258 if (testVectorHmacAndAesDifferentKeys == null) { 259 testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder() 260 .setAssociatedData(TEST_ASSOCIATED_DATA) 261 .setDecryptionKeyId(TEST_DKID) 262 .setPublicMetadata(TEST_METADATA) 263 .setVerificationKeyId(TEST_VKID) 264 .buildSignCryptedMessage( 265 testKey1, 266 SigType.HMAC_SHA256, 267 testKey2, 268 EncType.AES_256_CBC, 269 TEST_MESSAGE).toByteArray(); 270 } 271 printInitializerFor( 272 indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys); 273 274 System.out.printf( 275 " } catch (Exception e) {\n throw new RuntimeException(e);\n }\n }\n"); 276 } 277 makeAesKey()278 private SecretKey makeAesKey() throws NoSuchAlgorithmException { 279 KeyGenerator aesKeygen = KeyGenerator.getInstance("AES"); 280 aesKeygen.init(256); 281 return aesKeygen.generateKey(); 282 } 283 printInitializerFor(String indent, String name, byte[] value)284 private void printInitializerFor(String indent, String name, byte[] value) { 285 System.out.printf("%s%s = new byte[] %s;\n", 286 indent, 287 name, 288 byteArrayToJavaCode(indent, value)); 289 } 290 byteArrayToJavaCode(String lineIndent, byte[] array)291 private static String byteArrayToJavaCode(String lineIndent, byte[] array) { 292 String newline = "\n" + lineIndent + " "; 293 String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", ""); 294 int wrapAfter = 16; 295 int count = wrapAfter; 296 StringBuilder result = new StringBuilder("{"); 297 for (String entry : unwrappedArray.split(" ")) { 298 if (++count > wrapAfter) { 299 result.append(newline); 300 count = 0; 301 } else { 302 result.append(" "); 303 } 304 result.append(entry); 305 } 306 result.append(" }"); 307 return result.toString(); 308 } 309 encodePublicKey(PublicKey pk)310 private static byte[] encodePublicKey(PublicKey pk) { 311 return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray(); 312 } 313 parsePublicKey(byte[] encodedPk)314 private static PublicKey parsePublicKey(byte[] encodedPk) 315 throws InvalidKeySpecException, InvalidProtocolBufferException { 316 GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk); 317 if (PublicKeyProtoUtil.isLegacyCryptoRequired() 318 && gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) { 319 return null; 320 } 321 return PublicKeyProtoUtil.parsePublicKey(gpk); 322 } 323 encodeEcPrivateKey(PrivateKey sk)324 private static byte[] encodeEcPrivateKey(PrivateKey sk) { 325 return sk.getEncoded(); 326 } 327 parseEcPrivateKey(byte[] sk)328 private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException { 329 if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { 330 return null; 331 } 332 return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk)); 333 } 334 335 // The following block of code was automatically generated by cut and pasting the output of the 336 // generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please 337 // DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing. 338 // 339 // --- AUTO GENERATED CODE BEGINS HERE --- 340 static { 341 try { 342 TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] { 343 8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17, 344 -126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19, 345 52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116, 346 64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83, 347 41, 95, -24, -114, 23, -5 }); 348 TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] { 349 48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 350 8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4, 351 32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81, 352 51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 }); 353 TEST_KEY1 = new SecretKeySpec(new byte[] { 354 -89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37, 355 -7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES"); 356 TEST_KEY2 = new SecretKeySpec(new byte[] { 357 -6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119, 358 -113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES"); 359 TEST_VECTOR_ECDSA_ONLY = new byte[] { 360 10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11, 361 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 362 56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 363 93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50, 364 21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33, 365 11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44, 366 52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63, 367 43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 }; 368 TEST_VECTOR_ECDSA_AND_AES = new byte[] { 369 10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, 370 0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115, 371 -111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 372 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4, 373 32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34, 374 50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62, 375 -76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11, 376 -70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122, 377 56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35, 378 21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84, 379 -29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 }; 380 TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] { 381 10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, 382 0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70, 383 121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 384 23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88, 385 -33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38, 386 -4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53, 387 37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79, 388 94, -7, -19, -41, -47, -29, 1, 12 }; 389 TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] { 390 10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, 391 0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75, 392 -127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 393 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47, 394 98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114, 395 -48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72, 396 10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78, 397 97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9, 398 -91, 115, 42, 24, 49, -76, -111 }; 399 } catch (Exception e) { 400 throw new RuntimeException(e); 401 } 402 } 403 } 404