Skip to content

Commit

Permalink
Merge pull request cryptography-cafe#4 from cryptography-cafe/validation
Browse files Browse the repository at this point in the history
Validation improvements
  • Loading branch information
str4d authored Aug 10, 2019
2 parents bb0cae4 + 7aff2cb commit bd2d077
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 8 deletions.
18 changes: 10 additions & 8 deletions src/main/java/cafe/cryptography/ed25519/Ed25519Signature.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public class Ed25519Signature {
* @return a signature.
*/
public static Ed25519Signature fromByteArray(byte[] input) {
if (input.length != 64) {
throw new IllegalArgumentException("signature length is wrong");
}

// RFC 8032, section 5.1.7:
// @formatter:off
// 1. To verify a signature [...], first split the signature into two
Expand All @@ -56,14 +60,12 @@ public byte[] toByteArray() {
// little-endian encoding of S (32 octets; the three most
// significant bits of the final octet are always zero).
// @formatter:on
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
try {
baos.write(this.R.toByteArray());
baos.write(this.S.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Should be able to write to a ByteArrayOutputStream");
}
return baos.toByteArray();
byte[] Rbar = this.R.toByteArray();
byte[] Sbar = this.S.toByteArray();
byte[] sig = new byte[Rbar.length + Sbar.length];
System.arraycopy(Rbar, 0, sig, 0, Rbar.length);
System.arraycopy(Sbar, 0, sig, Rbar.length, Sbar.length);
return sig;
}

@Override
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/cafe/cryptography/ed25519/Ed25519PrivateKeyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* This file is part of ed25519-elisabeth.
* Copyright (c) 2019 Jack Grigg
* See LICENSE for licensing information.
*/

package cafe.cryptography.ed25519;

import org.junit.Test;

public class Ed25519PrivateKeyTest {
@Test
public void fromByteArrayAcceptsAllBitsSet() {
// An Ed25519 private key is only ever used as an input to a hash
// function, not as a scalar, so all 32-byte strings are valid.
Ed25519PrivateKey
.fromByteArray(Utils.hexToBytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
}

@Test(expected = IllegalArgumentException.class)
public void fromByteArrayRejectsShortInput() {
Ed25519PrivateKey.fromByteArray(Utils.hexToBytes("00"));
}

@Test(expected = IllegalArgumentException.class)
public void fromByteArrayRejectsLongInput() {
Ed25519PrivateKey
.fromByteArray(Utils.hexToBytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"));
}
}
39 changes: 39 additions & 0 deletions src/test/java/cafe/cryptography/ed25519/Ed25519SignatureTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* This file is part of ed25519-elisabeth.
* Copyright (c) 2019 Jack Grigg
* See LICENSE for licensing information.
*/

package cafe.cryptography.ed25519;

import org.junit.Test;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

public class Ed25519SignatureTest {
@Test
public void fromByteArrayAcceptsInvalidR() {
// Validation of R happens during signature verification
Ed25519Signature.fromByteArray(Utils.hexToBytes(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000"));
}

@Test(expected = IllegalArgumentException.class)
public void fromByteArrayRejectsShortInput() {
Ed25519Signature.fromByteArray(Utils.hexToBytes("00"));
}

@Test(expected = IllegalArgumentException.class)
public void fromByteArrayRejectsLongInput() {
Ed25519Signature.fromByteArray(Utils.hexToBytes(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ff"));
}

@Test(expected = IllegalArgumentException.class)
public void fromByteArrayRejectsNonCanonicalS() {
Ed25519Signature.fromByteArray(Utils.hexToBytes(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
}
}
103 changes: 103 additions & 0 deletions src/test/java/cafe/cryptography/ed25519/Ed25519TestVectors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* This file is part of ed25519-elisabeth.
* Copyright (c) 2019 Jack Grigg
* See LICENSE for licensing information.
*/

package cafe.cryptography.ed25519;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import cafe.cryptography.curve25519.InvalidEncodingException;
import org.junit.Test;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

/**
* Test against the medium test vectors set.
*
* TESTVECTORS is taken from ed25519-dalek, which in turn obtained it from
* sign.input.gz in agl's ed25519 Golang package. It is a selection of test
* cases from https://ed25519.cr.yp.to/python/sign.input
*/
public class Ed25519TestVectors {
public static class TestTuple {
public static int numCases;
public int caseNum;
public byte[] sk;
public byte[] vk;
public byte[] message;
public byte[] signature;

public TestTuple(String line) {
this.caseNum = ++numCases;
String[] x = line.split(":");
this.sk = Utils.hexToBytes(x[0].substring(0, 64));
this.vk = Utils.hexToBytes(x[1]);
this.message = Utils.hexToBytes(x[2]);
this.signature = Utils.hexToBytes(x[3].substring(0, 128));
}
}

public static Collection<TestTuple> testCases = getTestData("TESTVECTORS");

public static Collection<TestTuple> getTestData(String fileName) {
List<TestTuple> testCases = new ArrayList<TestTuple>();
BufferedReader file = null;
try {
InputStream is = Ed25519TestVectors.class.getClassLoader().getResourceAsStream(fileName);
if (is == null) {
throw new IOException("Resource not found: " + fileName);
}
file = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = file.readLine()) != null) {
testCases.add(new TestTuple(line));
}
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
}
}
}
return testCases;
}

@Test
public void derivePublic() {
for (TestTuple testCase : testCases) {
Ed25519PrivateKey sk = Ed25519PrivateKey.fromByteArray(testCase.sk);
assertThat("Test case " + testCase.caseNum + " failed", sk.derivePublic().toByteArray(), is(testCase.vk));
}
}

@Test
public void testSign() {
for (TestTuple testCase : testCases) {
Ed25519PrivateKey sk = Ed25519PrivateKey.fromByteArray(testCase.sk);
assertThat("Test case " + testCase.caseNum + " failed",
sk.expand().sign(testCase.message, sk.derivePublic()).toByteArray(), is(testCase.signature));
}
}

@Test
public void testVerify() throws InvalidEncodingException {
for (TestTuple testCase : testCases) {
Ed25519PublicKey vk = Ed25519PublicKey.fromByteArray(testCase.vk);
Ed25519Signature sig = Ed25519Signature.fromByteArray(testCase.signature);
assertTrue("Test case " + testCase.caseNum + " failed", vk.verify(testCase.message, sig));
}
}
}
Loading

0 comments on commit bd2d077

Please sign in to comment.