Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions exonum-java-binding/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed
- `Ed25519CryptoFunction` to use the system libsodium by default. If libsodium is not installed,
it will load the bundled library. (#991)
- `Ed25519CryptoFunction` is made package-private. It remains accessible via
`CryptoFunctions#ed25519`.

## [0.7.0] - 2019-07-17

### Overview
Expand Down
27 changes: 27 additions & 0 deletions exonum-java-binding/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,36 @@
${jacoco.args}
${java.vm.assertionFlag}
</argLine>
<excludedGroups>${excludeTags}, forked</excludedGroups>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<argLine>
${jacoco.it.args}
${java.vm.assertionFlag}
</argLine>
<includes>
<include>**/*Test.java</include>
</includes>
<groups>forked</groups>
<!-- Run each test class in its own VM -->
<reuseForks>false</reuseForks>
</configuration>
<executions>
<execution>
<id>run-forked-its</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ private CryptoFunctions() {}

/**
* Returns a ED25519 public-key signature system crypto function.
*
* <p>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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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 {

}