// Copyright 2024 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 // // http://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.crypto.tink.signature; import static com.google.common.truth.Truth.assertThat; import com.google.crypto.tink.InsecureSecretKeyAccess; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.Parameters; import com.google.crypto.tink.PublicKeySign; import com.google.crypto.tink.PublicKeyVerify; import com.google.crypto.tink.Registry; import com.google.crypto.tink.RegistryConfiguration; import com.google.crypto.tink.TinkProtoKeysetFormat; import com.google.crypto.tink.TinkProtoParametersFormat; import com.google.crypto.tink.proto.Ed25519KeyFormat; import com.google.crypto.tink.proto.Ed25519PrivateKey; import com.google.crypto.tink.proto.Ed25519PublicKey; import com.google.crypto.tink.proto.KeyData; import com.google.crypto.tink.proto.KeyData.KeyMaterialType; import com.google.crypto.tink.proto.KeyStatusType; import com.google.crypto.tink.proto.KeyTemplate; import com.google.crypto.tink.proto.Keyset; import com.google.crypto.tink.proto.OutputPrefixType; import com.google.crypto.tink.signature.internal.testing.LegacyPublicKeySignKeyManager; import com.google.crypto.tink.signature.internal.testing.LegacyPublicKeyVerifyKeyManager; import com.google.crypto.tink.subtle.Ed25519Sign; import com.google.crypto.tink.subtle.Ed25519Verify; import com.google.crypto.tink.subtle.Hex; import com.google.crypto.tink.util.Bytes; import com.google.crypto.tink.util.SecretBytes; import com.google.protobuf.ByteString; import com.google.protobuf.ExtensionRegistryLite; import java.security.GeneralSecurityException; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nullable; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.FromDataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; /** * This test attempts to test the case where a user registers their own key type with * Registry.registerKeyManager() and then uses it. */ @RunWith(Theories.class) public final class KeyManagerIntegrationTest { private static final String PRIVATE_TYPE_URL = "type.googleapis.com/custom.Ed25519PrivateKey"; private static final String PUBLIC_TYPE_URL = "type.googleapis.com/custom.Ed25519PublicKey"; private static byte[] publicKeyByteArray; private static byte[] privateKeyByteArray; @BeforeClass public static void setUpClass() throws Exception { // We register Tink and key manger, as a user would typically do if they add their own key type. SignatureConfig.register(); // Register the key managers the user would register. These have type URLs PRIVATE_TYPE_URL and // PUBLIC_TYPE_URL, and interpret the keys as Ed25519PrivateKey and Ed25519PublicKey exactly // as Tink would. Registry.registerKeyManager(new LegacyPublicKeySignKeyManager(), true); Registry.registerKeyManager(new LegacyPublicKeyVerifyKeyManager(), false); publicKeyByteArray = Hex.decode("ea42941a6dc801484390b2955bc7376d172eeb72640a54e5b50c95efa2fc6ad8"); privateKeyByteArray = Hex.decode("9cac7d19aeecc563a3dff7bcae0fbbbc28087b986c49a3463077dd5281437e81"); } @Test public void testGetPublicKeyset_works() throws Exception { Ed25519PublicKey protoPublicKey = Ed25519PublicKey.newBuilder() .setVersion(0) .setKeyValue(ByteString.copyFrom(publicKeyByteArray)) .build(); Ed25519PrivateKey protoPrivateKey = Ed25519PrivateKey.newBuilder() .setVersion(0) .setPublicKey(protoPublicKey) .setKeyValue(ByteString.copyFrom(privateKeyByteArray)) .build(); KeyData keyData = KeyData.newBuilder() .setTypeUrl(PRIVATE_TYPE_URL) .setValue(protoPrivateKey.toByteString()) .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PRIVATE) .build(); Keyset keyset = Keyset.newBuilder() .addKey( Keyset.Key.newBuilder() .setKeyData(keyData) .setStatus(KeyStatusType.ENABLED) .setOutputPrefixType(OutputPrefixType.TINK) .setKeyId(0x23456789) .build()) .setPrimaryKeyId(0x23456789) .build(); KeysetHandle handle = TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get()); KeysetHandle publicHandle = handle.getPublicKeysetHandle(); Keyset publicKeyset = Keyset.parseFrom( TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicHandle), ExtensionRegistryLite.getEmptyRegistry()); assertThat(publicKeyset.getPrimaryKeyId()).isEqualTo(0x23456789); assertThat(publicKeyset.getKeyCount()).isEqualTo(1); assertThat(publicKeyset.getKey(0).getKeyId()).isEqualTo(0x23456789); assertThat(publicKeyset.getKey(0).getStatus()).isEqualTo(KeyStatusType.ENABLED); assertThat(publicKeyset.getKey(0).getOutputPrefixType()).isEqualTo(OutputPrefixType.TINK); assertThat(publicKeyset.getKey(0).getKeyData().getTypeUrl()).isEqualTo(PUBLIC_TYPE_URL); assertThat(publicKeyset.getKey(0).getKeyData().getKeyMaterialType()) .isEqualTo(KeyMaterialType.ASYMMETRIC_PUBLIC); assertThat( Ed25519PublicKey.parseFrom( publicKeyset.getKey(0).getKeyData().getValue(), ExtensionRegistryLite.getEmptyRegistry())) .isEqualTo(protoPublicKey); } @DataPoints("allOutputPrefixTypes") public static final OutputPrefixType[] OUTPUT_PREFIX_TYPES = new OutputPrefixType[] { OutputPrefixType.LEGACY, OutputPrefixType.CRUNCHY, OutputPrefixType.TINK, OutputPrefixType.RAW }; private static Ed25519Parameters.Variant variantForOutputPrefix(OutputPrefixType outputPrefixType) throws GeneralSecurityException { switch (outputPrefixType) { case LEGACY: return Ed25519Parameters.Variant.LEGACY; case CRUNCHY: return Ed25519Parameters.Variant.CRUNCHY; case TINK: return Ed25519Parameters.Variant.TINK; case RAW: return Ed25519Parameters.Variant.NO_PREFIX; default: throw new GeneralSecurityException("Unknown output prefix type: " + outputPrefixType); } } /** * This test computes the signature using a keyset with one key, with the custom key manager. It * then verifies the Signature using normal Tink subtle Ed25519Verify. */ @Theory public void testComputeCustom_verifyBuiltIn_works( @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception { Ed25519PublicKey protoPublicKey = Ed25519PublicKey.newBuilder() .setVersion(0) .setKeyValue(ByteString.copyFrom(publicKeyByteArray)) .build(); Ed25519PrivateKey protoPrivateKey = Ed25519PrivateKey.newBuilder() .setVersion(0) .setPublicKey(protoPublicKey) .setKeyValue(ByteString.copyFrom(privateKeyByteArray)) .build(); KeyData keyData = KeyData.newBuilder() .setTypeUrl(PRIVATE_TYPE_URL) .setValue(protoPrivateKey.toByteString()) .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PRIVATE) .build(); Keyset keyset = Keyset.newBuilder() .addKey( Keyset.Key.newBuilder() .setKeyData(keyData) .setStatus(KeyStatusType.ENABLED) .setOutputPrefixType(outputPrefixType) .setKeyId(0x23456789) .build()) .setPrimaryKeyId(0x23456789) .build(); KeysetHandle handle = TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get()); PublicKeySign customSigner = handle.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class); byte[] message = new byte[] {1, 2, 3}; byte[] signature = customSigner.sign(message); @Nullable Integer idRequirement = outputPrefixType == OutputPrefixType.RAW ? null : 0x23456789; PublicKeyVerify tinkVerifier = Ed25519Verify.create( com.google.crypto.tink.signature.Ed25519PublicKey.create( variantForOutputPrefix(outputPrefixType), Bytes.copyFrom(publicKeyByteArray), idRequirement)); tinkVerifier.verify(signature, message); } /** * This test computes the signature using a Tink subtle Ed25519Sign. It then verifies the * Signature with a PublicKeyVerify from the custom keyset. */ @Theory public void testComputeBuiltIn_verifyCustom_works( @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception { Ed25519PublicKey protoPublicKey = Ed25519PublicKey.newBuilder() .setVersion(0) .setKeyValue(ByteString.copyFrom(publicKeyByteArray)) .build(); KeyData keyData = KeyData.newBuilder() .setTypeUrl(PUBLIC_TYPE_URL) .setValue(protoPublicKey.toByteString()) .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PUBLIC) .build(); Keyset keyset = Keyset.newBuilder() .addKey( Keyset.Key.newBuilder() .setKeyData(keyData) .setStatus(KeyStatusType.ENABLED) .setOutputPrefixType(outputPrefixType) .setKeyId(0x23456789) .build()) .setPrimaryKeyId(0x23456789) .build(); KeysetHandle handle = TinkProtoKeysetFormat.parseKeysetWithoutSecret(keyset.toByteArray()); PublicKeyVerify customVerifier = handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class); @Nullable Integer idRequirement = outputPrefixType == OutputPrefixType.RAW ? null : 0x23456789; PublicKeySign tinkSigner = Ed25519Sign.create( com.google.crypto.tink.signature.Ed25519PrivateKey.create( com.google.crypto.tink.signature.Ed25519PublicKey.create( variantForOutputPrefix(outputPrefixType), Bytes.copyFrom(publicKeyByteArray), idRequirement), SecretBytes.copyFrom(privateKeyByteArray, InsecureSecretKeyAccess.get()))); byte[] message = new byte[] {1, 2, 3}; byte[] signature = tinkSigner.sign(message); customVerifier.verify(signature, message); } @Theory public void testKeyGeneration_givesNewKeys_works( @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception { KeyTemplate protoKeyTemplate = KeyTemplate.newBuilder() .setOutputPrefixType(outputPrefixType) .setTypeUrl(PRIVATE_TYPE_URL) .setValue(Ed25519KeyFormat.getDefaultInstance().toByteString()) .build(); Parameters parameters = TinkProtoParametersFormat.parse(protoKeyTemplate.toByteArray()); Set keys = new TreeSet<>(); int numKeys = 20; for (int i = 0; i < numKeys; i++) { KeysetHandle handle = KeysetHandle.newBuilder() .addEntry( KeysetHandle.generateEntryFromParameters(parameters) .withFixedId(0x88117722) .makePrimary()) .build(); Keyset keyset = Keyset.parseFrom( TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()), ExtensionRegistryLite.getEmptyRegistry()); assertThat(keyset.getPrimaryKeyId()).isEqualTo(0x88117722); assertThat(keyset.getKeyCount()).isEqualTo(1); assertThat(keyset.getKey(0).getKeyId()).isEqualTo(0x88117722); assertThat(keyset.getKey(0).getStatus()).isEqualTo(KeyStatusType.ENABLED); assertThat(keyset.getKey(0).getOutputPrefixType()).isEqualTo(outputPrefixType); assertThat(keyset.getKey(0).getKeyData().getTypeUrl()).isEqualTo(PRIVATE_TYPE_URL); assertThat(keyset.getKey(0).getKeyData().getKeyMaterialType()) .isEqualTo(KeyMaterialType.ASYMMETRIC_PRIVATE); Ed25519PrivateKey privateKey = Ed25519PrivateKey.parseFrom( keyset.getKey(0).getKeyData().getValue(), ExtensionRegistryLite.getEmptyRegistry()); keys.add(Hex.encode(privateKey.getKeyValue().toByteArray())); } assertThat(keys).hasSize(numKeys); } }