It is recommended to install libsodium in the system through a package manager and configure + * automatic updates to receive security fixes and performance improvements of libsodium + * in a timely manner. This implementation will attempt to use the installed libsodium; + * if it is not available, it will use the bundled one. */ public static CryptoFunction ed25519() { return Ed25519CryptoFunction.INSTANCE; diff --git a/exonum-java-binding/common/src/main/java/com/exonum/binding/common/crypto/Ed25519CryptoFunction.java b/exonum-java-binding/common/src/main/java/com/exonum/binding/common/crypto/Ed25519CryptoFunction.java index fce0c5b24a..c24f319712 100644 --- a/exonum-java-binding/common/src/main/java/com/exonum/binding/common/crypto/Ed25519CryptoFunction.java +++ b/exonum-java-binding/common/src/main/java/com/exonum/binding/common/crypto/Ed25519CryptoFunction.java @@ -23,17 +23,25 @@ import static com.exonum.binding.common.crypto.CryptoUtils.hasLength; import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.annotations.VisibleForTesting; import com.goterl.lazycode.lazysodium.LazySodiumJava; import com.goterl.lazycode.lazysodium.SodiumJava; +import com.goterl.lazycode.lazysodium.utils.LibraryLoader; +import com.goterl.lazycode.lazysodium.utils.LibraryLoader.Mode; /** * A ED25519 public-key signature system crypto function. */ -public enum Ed25519CryptoFunction implements CryptoFunction { +final class Ed25519CryptoFunction implements CryptoFunction { - INSTANCE; + static final Ed25519CryptoFunction INSTANCE = new Ed25519CryptoFunction(Mode.PREFER_SYSTEM); - private final LazySodiumJava lazySodium = new LazySodiumJava(new SodiumJava()); + private final LazySodiumJava lazySodium; + + @VisibleForTesting + Ed25519CryptoFunction(LibraryLoader.Mode mode) { + lazySodium = new LazySodiumJava(new SodiumJava(mode)); + } @Override public KeyPair generateKeyPair(byte[] seed) { diff --git a/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTest.java b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTestable.java similarity index 69% rename from exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTest.java rename to exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTestable.java index 67b8d410f9..bf2e3a2a99 100644 --- a/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTest.java +++ b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionTestable.java @@ -30,17 +30,25 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.exonum.binding.test.Bytes; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class Ed25519CryptoFunctionTest { +abstract class Ed25519CryptoFunctionTestable { - private static final CryptoFunction CRYPTO_FUNCTION = CryptoFunctions.ed25519(); + abstract Ed25519CryptoFunction createFunction(); + + private Ed25519CryptoFunction cryptoFunction; + + @BeforeEach + void createCryptoFunction() { + cryptoFunction = createFunction(); + } @Test void generateKeyPairWithSeed() { byte[] seed = new byte[SEED_BYTES]; - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(seed); + KeyPair keyPair = cryptoFunction.generateKeyPair(seed); assertNotNull(keyPair); assertThat(keyPair.getPrivateKey().size(), equalTo(PRIVATE_KEY_BYTES)); assertThat(keyPair.getPublicKey().size(), equalTo(PUBLIC_KEY_BYTES)); @@ -53,94 +61,94 @@ void generateKeyPairInvalidSeedSize() { IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> CRYPTO_FUNCTION.generateKeyPair(seed)); + () -> cryptoFunction.generateKeyPair(seed)); assertEquals("Seed byte array has invalid size (2), must be " + SEED_BYTES, thrown.getMessage()); } @Test void generateKeyPairNullSeed() { - assertThrows(NullPointerException.class, () -> CRYPTO_FUNCTION.generateKeyPair(null)); + assertThrows(NullPointerException.class, () -> cryptoFunction.generateKeyPair(null)); } @Test void validSignatureVerificationTest() { - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivateKey(); PublicKey publicKey = keyPair.getPublicKey(); byte[] message = bytes("myMessage"); - byte[] signature = CRYPTO_FUNCTION.signMessage(message, privateKey); - assertTrue(CRYPTO_FUNCTION.verify(message, signature, publicKey)); + byte[] signature = cryptoFunction.signMessage(message, privateKey); + assertTrue(cryptoFunction.verify(message, signature, publicKey)); } @Test void validSignatureEmptyMessageVerificationTest() { - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivateKey(); PublicKey publicKey = keyPair.getPublicKey(); byte[] emptyMessage = new byte[0]; - byte[] signature = CRYPTO_FUNCTION.signMessage(emptyMessage, privateKey); - assertTrue(CRYPTO_FUNCTION.verify(emptyMessage, signature, publicKey)); + byte[] signature = cryptoFunction.signMessage(emptyMessage, privateKey); + assertTrue(cryptoFunction.verify(emptyMessage, signature, publicKey)); } @Test void invalidLengthSignatureVerificationTest() { - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PublicKey publicKey = keyPair.getPublicKey(); byte[] message = bytes("myMessage"); byte[] invalidSignature = bytes("invalidLengthMessage"); - assertFalse(CRYPTO_FUNCTION.verify(message, invalidSignature, publicKey)); + assertFalse(cryptoFunction.verify(message, invalidSignature, publicKey)); } @Test void invalidSignatureVerificationTest() { - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PublicKey publicKey = keyPair.getPublicKey(); byte[] message = bytes("myMessage"); byte[] invalidSignature = Bytes.createPrefixed(message, SIGNATURE_BYTES); - assertFalse(CRYPTO_FUNCTION.verify(message, invalidSignature, publicKey)); + assertFalse(cryptoFunction.verify(message, invalidSignature, publicKey)); } @Test void invalidPublicKeyVerificationTest() { // Generate a key pair. - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivateKey(); byte[] message = bytes("myMessage"); // Sign a message properly. - byte[] signature = CRYPTO_FUNCTION.signMessage(message, privateKey); + byte[] signature = cryptoFunction.signMessage(message, privateKey); // Try to use another public key. - PublicKey publicKey = CRYPTO_FUNCTION.generateKeyPair().getPublicKey(); - assertFalse(CRYPTO_FUNCTION.verify(message, signature, publicKey)); + PublicKey publicKey = cryptoFunction.generateKeyPair().getPublicKey(); + assertFalse(cryptoFunction.verify(message, signature, publicKey)); } @Test void invalidPublicKeyLengthVerificationTest() { // Generate a key pair. - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivateKey(); byte[] message = bytes("myMessage"); // Sign a message. - byte[] signature = CRYPTO_FUNCTION.signMessage(message, privateKey); + byte[] signature = cryptoFunction.signMessage(message, privateKey); // Try to use a public key of incorrect length. PublicKey publicKey = PublicKey.fromHexString("abcd"); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> CRYPTO_FUNCTION.verify(message, signature, publicKey)); + () -> cryptoFunction.verify(message, signature, publicKey)); assertEquals("Public key has invalid size (2), must be " + PUBLIC_KEY_BYTES, thrown.getMessage()); } @Test void invalidMessageVerificationTest() { - KeyPair keyPair = CRYPTO_FUNCTION.generateKeyPair(); + KeyPair keyPair = cryptoFunction.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivateKey(); PublicKey publicKey = keyPair.getPublicKey(); byte[] message = bytes("myMessage"); - byte[] signature = CRYPTO_FUNCTION.signMessage(message, privateKey); + byte[] signature = cryptoFunction.signMessage(message, privateKey); byte[] anotherMessage = bytes("anotherMessage"); - assertFalse(CRYPTO_FUNCTION.verify(anotherMessage, signature, publicKey)); + assertFalse(cryptoFunction.verify(anotherMessage, signature, publicKey)); } } diff --git a/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithBundledTest.java b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithBundledTest.java new file mode 100644 index 0000000000..eab4147710 --- /dev/null +++ b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithBundledTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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.exonum.binding.common.crypto; + +import com.exonum.binding.test.CiOnly; +import com.goterl.lazycode.lazysodium.utils.LibraryLoader.Mode; +import org.junit.jupiter.api.DisplayName; + +@Forked +@CiOnly +@DisplayName("Test that the Ed25519 crypto function works with the libsodium, " + + "bundled in lazysodium") +class Ed25519CryptoFunctionWithBundledTest extends Ed25519CryptoFunctionTestable { + + @Override + Ed25519CryptoFunction createFunction() { + return new Ed25519CryptoFunction(Mode.BUNDLED_ONLY); + } +} diff --git a/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithSystemSodiumTest.java b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithSystemSodiumTest.java new file mode 100644 index 0000000000..c22c8f65ed --- /dev/null +++ b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Ed25519CryptoFunctionWithSystemSodiumTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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.exonum.binding.common.crypto; + +import com.goterl.lazycode.lazysodium.utils.LibraryLoader.Mode; +import org.junit.jupiter.api.DisplayName; + +@Forked +@DisplayName("Test that the Ed25519 crypto function works with the installed libsodium") +class Ed25519CryptoFunctionWithSystemSodiumTest extends Ed25519CryptoFunctionTestable { + + @Override + Ed25519CryptoFunction createFunction() { + return new Ed25519CryptoFunction(Mode.SYSTEM_ONLY); + } +} diff --git a/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Forked.java b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Forked.java new file mode 100644 index 0000000000..e2fe754ef3 --- /dev/null +++ b/exonum-java-binding/common/src/test/java/com/exonum/binding/common/crypto/Forked.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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.exonum.binding.common.crypto; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; + + +/** + * Indicates that a test class must be run in a forked individual VM. + * + *
For this annotation to have any effect, the surefire and failsafe plugin must be configured + * accordingly. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Tag("forked") +public @interface Forked { + +}