diff --git a/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/AesCtrHmacStreamingTestUtil.java b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/AesCtrHmacStreamingTestUtil.java new file mode 100644 index 00000000..1fedac08 --- /dev/null +++ b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/AesCtrHmacStreamingTestUtil.java @@ -0,0 +1,98 @@ +// 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.streamingaead.internal.testing; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.crypto.tink.internal.TinkBugException.exceptionIsBug; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.crypto.tink.AccessesPartialKey; +import com.google.crypto.tink.InsecureSecretKeyAccess; +import com.google.crypto.tink.streamingaead.AesCtrHmacStreamingKey; +import com.google.crypto.tink.streamingaead.AesCtrHmacStreamingParameters; +import com.google.crypto.tink.subtle.Bytes; +import com.google.crypto.tink.subtle.Hex; +import com.google.crypto.tink.util.SecretBytes; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +/** Test vectors for AesCtrHmac StreamingAEAD. */ +@AccessesPartialKey +public final class AesCtrHmacStreamingTestUtil { + private static byte[] xor(byte[] b1, byte[] b2) { + assertThat(b1.length).isEqualTo(b2.length); + byte[] result = new byte[b1.length]; + for (int i = 0; i < result.length; i++) { + result[i] = (byte) (b1[i] ^ b2[i]); + } + return result; + } + + /** + * A test vector which was created by hand for the cross language tests (see + * aes_ctr_hmac_streaming_key_test there, and for more information about the magic values which + * appear in this test. + */ + private static StreamingAeadTestVector createTestVector0() throws GeneralSecurityException { + AesCtrHmacStreamingParameters parameters = + AesCtrHmacStreamingParameters.builder() + .setKeySizeBytes(16) + .setDerivedKeySizeBytes(16) + .setHkdfHashType(AesCtrHmacStreamingParameters.HashType.SHA1) + .setHmacHashType(AesCtrHmacStreamingParameters.HashType.SHA256) + .setHmacTagSizeBytes(32) + .setCiphertextSegmentSizeBytes(64) + .build(); + AesCtrHmacStreamingKey key = + AesCtrHmacStreamingKey.create( + parameters, + SecretBytes.copyFrom( + Hex.decode("6eb56cdc726dfbe5d57f2fcdc6e9345b"), InsecureSecretKeyAccess.get())); + byte[] plaintext = + "This is a fairly long plaintext. However, it is not crazy long.".getBytes(UTF_8); + byte[] headerLength = Hex.decode("18"); + byte[] salt = Hex.decode("93b3af5e14ab378d065addfc8484da64"); + byte[] noncePrefix = Hex.decode("2c0862877baea8"); + byte[] header = Bytes.concat(headerLength, salt, noncePrefix); + + byte[] msg0 = Arrays.copyOfRange(plaintext, 0, 8); + byte[] msg1 = Arrays.copyOfRange(plaintext, 8, 40); + byte[] msg2 = Arrays.copyOfRange(plaintext, 40, plaintext.length); + + byte[] c0 = xor(msg0, Hex.decode("ea8e18301bd57bfd")); + byte[] c1 = + xor(msg1, Hex.decode("2999c8ea5401704243c8cd77929fd52617fec5542a842446251bb2f3a81f6249")); + byte[] c2 = xor(msg2, Hex.decode("70fe58e44835a6602952749e763637d9d973bca8358086")); + byte[] tag0 = Hex.decode("8303ca71c04d8e06e1b01cff7c1178af47dac031517b1f6a2d9be84105677a68"); + byte[] tag1 = Hex.decode("834d890839f37f762caddc029cc673300ff107fd51f9a62058fcd00befc362e5"); + byte[] tag2 = Hex.decode("5fb0c893903271af38380c2f355cb85e5ec571648513123321bde0c6042f43c7"); + + byte[] ciphertext = Bytes.concat(header, c0, tag0, c1, tag1, c2, tag2); + byte[] aad = "aad".getBytes(UTF_8); + return new StreamingAeadTestVector(key, plaintext, aad, ciphertext); + } + + public static StreamingAeadTestVector[] createAesCtrHmacTestVectors() { + return exceptionIsBug( + () -> + new StreamingAeadTestVector[] { + createTestVector0(), + }); + } + + private AesCtrHmacStreamingTestUtil() {} +} diff --git a/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/BUILD.bazel b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/BUILD.bazel new file mode 100644 index 00000000..31ad2e7f --- /dev/null +++ b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/BUILD.bazel @@ -0,0 +1,61 @@ +licenses(["notice"]) + +package(default_visibility = ["//:__subpackages__"]) + +java_library( + name = "aes_ctr_hmac_streaming_test_util", + testonly = 1, + srcs = ["AesCtrHmacStreamingTestUtil.java"], + deps = [ + ":streaming_aead_test_vector", + "//src/main/java/com/google/crypto/tink:accesses_partial_key", + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access", + "//src/main/java/com/google/crypto/tink/internal:tink_bug_exception", + "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_key", + "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_parameters", + "//src/main/java/com/google/crypto/tink/subtle:bytes", + "//src/main/java/com/google/crypto/tink/subtle:hex", + "//src/main/java/com/google/crypto/tink/util:secret_bytes", + "@maven//:com_google_truth_truth", + ], +) + +java_library( + name = "streaming_aead_test_vector", + testonly = 1, + srcs = ["StreamingAeadTestVector.java"], + deps = [ + "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_key", + "//src/main/java/com/google/crypto/tink/util:bytes", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +android_library( + name = "aes_ctr_hmac_streaming_test_util-android", + testonly = 1, + srcs = ["AesCtrHmacStreamingTestUtil.java"], + deps = [ + ":streaming_aead_test_vector-android", + "//src/main/java/com/google/crypto/tink:accesses_partial_key-android", + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access-android", + "//src/main/java/com/google/crypto/tink/internal:tink_bug_exception-android", + "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_key-android", + "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_parameters-android", + "//src/main/java/com/google/crypto/tink/subtle:bytes-android", + "//src/main/java/com/google/crypto/tink/subtle:hex-android", + "//src/main/java/com/google/crypto/tink/util:secret_bytes-android", + "@maven//:com_google_truth_truth", + ], +) + +android_library( + name = "streaming_aead_test_vector-android", + testonly = 1, + srcs = ["StreamingAeadTestVector.java"], + deps = [ + "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_key-android", + "//src/main/java/com/google/crypto/tink/util:bytes-android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) diff --git a/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/StreamingAeadTestVector.java b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/StreamingAeadTestVector.java new file mode 100644 index 00000000..a4acae68 --- /dev/null +++ b/src/main/java/com/google/crypto/tink/streamingaead/internal/testing/StreamingAeadTestVector.java @@ -0,0 +1,54 @@ +// 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.streamingaead.internal.testing; + +import com.google.crypto.tink.streamingaead.StreamingAeadKey; +import com.google.crypto.tink.util.Bytes; +import com.google.errorprone.annotations.Immutable; + +/** Test vector for StreamingAEAD encryption. */ +@Immutable +public final class StreamingAeadTestVector { + public StreamingAeadTestVector( + StreamingAeadKey key, byte[] plaintext, byte[] associatedData, byte[] ciphertext) { + this.key = key; + this.plaintext = Bytes.copyFrom(plaintext); + this.associatedData = Bytes.copyFrom(associatedData); + this.ciphertext = Bytes.copyFrom(ciphertext); + } + + private final StreamingAeadKey key; + private final Bytes plaintext; + private final Bytes associatedData; + private final Bytes ciphertext; + + public StreamingAeadKey getKey() { + return key; + } + + public byte[] getPlaintext() { + return plaintext.toByteArray(); + } + + public byte[] getAssociatedData() { + return associatedData.toByteArray(); + } + + public byte[] getCiphertext() { + return ciphertext.toByteArray(); + } +} diff --git a/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java b/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java index 9d8014c0..6b25fc4a 100644 --- a/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java +++ b/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2017 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,11 +27,19 @@ import com.google.crypto.tink.StreamingAead; import com.google.crypto.tink.TinkProtoKeysetFormat; import com.google.crypto.tink.internal.KeyManagerRegistry; +import com.google.crypto.tink.streamingaead.internal.testing.AesCtrHmacStreamingTestUtil; +import com.google.crypto.tink.streamingaead.internal.testing.StreamingAeadTestVector; import com.google.crypto.tink.subtle.AesCtrHmacStreaming; import com.google.crypto.tink.subtle.Hex; import com.google.crypto.tink.testing.StreamingTestUtil; +import com.google.crypto.tink.testing.StreamingTestUtil.ByteBufferChannel; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.experimental.theories.DataPoints; @@ -193,4 +201,50 @@ public void serializeAndParse_works() throws Exception { TinkProtoKeysetFormat.parseKeyset(serializedHandle, InsecureSecretKeyAccess.get()); assertThat(parsedHandle.equalsKeyset(handle)).isTrue(); } + + @DataPoints("testVectors") + public static final StreamingAeadTestVector[] streamingTestVector = + AesCtrHmacStreamingTestUtil.createAesCtrHmacTestVectors(); + + @Theory + public void decryptCiphertextInputStream_works( + @FromDataPoints("testVectors") StreamingAeadTestVector v) throws Exception { + KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getKey()).makePrimary(); + @Nullable Integer id = v.getKey().getIdRequirementOrNull(); + if (id == null) { + entry.withRandomId(); + } else { + entry.withFixedId(id); + } + KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); + StreamingAead streamingAead = + handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class); + InputStream plaintextStream = + streamingAead.newDecryptingStream( + new ByteArrayInputStream(v.getCiphertext()), v.getAssociatedData()); + byte[] decryption = new byte[v.getPlaintext().length]; + plaintextStream.read(decryption); + assertThat(decryption).isEqualTo(v.getPlaintext()); + } + + @Theory + public void decryptCiphertextChannel_works( + @FromDataPoints("testVectors") StreamingAeadTestVector v) throws Exception { + KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getKey()).makePrimary(); + @Nullable Integer id = v.getKey().getIdRequirementOrNull(); + if (id == null) { + entry.withRandomId(); + } else { + entry.withFixedId(id); + } + KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); + StreamingAead streamingAead = + handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class); + ReadableByteChannel plaintextChannel = + streamingAead.newDecryptingChannel( + new ByteBufferChannel(v.getCiphertext()), v.getAssociatedData()); + ByteBuffer decryption = ByteBuffer.allocate(v.getPlaintext().length); + plaintextChannel.read(decryption); + assertThat(decryption.array()).isEqualTo(v.getPlaintext()); + } } diff --git a/src/test/java/com/google/crypto/tink/streamingaead/BUILD.bazel b/src/test/java/com/google/crypto/tink/streamingaead/BUILD.bazel index dc8bb4e6..d63a32f6 100644 --- a/src/test/java/com/google/crypto/tink/streamingaead/BUILD.bazel +++ b/src/test/java/com/google/crypto/tink/streamingaead/BUILD.bazel @@ -136,9 +136,12 @@ java_test( "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_key_manager", "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_parameters", "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config", + "//src/main/java/com/google/crypto/tink/streamingaead/internal/testing:aes_ctr_hmac_streaming_test_util", + "//src/main/java/com/google/crypto/tink/streamingaead/internal/testing:streaming_aead_test_vector", "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_hmac_streaming", "//src/main/java/com/google/crypto/tink/subtle:hex", "//src/main/java/com/google/crypto/tink/testing:streaming_test_util", + "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ],