diff --git a/WORKSPACE b/WORKSPACE index c0781b0c2f29e182f95225cece20171232709ff8..a669286b18839954ea3251fc63d0e1c67bca6b5c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -116,3 +116,9 @@ maven_jar( artifact = "com.google.truth:truth:jar:0.32", sha1 = "e996fb4b41dad04365112786796c945f909cfdf7", ) + +maven_jar( + name = "com_google_errorprone_error_prone_annotations", + artifact = "com.google.errorprone:error_prone_annotations:2.0.19", + sha1 = "c3754a0bdd545b00ddc26884f9e7624f8b6a14de", +) \ No newline at end of file diff --git a/java/src/main/java/com/google/cloud/crypto/tink/BUILD b/java/src/main/java/com/google/cloud/crypto/tink/BUILD index 260b02672982f4bcaae0a88f485b36b938729cff..26dc66d8eb357d368921cf880d5532cfeb0cdd0a 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/BUILD +++ b/java/src/main/java/com/google/cloud/crypto/tink/BUILD @@ -72,6 +72,7 @@ java_library( "//java/src/main/java/com/google/cloud/crypto/tink/subtle", "//proto:common_java_proto", "//proto:tink_java_proto", + "@com_google_errorprone_error_prone_annotations//jar", "@com_google_protobuf_java//:protobuf_java", ], ) @@ -84,6 +85,7 @@ java_library( "//java/src/main/java/com/google/cloud/crypto/tink/subtle", "//proto:common_java_proto_lite", "//proto:tink_java_proto_lite", + "@com_google_errorprone_error_prone_annotations//jar", "@com_google_protobuf_java//:protobuf_java", ], ) diff --git a/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java b/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java index 93ff9dc8a55de9c1536de74a33953f625ca6331a..6e7a528431a5a9e1b23f81629b29825c3dad5a19 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java +++ b/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java @@ -18,11 +18,14 @@ package com.google.cloud.crypto.tink; import com.google.cloud.crypto.tink.TinkProto.Keyset; import com.google.cloud.crypto.tink.TinkProto.KeysetInfo; +import com.google.cloud.crypto.tink.subtle.ImmutableByteArray; +import com.google.errorprone.annotations.Immutable; /** * KeysetHandle provides abstracted access to Keysets, to limit the exposure * of actual protocol buffers that hold sensitive key material. */ +@Immutable public final class KeysetHandle { /** * The {@code Keyset}. @@ -32,7 +35,7 @@ public final class KeysetHandle { /** * {@code Keyset} encrypted with some key. */ - private final byte[] encryptedKeyset; + private final ImmutableByteArray encryptedKeyset; /** * This constructor is package-private. To get a new instance, users have to use one of @@ -51,7 +54,7 @@ public final class KeysetHandle { */ KeysetHandle(Keyset keyset, final byte[] encryptedKeyset) { this.keyset = keyset; - this.encryptedKeyset = encryptedKeyset; + this.encryptedKeyset = ImmutableByteArray.of(encryptedKeyset); } /** @@ -72,7 +75,11 @@ public final class KeysetHandle { * @return the actual keyset data. */ public byte[] getEncryptedKeyset() { - return encryptedKeyset; + if (encryptedKeyset == null) { + return null; + } else { + return encryptedKeyset.getBytes(); + } } /** diff --git a/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java b/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java index 5cd93b79a1488c55f6be345505764b80ffe3d9ae..9f79df3126335e73abc91c09d634c4f5685caced 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java +++ b/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java @@ -18,6 +18,8 @@ package com.google.cloud.crypto.tink; import com.google.cloud.crypto.tink.TinkProto.KeyStatusType; import com.google.cloud.crypto.tink.TinkProto.Keyset; +import com.google.cloud.crypto.tink.subtle.ImmutableByteArray; +import com.google.errorprone.annotations.Immutable; import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -35,7 +37,7 @@ import java.util.concurrent.ConcurrentMap; * PrimitiveSet is an auxiliary class used for supporting key rotation: primitives in a set * correspond to keys in a keyset. Users will usually work with primitive instances, * which essentially wrap primitive sets. For example an instance of an Aead-primitive - * for a given keyset holds a set of Aead-primitivies corresponding to the keys in the keyset, + * for a given keyset holds a set of Aead-primitives corresponding to the keys in the keyset, * and uses the set members to do the actual crypto operations: to encrypt data the primary * Aead-primitive from the set is used, and upon decryption the ciphertext's prefix * determines the id of the primitive from the set. <p> @@ -48,18 +50,19 @@ public final class PrimitiveSet<P> { * A single entry in the set. In addition to the actual primitive it holds also * some extra information about the primitive. */ + @Immutable(containerOf={"P"}) public class Entry<P> { // The actual primitive. private final P primitive; // Identifies the primitive within the set. // It is the ciphertext prefix of the correponding key. - private final byte[] identifier; + private final ImmutableByteArray identifier; // The status of the key represented by the primitive. private final KeyStatusType status; public Entry(P primitive, final byte[] identifier, KeyStatusType status) { this.primitive = primitive; - this.identifier = identifier; + this.identifier = ImmutableByteArray.of(identifier); this.status = status; } public P getPrimitive() { @@ -69,7 +72,11 @@ public final class PrimitiveSet<P> { return status; } public final byte[] getIdentifier() { - return identifier; + if (identifier == null) { + return null; + } else { + return identifier.getBytes(); + } } } diff --git a/java/src/main/java/com/google/cloud/crypto/tink/subtle/BUILD b/java/src/main/java/com/google/cloud/crypto/tink/subtle/BUILD index a30b2d59a3ed651fad0b0ae90c2c15fc0922e337..a353263a72e3f6dfba3b12ffb1ae0daa6e38e723 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/subtle/BUILD +++ b/java/src/main/java/com/google/cloud/crypto/tink/subtle/BUILD @@ -19,10 +19,14 @@ java_library( "EcUtil.java", "EngineFactory.java", "EngineWrapper.java", + "ImmutableByteArray.java", "Random.java", "SubtleUtil.java", ], javacopts = JAVACOPTS, + deps = [ + "@com_google_errorprone_error_prone_annotations//jar", + ], ) # aead subtle @@ -121,6 +125,7 @@ java_library( deps = [ ":subtle", "//java/src/main/java/com/google/cloud/crypto/tink:primitives", + "@com_google_errorprone_error_prone_annotations//jar", ], ) @@ -166,6 +171,7 @@ java_library( ":subtle", ":x25519", "//java/src/main/java/com/google/cloud/crypto/tink:primitives", + "@com_google_errorprone_error_prone_annotations//jar", ], ) diff --git a/java/src/main/java/com/google/cloud/crypto/tink/subtle/EciesHkdfSenderKem.java b/java/src/main/java/com/google/cloud/crypto/tink/subtle/EciesHkdfSenderKem.java index 9a70f70e021d9e18f00d4e69dc5de85e143f8d96..24a63c7fbcb0003e22c6975ca74caffa8303a003 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/subtle/EciesHkdfSenderKem.java +++ b/java/src/main/java/com/google/cloud/crypto/tink/subtle/EciesHkdfSenderKem.java @@ -16,6 +16,7 @@ package com.google.cloud.crypto.tink.subtle; +import com.google.errorprone.annotations.Immutable; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -34,18 +35,27 @@ public final class EciesHkdfSenderKem { /** * A container for key parts generated by the KEM. */ + @Immutable public static final class KemKey { - private byte[] kemBytes; - private byte[] symmetricKey; + private final ImmutableByteArray kemBytes; + private final ImmutableByteArray symmetricKey; public KemKey(final byte[] kemBytes, final byte[] symmetricKey) { - this.kemBytes = kemBytes; - this.symmetricKey = symmetricKey; + this.kemBytes = ImmutableByteArray.of(kemBytes); + this.symmetricKey = ImmutableByteArray.of(symmetricKey); } public byte[] getKemBytes() { - return kemBytes; + if (kemBytes == null) { + return null; + } else { + return kemBytes.getBytes(); + } } public byte[] getSymmetricKey() { - return symmetricKey; + if (symmetricKey == null) { + return null; + } else { + return symmetricKey.getBytes(); + } } } diff --git a/java/src/main/java/com/google/cloud/crypto/tink/subtle/Ed25519Verify.java b/java/src/main/java/com/google/cloud/crypto/tink/subtle/Ed25519Verify.java index 348d1f71fa0d0d26dad1c15bada96c1d11a5a29a..f7f0d3ac411a5aff62507be9a0b0088e4334c280 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/subtle/Ed25519Verify.java +++ b/java/src/main/java/com/google/cloud/crypto/tink/subtle/Ed25519Verify.java @@ -19,6 +19,7 @@ package com.google.cloud.crypto.tink.subtle; import static com.google.cloud.crypto.tink.subtle.Curve25519.FIELD_LEN; import com.google.cloud.crypto.tink.PublicKeyVerify; +import com.google.errorprone.annotations.Immutable; import java.security.GeneralSecurityException; import java.security.SignatureException; @@ -36,12 +37,13 @@ import java.security.SignatureException; * // all the rest of security exceptions. * } */ +@Immutable public class Ed25519Verify implements PublicKeyVerify { public static final int PUBLIC_KEY_LEN = FIELD_LEN; public static final int SIGNATURE_LEN = FIELD_LEN * 2; - private final byte[] publicKey; + private final ImmutableByteArray publicKey; public Ed25519Verify(final byte[] publicKey) throws GeneralSecurityException { @@ -49,7 +51,7 @@ public class Ed25519Verify implements PublicKeyVerify { throw new IllegalArgumentException( String.format("Given public key's length is not %s.", PUBLIC_KEY_LEN)); } - this.publicKey = publicKey; + this.publicKey = ImmutableByteArray.of(publicKey); } @Override @@ -61,7 +63,7 @@ public class Ed25519Verify implements PublicKeyVerify { if (((signature[SIGNATURE_LEN - 1] & 0xff) & 224) != 0) { throw new IllegalArgumentException("Given signature's 3 most significant bits must be 0."); } - if (!Ed25519.verify(data, signature, publicKey)) { + if (!Ed25519.verify(data, signature, publicKey.getBytes())) { throw new SignatureException("Signature check failed."); } } diff --git a/java/src/main/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArray.java b/java/src/main/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArray.java new file mode 100644 index 0000000000000000000000000000000000000000..26241fa389d7e61a8ffc4f1d1956727eb220a243 --- /dev/null +++ b/java/src/main/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArray.java @@ -0,0 +1,74 @@ +// Copyright 2017 Google Inc. +// +// 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.cloud.crypto.tink.subtle; + +import com.google.errorprone.annotations.Immutable; + +/** + * Immutable Wrapper around a byte array. + * + * <p>Wrap a bytearray so it prevents callers from modifying its contents. It does this by making a + * copy upon initialization, and also makes a copy if the underlying bytes are requested. + */ +@Immutable +@SuppressWarnings("Immutable") +public final class ImmutableByteArray { + + /** + * @param data the byte array to be wrapped. + * @return an immutable wrapper around the provided bytes. + */ + public static ImmutableByteArray of(final byte[] data) { + if (data == null) { + return null; + } else { + return of(data, 0, data.length); + } + } + + /** + * Wrap an immutable byte array over a slice of a bytearray + * + * @param data the byte array to be wrapped. + * @param start the starting index of the slice + * @param len the length of the slice. start + len must be less than the length of the array. + * @return an immutable wrapper around the bytes in the slice from {@code start} to + * {@code start + len} + */ + public static ImmutableByteArray of(final byte[] data, final int start, final int len) { + return new ImmutableByteArray(data, start, len); + } + + /** @return a copy of the bytes wrapped by this object. */ + public byte[] getBytes() { + byte[] result = new byte[data.length]; + System.arraycopy(data, 0, result, 0, data.length); + return result; + } + + /** @return the length of the bytes wrapped by this object. */ + public int getLength() { + return data.length; + } + + private ImmutableByteArray(final byte[] buf, final int start, final int len) { + data = new byte[len]; + System.arraycopy(buf, start, data, 0, len); + } + + private final byte[] data; +} diff --git a/java/src/test/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArrayTest.java b/java/src/test/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArrayTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1ed8364416b274166714f4c1e5971b10aa88cffb --- /dev/null +++ b/java/src/test/java/com/google/cloud/crypto/tink/subtle/ImmutableByteArrayTest.java @@ -0,0 +1,40 @@ +// Copyright 2017 Google Inc. +// +// 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.cloud.crypto.tink.subtle; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for ImmutableByteArray */ +@RunWith(JUnit4.class) +public class ImmutableByteArrayTest { + + @Test + public void checkWrap() { + byte[] initial = new byte[] {(byte) 1}; + ImmutableByteArray ba = ImmutableByteArray.of(initial); + byte[] result = ba.getBytes(); + assertNotSame(result, initial); + assertArrayEquals(result, initial); + assertEquals(ba.getLength(), initial.length); + } +} diff --git a/pom.xml b/pom.xml index 75192cefb0657cadaddd371eaacc5dc1738f513c..42fe9d72ac8be79be592c156a0896baa7a8feb7f 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ <os-maven-plugin.version>1.2.0.Final</os-maven-plugin.version> <!-- library versions --> + <error-prone-annotations.version>2.0.19</error-prone-annotations.version> <google-api-client.version>1.22.0</google-api-client.version> <google-api-services-cloudkms.version>v1-rev9-1.22.0</google-api-services-cloudkms.version> <gson.version>2.8.0</gson.version> @@ -122,6 +123,12 @@ <version>${protobuf.version}</version> </dependency> + <dependency> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_annotations</artifactId> + <version>${error-prone-annotations.version}</version> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> @@ -170,6 +177,11 @@ <artifactId>protobuf-java</artifactId> </dependency> + <dependency> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_annotations</artifactId> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId>