blob: 285b259658710547f8ac2e871ad7361db6de94a7 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.security.cryptauth.lib.securemessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import junit.framework.TestCase;
/**
* Tests the library against some very basic test vectors, to help ensure wire-format
* compatibility is not broken.
*/
public class SecureMessageSimpleTestVectorTest extends TestCase {
private static final KeyFactory EC_KEY_FACTORY;
static {
try {
if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
EC_KEY_FACTORY = null;
} else {
EC_KEY_FACTORY = KeyFactory.getInstance("EC");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static final byte[] TEST_ASSOCIATED_DATA = {
11, 22, 33, 44, 55
};
private static final byte[] TEST_METADATA = {
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
};
private static final byte[] TEST_VKID = {
0, 0, 1
};
private static final byte[] TEST_DKID = {
-1, -1, 0,
};
private static final byte[] TEST_MESSAGE = {
0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90
};
// The following fields are initialized below, in a static block that contains auto-generated test
// vectors. Initialization can't just be done inline due to code that throws checked exceptions.
private static final PublicKey TEST_EC_PUBLIC_KEY;
private static final PrivateKey TEST_EC_PRIVATE_KEY;
private static final SecretKey TEST_KEY1;
private static final SecretKey TEST_KEY2;
private static final byte[] TEST_VECTOR_ECDSA_ONLY;
private static final byte[] TEST_VECTOR_ECDSA_AND_AES;
private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
public void testEcdsaOnly() throws Exception {
if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
// On older Android platforms we can't run this test.
return;
}
SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY);
Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA);
assertTrue(Arrays.equals(
unverifiedHeader.toByteArray(),
headerAndBody.getHeader().toByteArray()));
assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
assertFalse(unverifiedHeader.hasDecryptionKeyId());
}
public void testEcdsaAndAes() throws Exception {
if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
// On older Android platforms we can't run this test.
return;
}
SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES);
Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
testVector,
TEST_EC_PUBLIC_KEY,
SigType.ECDSA_P256_SHA256,
TEST_KEY1,
EncType.AES_256_CBC,
TEST_ASSOCIATED_DATA);
assertTrue(Arrays.equals(
unverifiedHeader.toByteArray(),
headerAndBody.getHeader().toByteArray()));
assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
}
public void testHmacAndAesSameKeys() throws Exception {
SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS);
Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
testVector,
TEST_KEY1,
SigType.HMAC_SHA256,
TEST_KEY1,
EncType.AES_256_CBC,
TEST_ASSOCIATED_DATA);
assertTrue(Arrays.equals(
unverifiedHeader.toByteArray(),
headerAndBody.getHeader().toByteArray()));
assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
}
public void testHmacAndAesDifferentKeys() throws Exception {
SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS);
Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
testVector,
TEST_KEY1,
SigType.HMAC_SHA256,
TEST_KEY2,
EncType.AES_256_CBC,
TEST_ASSOCIATED_DATA);
assertTrue(Arrays.equals(
unverifiedHeader.toByteArray(),
headerAndBody.getHeader().toByteArray()));
assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
}
/**
* This code emits the test vectors to {@code System.out}. It will not generate fresh test
* vectors unless an existing test vector is set to {@code null}, but it contains all of the code
* used to the generate the test vector values. Ideally, existing test vectors should never be
* regenerated, but having this code available should make it easier to add new test vectors.
*/
public void testGenerateTestVectorsPseudoTest() throws Exception {
if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
// On older Android platforms we can't run this test.
return;
}
System.out.printf(" static {\n try {\n");
String indent = " ";
PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY;
PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY;
if (testEcPublicKey == null) {
KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
testEcPublicKey = testEcKeyPair.getPublic();
testEcPrivateKey = testEcKeyPair.getPrivate();
}
System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n",
indent,
"TEST_EC_PUBLIC_KEY",
byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey)));
System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n",
indent,
"TEST_EC_PRIVATE_KEY",
byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey)));
SecretKey testKey1 = TEST_KEY1;
if (testKey1 == null) {
testKey1 = makeAesKey();
}
System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
indent,
"TEST_KEY1",
byteArrayToJavaCode(indent, testKey1.getEncoded()));
SecretKey testKey2 = TEST_KEY2;
if (testKey2 == null) {
testKey2 = makeAesKey();
}
System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
indent,
"TEST_KEY2",
byteArrayToJavaCode(indent, testKey2.getEncoded()));
byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY;
if (testVectorEcdsaOnly == null) {
testVectorEcdsaOnly = new SecureMessageBuilder()
.setAssociatedData(TEST_ASSOCIATED_DATA)
.setPublicMetadata(TEST_METADATA)
.setVerificationKeyId(TEST_VKID)
.buildSignedCleartextMessage(
testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray();
}
printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly);
byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES;
if (testVectorEcdsaAndAes == null) {
testVectorEcdsaAndAes = new SecureMessageBuilder()
.setAssociatedData(TEST_ASSOCIATED_DATA)
.setDecryptionKeyId(TEST_DKID)
.setPublicMetadata(TEST_METADATA)
.setVerificationKeyId(TEST_VKID)
.buildSignCryptedMessage(
testEcPrivateKey,
SigType.ECDSA_P256_SHA256,
testKey1,
EncType.AES_256_CBC,
TEST_MESSAGE).toByteArray();
}
printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes);
byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
if (testVectorHmacAndAesSameKeys == null) {
testVectorHmacAndAesSameKeys = new SecureMessageBuilder()
.setAssociatedData(TEST_ASSOCIATED_DATA)
.setDecryptionKeyId(TEST_DKID)
.setPublicMetadata(TEST_METADATA)
.setVerificationKeyId(TEST_VKID)
.buildSignCryptedMessage(
testKey1,
SigType.HMAC_SHA256,
testKey1,
EncType.AES_256_CBC,
TEST_MESSAGE).toByteArray();
}
printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys);
byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
if (testVectorHmacAndAesDifferentKeys == null) {
testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder()
.setAssociatedData(TEST_ASSOCIATED_DATA)
.setDecryptionKeyId(TEST_DKID)
.setPublicMetadata(TEST_METADATA)
.setVerificationKeyId(TEST_VKID)
.buildSignCryptedMessage(
testKey1,
SigType.HMAC_SHA256,
testKey2,
EncType.AES_256_CBC,
TEST_MESSAGE).toByteArray();
}
printInitializerFor(
indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys);
System.out.printf(
" } catch (Exception e) {\n throw new RuntimeException(e);\n }\n }\n");
}
private SecretKey makeAesKey() throws NoSuchAlgorithmException {
KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
aesKeygen.init(256);
return aesKeygen.generateKey();
}
private void printInitializerFor(String indent, String name, byte[] value) {
System.out.printf("%s%s = new byte[] %s;\n",
indent,
name,
byteArrayToJavaCode(indent, value));
}
private static String byteArrayToJavaCode(String lineIndent, byte[] array) {
String newline = "\n" + lineIndent + " ";
String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", "");
int wrapAfter = 16;
int count = wrapAfter;
StringBuilder result = new StringBuilder("{");
for (String entry : unwrappedArray.split(" ")) {
if (++count > wrapAfter) {
result.append(newline);
count = 0;
} else {
result.append(" ");
}
result.append(entry);
}
result.append(" }");
return result.toString();
}
private static byte[] encodePublicKey(PublicKey pk) {
return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
}
private static PublicKey parsePublicKey(byte[] encodedPk)
throws InvalidKeySpecException, InvalidProtocolBufferException {
GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk);
if (PublicKeyProtoUtil.isLegacyCryptoRequired()
&& gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) {
return null;
}
return PublicKeyProtoUtil.parsePublicKey(gpk);
}
private static byte[] encodeEcPrivateKey(PrivateKey sk) {
return sk.getEncoded();
}
private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException {
if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
return null;
}
return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk));
}
// The following block of code was automatically generated by cut and pasting the output of the
// generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please
// DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing.
//
// --- AUTO GENERATED CODE BEGINS HERE ---
static {
try {
TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] {
8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17,
-126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19,
52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116,
64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83,
41, 95, -24, -114, 23, -5 });
TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] {
48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6,
8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4,
32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81,
51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 });
TEST_KEY1 = new SecretKeySpec(new byte[] {
-89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37,
-7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES");
TEST_KEY2 = new SecretKeySpec(new byte[] {
-6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119,
-113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES");
TEST_VECTOR_ECDSA_ONLY = new byte[] {
10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6,
93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50,
21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33,
11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44,
52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63,
43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 };
TEST_VECTOR_ECDSA_AND_AES = new byte[] {
10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115,
-111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4,
32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34,
50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62,
-76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11,
-70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122,
56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35,
21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84,
-29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 };
TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] {
10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70,
121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88,
-33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38,
-4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53,
37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79,
94, -7, -19, -41, -47, -29, 1, 12 };
TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] {
10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75,
-127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47,
98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114,
-48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72,
10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78,
97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9,
-91, 115, 42, 24, 49, -76, -111 };
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}