Add DER & PEM support for SigningKeySeed and VerificationKeyBytes (RFC 8410) (#46)

* Add DER & PEM support for SigningKeySeed and VerificationKeyBytes (RFC 8410)

- Add encoding to and decoding from DER bytes and PEM strings for SigningKeySeed and VerificationKeyBytes.
- Add some functions so that the Java code mirrors, to a certain degree, the JDK 15 interface for Ed25519 keys and signatures.
- Add encoding and decoding for signatures (technically identity functions).
- Miscellaneous cleanup.

* Accommodate extra octet string in private key DER bytes

- In RFC 8410, DER-encoded private keys are in an octet string that's encapsulated by another octet string. Add the extra octet string, and adjust tests as necessary.
- In the tests, use the private key from RFC 8410, Sect. 10.3.

* Update pkcs8 to 0.7.0

* Cleanup

- Enhance PEM capabilities for SigningKey and VerificationKeyBytes. This also allowed for some tests to be simplified.
- From -> TryFrom for some VerificationKeyBytes impls.

* Upgrade JNI Rust bindings to PKCS8 0.7.5

- Make necessary changes to support the newer crate.
- Fix an unrelated compiler warning.

* More fixups

- Get code to compile after updating to the latest Rust.
- Fix a couple of failing tests (add LF to expected encoding output).

* Major update

- Update pkcs8 crate to 0.10.0, and update code as required to support the crate. This includes supporting the Decode(Public/Private)Key and Encode(Public/Private)Key traits so as to take advantage of Ed25519 DER and PEM code in the crate.
- Add the latest ed25519 crate (2.2.0) to support KeypairBytes and other features.
- Remove the signature code and implement Signature (Signer and Verifier traits) from the "signatures" crate included with the pkcs8 crate.
- Update the JNI code. This includes mandating Scala 3 usage.
- Minor cleanup (including warning fixes) and changes to make the code a bit clearer.

A follow-up commit will clean up the tests and probably add support for v2 private DER keys.

* Further code cleanup

- Update pkcs8 crate to 0.10.1.
- Fix PEM feature code.
- Update Ed25519 JNI code as needed.
- Remove dead code.
- Re-enable a couple of unit tests.

Note that a couple of Ed25519 JNI unit tests are still failing. A follow-up PR will have the fix.

* Add missing DER/PEM files for unit tests

* Add JNI comments to resolve publisher warnings

When executing `sbt publishLocal` and generating a JAR file, there are warnings regarding some functions not having public comments. Add public comments as needed.

* JNI README update

* Comment touchup

* Review fixups

- Finish adding PEM/PKCS8 tags and cfg items as needed to separate the features from default compilation.
- Revert some minor name changes.
- Make the JNI README more precise with regards to requirements.
- Add ARM64 macOS support to JNI. Untested but it should work, and it doesn't break Intel Macs.
- Miscellaneous cleanup, including fixing cargo and sbt warnings.

* Upgrade jni crate to 0.20.0

The 0.21.X crates feature a major refactor that breaks the code. Don't upgrade to them until some issues are resolved. (See https://github.com/jni-rs/jni-rs/issues/432 for more info.)

* Upgrade jni crate to 0.21.1

- A path forward to upgrading to 0.21.X was suggested by the jni-rs library developer (https://github.com/jni-rs/jni-rs/issues/439#issuecomment-1493074721). Upgrade the code, improving the safety of the JNI code.
- Cargo.toml fixups.

* cargo clippy / cargo fmt cleanup

Also do minor JNI README cleanup.

* Use an export to clean up some tests a bit

---------

Co-authored-by: Douglas Roark <douglas.roark@gemini.com>
This commit is contained in:
Douglas Roark 2023-04-21 13:56:37 -07:00 committed by GitHub
parent 79085903a0
commit 346f4cde27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1173 additions and 173 deletions

View File

@ -16,8 +16,11 @@ features = ["nightly"]
[dependencies]
# "digest" is exempt from SemVer, so we should always use a specific version
curve25519-dalek = { version = "=4.0.0-pre.5", default-features = false, features = ["alloc", "digest"] }
der = { version = "0.7.1", optional = true }
ed25519 = { version = "2.2.0", features = ["alloc", "pem"] }
hashbrown = "0.12.0"
hex = { version = "0.4", default-features = false, features = ["alloc"] }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
pkcs8 = { version = "0.10.1", optional = true, features = ["alloc", "pem"] }
rand_core = "0.6"
serde = { version = "1", optional = true, features = ["derive"] }
sha2 = { version = "0.10", default-features = false }
@ -34,6 +37,8 @@ once_cell = "1.4"
[features]
nightly = []
default = ["serde", "std"]
pem = ["der"]
pkcs8 = ["dep:pkcs8"]
std = []
[[test]]

View File

@ -5,6 +5,14 @@ specific `ed25519-zebra` calls and provides a minor analogue for some Rust
classes, allowing for things like basic sanity checks of certain values. Tests
written in Scala have also been included.
Note that Scala 3 is required to build and test the JNI code.
## Build Requirements
- For PEM support, the `pem` feature must be enabled in both `Cargo.toml` files
(`pem = ["der"]`).
- For PKCS #8 (DER) support, the `pkcs8` feature must be enabled in both `Cargo.toml`
files (`pkcs8 = ["dep:pkcs8"]`).
## Compilation / Library Usage
To build the JNI code, there are several steps. The exact path forward depends
on the user's preferred deployment method. No matter what, the following steps
@ -21,24 +29,39 @@ From here, there are two deployment methods: Direct library usage and JARs.
It's possible to generate a JAR that can be loaded into a project via
[SciJava's NativeLoader](https://javadoc.scijava.org/SciJava/org/scijava/nativelib/NativeLoader.html),
along with the Java JNI interface file. There are two exta steps to perform
along with the Java JNI interface file. There are two extra steps to perform
after the mandatory compilation steps.
- Run `jni_jar_prereq.sh` from the `ed25519/scripts` subdirectory. This performs
some JAR setup steps.
- Run `jni_jar_prereq.sh` from the `ed25519jni/scripts` subdirectory. Use the `-d`
flag if working with debug builds. This script performs some JAR setup steps, and
enables the local Scala tests against the JNI code.
- Run `sbt clean publishLocal` from the `ed25519jni/jvm` subdirectory. This
generates the final `ed25519jni.jar` file.
generates the final `ed25519jni.jar` file in the {$HOME}/.ivy2/local subdirectory.
### Direct library usage
(NOTE: Future work will better accommodate this option. For now, users will have
to develop their own solutions.)
Use a preferred method to load the Rust core and JNI libraries directly as
needed. If necessary, include the JNI Java files too.
Note that the code is designed to support only the aforementioned JAR method. Local
changes may be required to support other deployment methods.
## Testing
Run `sbt test` from the `ed25519jni/jvm` directory. Note that, in order to run
the tests, the [JAR compilation method](#jar) must be executed first.
### Prerequisites
To reiterate, before running any tests, execute the first step from the
[JAR compilation method](#jar) section above.
### Commands
The precise test command will depend on if you built the code with PKCS8 and/or
PEM support. Both are disabled by default. This also means that, without disabling
the associated tests or enabling the features, an `UnsatisfiedLinkError` Java error
will occur.
The following examples show how to run tests.
- `sbt test` - Run all tests. Must compile with PEM and PKCS #8 support.
- `sbt "testOnly * -- -l Pkcs8Test"` - Run all tests unrelated to PKCS #8.
- `sbt "testOnly * -- -l \"PemTest Pkcs8Test\""` - Run all tests unrelated to PEM
and PKCS #8.
## Capabilities
Among other things, the JNI code can perform the following actions.
@ -46,4 +69,5 @@ Among other things, the JNI code can perform the following actions.
* Generate a random 32 byte signing key seed.
* Generate a 32 byte verification key from a signing key seed.
* Sign arbitrary data with a signing key seed.
* Verify a signature for arbitrary data with verification key bytes (32 bytes).
* Verify a signature for arbitrary data with verification key bytes.
* Generate DER bytes and PEM strings for signing key seeds and verification key bytes, and read back the DER bytes and PEM strings into signing key seeds and verification key bytes.

View File

@ -2,11 +2,11 @@ organization := "org.zfnd"
name := "ed25519jni"
version := "0.0.4-JNI-DEV"
version := "0.0.5-JNI-DEV"
scalaVersion := "2.12.10"
scalaVersion := "3.1.3"
scalacOptions ++= Seq("-Xmax-classfile-name", "140")
//scalacOptions ++= Seq("-Xmax-classfile-name", "140")
autoScalaLibrary := false // exclude scala-library from dependencies
@ -14,12 +14,12 @@ crossPaths := false // drop off Scala suffix from artifact names.
libraryDependencies ++= Deps.ed25519jni
unmanagedResourceDirectories in Compile += baseDirectory.value / "natives"
Compile / unmanagedResourceDirectories += baseDirectory.value / "natives"
publishArtifact := true
javacOptions in (Compile,doc) ++= Seq(
Compile / doc / javacOptions ++= Seq(
"-windowtitle", "JNI bindings for ed25519-zebra"
)
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")

View File

@ -3,13 +3,15 @@ import sbt._
object Deps {
object V {
val nativeLoaderV = "2.3.4"
val scalaTest = "3.0.9"
val nativeLoaderV = "2.4.0"
val scalactic = "3.2.15"
val scalaTest = "3.2.15"
val slf4j = "1.7.30"
}
object Test {
val nativeLoader = "org.scijava" % "native-lib-loader" % V.nativeLoaderV
val scalactic = "org.scalactic" %% "scalactic" % V.scalactic
val scalaTest = "org.scalatest" %% "scalatest" % V.scalaTest % "test"
val slf4jApi = "org.slf4j" % "slf4j-api" % V.slf4j
val slf4jSimple = "org.slf4j" % "slf4j-simple" % V.slf4j % "test"
@ -17,6 +19,7 @@ object Deps {
val ed25519jni = List(
Test.nativeLoader,
Test.scalactic,
Test.scalaTest,
Test.slf4jApi,
Test.slf4jSimple,

View File

@ -1 +1 @@
sbt.version=1.4.6
sbt.version=1.8.2

View File

@ -6,6 +6,18 @@ import org.scijava.nativelib.NativeLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Interface class offering Java users access to certain ed25519-zebra functionality.
* Uses include but are not necessarily limited to:
* - Generating a signing key seed (essentially a private key).
* - Obtaining a DER-encoded (v1, per RFC 5958) signing key seed byte array.
* - Obtaining a PEM-encoded (v1, per RFC 5958) signing key seed string.
* - Getting verification key bytes (basically a public key) from a signing key seed.
* - Obtaining a DER-encoded (v1, per RFC 5958) verification key byte structure.
* - Obtaining a PEM-encoded (v1, per RFC 5958) verification key string.
* - Signing data with a signing key seed.
* - Verifying a signature with verification key bytes.
*/
public class Ed25519Interface {
private static final Logger logger;
private static final boolean enabled;
@ -23,6 +35,11 @@ public class Ed25519Interface {
enabled = isEnabled;
}
/**
* Default constuctor.
*/
public Ed25519Interface() { }
/**
* Helper method to determine whether the Ed25519 Rust backend is loaded and
* available.
@ -39,7 +56,7 @@ public class Ed25519Interface {
* values are allowed, this code will have to stay in sync.
*
* @param rng An initialized, secure RNG
* @return sks 32 byte signing key seed
* @return the signing key seed bytes (32 bytes)
*/
private static byte[] genSigningKeySeedFromJava(SecureRandom rng) {
byte[] seedBytes = new byte[SigningKeySeed.BYTE_LENGTH];
@ -61,6 +78,58 @@ public class Ed25519Interface {
return new SigningKeySeed(genSigningKeySeedFromJava(rng));
}
/**
* Get the encoded DER (RFC 8410) bytes for signing key seed bytes.
*
* @param sks the signing key seed bytes
* @return the encoded DER bytes
*/
public static native byte[] getSigningKeySeedEncoded(byte[] sks);
/**
* Get the encoded DER (RFC 8410) bytes for signing key seed bytes.
*
* @param sks the signing key seed
* @return the encoded DER bytes
*/
public static byte[] getSigningKeySeedEncoded(SigningKeySeed sks) {
return getSigningKeySeedEncoded(sks.getSigningKeySeedCopy());
}
/**
* Get the encoded PEM (RFC 8410) string for signing key seed bytes.
*
* @param sks the signing key seed bytes
* @return the encoded PEM string
*/
public static native String getSigningKeySeedPEM(byte[] sks);
/**
* Get the encoded PEM (RFC 8410) string for signing key seed bytes.
*
* @param sks the signing key seed
* @return the encoded PEM string
*/
public static String getSigningKeySeedPEM(SigningKeySeed sks) {
return getSigningKeySeedPEM(sks.getSigningKeySeedCopy());
}
/**
* Generate a SigningKeySeed object from DER (RFC 8410) bytes.
*
* @param derBytes the encoded DER bytes (48 bytes)
* @return a new SigningKeySeed object
*/
public static native byte[] generatePrivate(byte[] derBytes);
/**
* Generate a SigningKeySeed object from a PEM (RFC 8410) string.
*
* @param pemString the encoded PEM string
* @return a new SigningKeySeed object
*/
public static native byte[] generatePrivatePEM(String pemString);
/**
* Check if verification key bytes for a verification key are valid.
*
@ -88,6 +157,58 @@ public class Ed25519Interface {
return new VerificationKeyBytes(getVerificationKeyBytes(seed.getSigningKeySeed()));
}
/**
* Get the encoded DER (RFC 8410) bytes for verification key bytes.
*
* @param vkb the verification key byte array
* @return the encoded DER bytes (44 bytes)
*/
public static native byte[] getVerificationKeyBytesEncoded(byte[] vkb);
/**
* Get the encoded DER (RFC 8410) bytes for verification key bytes.
*
* @param vkb the verification key bytes
* @return the encoded DER bytes (44 bytes)
*/
public static byte[] getVerificationKeyBytesEncoded(VerificationKeyBytes vkb) {
return getVerificationKeyBytesEncoded(vkb.getVerificationKeyBytes());
}
/**
* Get the encoded PEM (RFC 8410) bytes for verification key bytes.
*
* @param vkb the verification key bytes
* @return the encoded PEM bytes
*/
public static native String getVerificationKeyBytesPEM(byte[] vkb);
/**
* Get the encoded PEM (RFC 8410) bytes for verification key bytes.
*
* @param vkb the verification key bytes
* @return the encoded PEM string
*/
public static String getVerificationKeyBytesPEM(VerificationKeyBytes vkb) {
return getVerificationKeyBytesPEM(vkb.getVerificationKeyBytes());
}
/**
* Generate a VerificationKeyBytes object from DER (RFC 8410) bytes.
*
* @param derBytes the encoded DER bytes (44 bytes)
* @return a new VerificationKeyBytes object
*/
public static native byte[] generatePublic(byte[] derBytes);
/**
* Generate a VerificationKeyBytes object from PEM (RFC 8410) bytes.
*
* @param pemString the encoded PEM string
* @return a new VerificationKeyBytes object
*/
public static native byte[] generatePublicPEM(String pemString);
/**
* Creates a signature on msg using the given signing key.
*

View File

@ -7,10 +7,18 @@ import org.slf4j.LoggerFactory;
/**
* Java wrapper class for signatures that performs some sanity checking.
*/
**/
public class Signature {
/**
* Length of signature components (R &amp; s).
**/
public static final int COMPONENT_LENGTH = 32;
/**
* Length of a signature.
**/
public static final int SIGNATURE_LENGTH = 2 * COMPONENT_LENGTH;
private static final Logger logger = LoggerFactory.getLogger(Signature.class);
private byte[] rBytes;
@ -35,16 +43,55 @@ public class Signature {
}
/**
* Get the raw signature bytes.
*
* @return a copy of the complete signature
*/
public byte[] getSignatureBytesCopy() {
return completeSignature.clone();
}
/**
* Get the raw signature bytes.
*
* @return a copy of the complete signature
*/
byte[] getSignatureBytes() {
return completeSignature;
}
/**
* Get the signature algorithm name.
*
* @return the signature algorithm name
*/
public String getAlgorithm() {
return "Ed25519";
}
/**
* Get the encoded signature bytes.
*
* @return the complete signature
*/
public byte[] getEncoded() {
return getSignatureBytes();
}
/**
* Get a Signature object from an encoded signature.
*
* @param encodedBytes untrusted, unvalidated bytes that may be an encoding of a verification key
* @return the Signature object
*/
public static Signature getFromEncoded(byte[] encodedBytes) {
if (encodedBytes.length != SIGNATURE_LENGTH) {
throw new IllegalArgumentException("Ed25519 signature must be " + SIGNATURE_LENGTH + " bytes large.");
}
return new Signature(encodedBytes);
}
/**
* Optionally convert bytes into a verification key wrapper.
*

View File

@ -9,6 +9,9 @@ import org.slf4j.LoggerFactory;
* Java wrapper class for signing key seeds that performs some sanity checking.
*/
public class SigningKeySeed {
/**
* Length of signing key seeds.
**/
public static final int BYTE_LENGTH = 32;
private static final Logger logger = LoggerFactory.getLogger(SigningKeySeed.class);
@ -34,7 +37,9 @@ public class SigningKeySeed {
}
/**
* @return a copy of the wrapped bytes
* Get a copy of the actual signing key seed bytes.
*
* @return a byte array copy of the wrapped bytes
*/
public byte[] getSigningKeySeedCopy() {
return seed.clone();
@ -44,6 +49,62 @@ public class SigningKeySeed {
return seed;
}
/**
* Generate a SigningKeySeed object from DER (RFC 8410) bytes.
*
* @param derBytes the encoded DER bytes
* @return a new SigningKeySeed object
*/
public static SigningKeySeed generatePrivate(byte[] derBytes) {
return SigningKeySeed.fromBytesOrThrow(Ed25519Interface.generatePrivate(derBytes));
}
/**
* Generate a SigningKeySeed object from PEM (RFC 5958) bytes.
*
* @param pemString the encoded PEM string
* @return a new SigningKeySeed object
*/
public static SigningKeySeed generatePrivatePEM(String pemString) {
return SigningKeySeed.fromBytesOrThrow(Ed25519Interface.generatePrivatePEM(pemString));
}
/**
* Get the encoded DER (RFC 8410) bytes for signing key seed bytes.
*
* @return the encoded DER bytes
*/
public byte[] getEncoded() {
return Ed25519Interface.getSigningKeySeedEncoded(this);
}
/**
* Get the encoded PEM (RFC 5958) bytes for signing key seed bytes.
*
* @return the encoded PEM bytes
*/
public String getPEM() {
return Ed25519Interface.getSigningKeySeedPEM(this);
}
/**
* Get the signing key algorithm name.
*
* @return the signing key algorithm name
*/
public static String getAlgorithm() {
return "EdDSA";
}
/**
* Get the signing key format.
*
* @return the signing key algorithm format
*/
public static String getFormat() {
return "PKCS#8";
}
/**
* Optionally convert bytes into a signing key seed wrapper.
*

View File

@ -9,6 +9,9 @@ import org.slf4j.LoggerFactory;
* Java wrapper class for verification key bytes that performs some sanity checking.
*/
public class VerificationKeyBytes {
/**
* Length of verification keys.
**/
public static final int BYTE_LENGTH = 32;
private static final Logger logger = LoggerFactory.getLogger(VerificationKeyBytes.class);
@ -26,7 +29,9 @@ public class VerificationKeyBytes {
}
/**
* @return a copy of the wrapped bytes
* Get a copy of the actual verification key bytes.
*
* @return a byte array with a copy of the wrapped bytes
*/
public byte[] getVerificationKeyBytesCopy() {
return vkb.clone();
@ -36,6 +41,44 @@ public class VerificationKeyBytes {
return vkb;
}
/**
* Generate a VerificationKeyBytes object from DER (RFC 8410) bytes.
*
* @param derBytes the encoded DER bytes
* @return a new VerificationKeyBytes object
*/
public static VerificationKeyBytes generatePublic(byte[] derBytes) {
return VerificationKeyBytes.fromBytesOrThrow(Ed25519Interface.generatePublic(derBytes));
}
/**
* Generate a VerificationKeyBytes object from PEM (RFC 8410) bytes.
*
* @param pemString the encoded PEM string
* @return a new VerificationKeyBytes object
*/
public static VerificationKeyBytes generatePublicPEM(String pemString) {
return VerificationKeyBytes.fromBytesOrThrow(Ed25519Interface.generatePublicPEM(pemString));
}
/**
* Get the encoded DER (RFC 8410) bytes for verification key bytes.
*
* @return the encoded DER bytes
*/
public byte[] getEncoded() {
return Ed25519Interface.getVerificationKeyBytesEncoded(this);
}
/**
* Get the encoded PEM (RFC 8410) bytes for verification key bytes.
*
* @return the encoded PEM bytes
*/
public String getPEM() {
return Ed25519Interface.getVerificationKeyBytesPEM(this);
}
/**
* Optionally convert bytes into a verification key wrapper.
*
@ -66,6 +109,24 @@ public class VerificationKeyBytes {
.orElseThrow(() -> new IllegalArgumentException("Expected " + BYTE_LENGTH + " bytes that encode a verification key!"));
}
/**
* Get the verification key algorithm name.
*
* @return the verification key algorithm name
*/
public static String getAlgorithm() {
return "EdDSA";
}
/**
* Get the verification key algorithm format.
*
* @return the verification key algorithm format
*/
public static String getFormat() {
return "X.509";
}
@Override
public boolean equals(final Object other) {
if (other == this) {

View File

@ -2,14 +2,15 @@ package org.zfnd.ed25519
import java.math.BigInteger
import java.security.SecureRandom
import org.scalatest.{ FlatSpec, MustMatchers }
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
class Ed25519InterfaceTest extends FlatSpec with MustMatchers {
class Ed25519InterfaceTest extends AnyFlatSpec with Matchers {
private val RANDOM = new SecureRandom
private def convertBytesToHex(bytes: Seq[Byte]): String = {
private def convertBytesToHex(bytes: Array[Byte]): String = {
val sb = new StringBuilder
for (b <- bytes) {
for (b <- bytes.toIndexedSeq) {
sb.append(String.format("%02x", Byte.box(b)))
}
sb.toString

View File

@ -1,22 +1,25 @@
package org.zfnd.ed25519
import java.security.SecureRandom
import org.scalatest.{ FlatSpec, MustMatchers }
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
import scala.collection.mutable.HashSet
class SignatureTest extends FlatSpec with MustMatchers {
class SignatureTest extends AnyFlatSpec with Matchers {
private val RANDOM = new SecureRandom()
it must "properly compare Signature objects" in {
val sig1 = new Array[Byte](Signature.SIGNATURE_LENGTH);
do {
RANDOM.nextBytes(sig1);
} while(!Signature.bytesAreValid(sig1));
while ({
RANDOM.nextBytes(sig1)
!Signature.bytesAreValid(sig1)
}) {}
val sig2 = new Array[Byte](Signature.SIGNATURE_LENGTH);
do {
RANDOM.nextBytes(sig2);
} while(!Signature.bytesAreValid(sig2));
while ({
RANDOM.nextBytes(sig2)
!Signature.bytesAreValid(sig2)
}) {}
val sigObj1 = Signature.fromBytesOrThrow(sig1);
val sigObj2 = Signature.fromBytesOrThrow(sig1);
@ -27,7 +30,7 @@ class SignatureTest extends FlatSpec with MustMatchers {
it must "reject illegal Signature bytes" in {
val sig = new Array[Byte](Signature.COMPONENT_LENGTH);
RANDOM.nextBytes(sig);
RANDOM.nextBytes(sig)
val sigObj = Signature.fromBytes(sig)
sigObj.isPresent() mustBe false
@ -35,9 +38,10 @@ class SignatureTest extends FlatSpec with MustMatchers {
it must "properly handle Signatures in hashed data structures" in {
val sig = new Array[Byte](Signature.SIGNATURE_LENGTH);
do {
RANDOM.nextBytes(sig);
} while(!Signature.bytesAreValid(sig));
while ({
RANDOM.nextBytes(sig)
!Signature.bytesAreValid(sig)
}) {}
val sigObj1 = Signature.fromBytesOrThrow(sig);
val sigObj2 = Signature.fromBytesOrThrow(sig);

View File

@ -0,0 +1,48 @@
package org.zfnd.ed25519
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import java.util.Arrays
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
import scala.collection.mutable.HashSet
class SigningKeySeedTest extends AnyFlatSpec with Matchers {
it must "wrap SigningKeySeed (RFC 8410 - DER)" taggedAs(Pkcs8Test) in {
val sksValue = BigInt("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842", 16)
val sks = SigningKeySeed.fromBytesOrThrow(sksValue.toByteArray.drop(1))
val sks_der = sks.getEncoded()
val expected_der = BigInt("302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842", 16)
Arrays.equals(sks_der, expected_der.toByteArray) mustBe true
}
it must "wrap SigningKeySeed (RFC 8410 - PEM)" taggedAs(PemTest) in {
val sksValue = BigInt("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842", 16)
val sks = SigningKeySeed.fromBytesOrThrow(sksValue.toByteArray.drop(1))
val sks_pem = sks.getPEM()
val expected_pem = BigInt("2d2d2d2d2d424547494e2050524956415445204b45592d2d2d2d2d0a4d43344341514177425159444b32567742434945494e5475637476354531684b31626259386664702b4b30362f6e776f792f48552b2b435871493945645668430a2d2d2d2d2d454e442050524956415445204b45592d2d2d2d2d0a", 16)
Arrays.equals(sks_pem.getBytes(), expected_pem.toByteArray) mustBe true
}
it must "decode DER encoding (RFC 8410) to SigningKeySeed" taggedAs(Pkcs8Test) in {
val der = BigInt("302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842", 16)
val vkb = SigningKeySeed.generatePrivate(der.toByteArray)
val expected_vkb = BigInt("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842", 16)
Arrays.equals(vkb.getSigningKeySeed, expected_vkb.toByteArray.drop(1)) mustBe true
}
it must "decode PEM encoding (RFC 8410) to SigningKeySeed" taggedAs(PemTest) in {
val pem = BigInt("2d2d2d2d2d424547494e2050524956415445204b45592d2d2d2d2d0a4d43344341514177425159444b32567742434945494e5475637476354531684b31626259386664702b4b30362f6e776f792f48552b2b435871493945645668430a2d2d2d2d2d454e442050524956415445204b45592d2d2d2d2d", 16)
val pem_str = new String(pem.toByteArray, StandardCharsets.UTF_8)
val vkb = SigningKeySeed.generatePrivatePEM(pem_str)
val expected_vkb = BigInt("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842", 16)
Arrays.equals(vkb.getSigningKeySeedCopy(), expected_vkb.toByteArray.drop(1)) mustBe true
}
}

View File

@ -0,0 +1,6 @@
package org.zfnd.ed25519
import org.scalatest.Tag
object PemTest extends Tag("PemTest")
object Pkcs8Test extends Tag("Pkcs8Test")

View File

@ -1,22 +1,27 @@
package org.zfnd.ed25519
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import org.scalatest.{ FlatSpec, MustMatchers }
import java.util.Arrays
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
import scala.collection.mutable.HashSet
class VerificationKeyBytesTest extends FlatSpec with MustMatchers {
class VerificationKeyBytesTest extends AnyFlatSpec with Matchers {
private val RANDOM = new SecureRandom()
it must "properly compare VerificationKeyBytes objects" in {
val vkb1 = new Array[Byte](VerificationKeyBytes.BYTE_LENGTH)
do {
while ({
RANDOM.nextBytes(vkb1)
} while(!VerificationKeyBytes.bytesAreValid(vkb1))
!VerificationKeyBytes.bytesAreValid(vkb1)
}) {}
val vkb2 = new Array[Byte](VerificationKeyBytes.BYTE_LENGTH)
do {
while ({
RANDOM.nextBytes(vkb2)
} while(!VerificationKeyBytes.bytesAreValid(vkb2))
!VerificationKeyBytes.bytesAreValid(vkb2)
}) {}
val vkbObj1 = new VerificationKeyBytes(vkb1)
val vkbObj2 = new VerificationKeyBytes(vkb1)
@ -27,9 +32,10 @@ class VerificationKeyBytesTest extends FlatSpec with MustMatchers {
it must "properly handle VerificationKeyBytes in hashed data structures" in {
val vkb = new Array[Byte](VerificationKeyBytes.BYTE_LENGTH)
do {
while ({
RANDOM.nextBytes(vkb)
} while(!VerificationKeyBytes.bytesAreValid(vkb))
!VerificationKeyBytes.bytesAreValid(vkb)
}) {}
val vkbObj1 = new VerificationKeyBytes(vkb)
val vkbObj2 = new VerificationKeyBytes(vkb)
@ -45,4 +51,41 @@ class VerificationKeyBytesTest extends FlatSpec with MustMatchers {
val vkbObj1 = VerificationKeyBytes.fromBytes(vkb1)
vkbObj1.isPresent() mustBe false
}
it must "wrap VerificationKeyBytes (RFC 8410 - DER)" taggedAs(Pkcs8Test) in {
val vkbValue = BigInt("4d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
val vkb = VerificationKeyBytes.fromBytesOrThrow(vkbValue.toByteArray)
val vkb_der = vkb.getEncoded()
val expected_der = BigInt("302a300506032b65700321004d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
Arrays.equals(vkb_der, expected_der.toByteArray) mustBe true
}
it must "wrap VerificationKeyBytes (RFC 8410 - PEM)" taggedAs(PemTest) in {
val vkbValue = BigInt("4d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
val vkb = VerificationKeyBytes.fromBytesOrThrow(vkbValue.toByteArray)
val vkb_pem = vkb.getPEM()
val expected_pem = BigInt("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d436f77425159444b3256774179454154536b57667a385a4571623372666f704f67556146634265786e755046795a3748465651334f68547651303d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a", 16)
Arrays.equals(vkb_pem.getBytes(), expected_pem.toByteArray) mustBe true
}
it must "decode DER encoding (RFC 8410) to VerificationKeyBytes" taggedAs(Pkcs8Test) in {
val der = BigInt("302a300506032b65700321004d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
val vkb = VerificationKeyBytes.generatePublic(der.toByteArray)
val expected_vkb = BigInt("4d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
Arrays.equals(vkb.getVerificationKeyBytes, expected_vkb.toByteArray) mustBe true
}
it must "decode PEM encoding (RFC 8410) to VerificationKeyBytes" taggedAs(PemTest) in {
val pem = BigInt("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d436f77425159444b3256774179454154536b57667a385a4571623372666f704f67556146634265786e755046795a3748465651334f68547651303d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d", 16)
val pem_str = new String(pem.toByteArray, StandardCharsets.UTF_8)
val vkb = VerificationKeyBytes.generatePublicPEM(pem_str)
val expected_vkb = BigInt("4d29167f3f1912a6f7adfa293a051a15c05ec67b8f17267b1c5550dce853bd0d", 16)
Arrays.equals(vkb.getVerificationKeyBytesCopy, expected_vkb.toByteArray) mustBe true
}
}

View File

@ -1 +1 @@
sbt.version=1.4.5
sbt.version=1.8.2

View File

@ -7,11 +7,21 @@ publish = false
edition = "2018"
[dependencies]
ed25519-zebra = { path = "../../", version = "3.0.0" }
der = { version = "0.7.1", optional = true }
ed25519-zebra = { path = "../../", version = "3.1.0", features = ["pem", "pkcs8"] }
failure = "0.1.8"
jni = "0.18.0"
jni = "0.21.1"
pkcs8 = { version = "0.10.1", optional = true, features = ["alloc", "pem"] }
ed25519 = { version = "2.2.0", features = ["alloc", "pem"] }
[lib]
name = "ed25519jni"
path = "src/lib.rs"
crate-type = ["staticlib", "cdylib"]
[features]
nightly = []
default = ["std"]
pem = ["der"]
pkcs8 = ["dep:pkcs8"]
std = []

View File

@ -1,31 +1,45 @@
use ed25519_zebra::{Signature, SigningKey, VerificationKey, VerificationKeyBytes,};
use jni::{objects::JClass, sys::{jboolean, jbyteArray}, JNIEnv,};
use std::{convert::TryFrom, panic, ptr,};
use ed25519::Signature;
use ed25519_zebra::{SigningKey, VerificationKey, VerificationKeyBytes};
use jni::{
objects::{JByteArray, JClass},
sys::jboolean,
JNIEnv,
};
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use ed25519::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePublicKey};
use std::{convert::TryFrom, panic, panic::AssertUnwindSafe};
#[cfg(feature = "pem")]
use der::pem::LineEnding;
#[cfg(feature = "pem")]
use jni::objects::JString;
mod utils;
use crate::utils::exception::unwrap_exc_or;
use crate::utils::exception::unwrap_exc_or_default;
#[no_mangle]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_checkVerificationKeyBytes(
env: JNIEnv<'_>,
_: JClass<'_>,
vk_bytes: jbyteArray,
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_checkVerificationKeyBytes<'local>(
env: JNIEnv<'local>,
_: JClass<'local>,
vk_bytes: JByteArray<'local>,
) -> jboolean {
let mut vkb = [0u8; 32];
vkb.copy_from_slice(&env.convert_byte_array(vk_bytes).unwrap());
let vkb_result = VerificationKeyBytes::try_from(VerificationKeyBytes::from(vkb));
let vkb_result = VerificationKeyBytes::try_from(vkb);
vkb_result.is_ok() as _
}
#[no_mangle]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getVerificationKeyBytes(
env: JNIEnv<'_>,
_: JClass<'_>,
sk_seed_bytes: jbyteArray,
) -> jbyteArray {
let res = panic::catch_unwind(|| {
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getVerificationKeyBytes<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
sk_seed_bytes: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut seed_data = [0u8; 32];
seed_data.copy_from_slice(&env.convert_byte_array(sk_seed_bytes).unwrap());
let sk = SigningKey::from(seed_data);
@ -33,18 +47,179 @@ pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getVerificationKey
let pkb_array: [u8; 32] = pkb.into();
Ok(env.byte_array_from_slice(&pkb_array).unwrap())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}));
unwrap_exc_or_default(&mut env, res)
}
// SigningKeySeed bytes -> DER bytes
#[no_mangle]
#[cfg(feature = "pkcs8")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getSigningKeySeedEncoded<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
sks_bytes: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut sks = [0u8; 32];
sks.copy_from_slice(&env.convert_byte_array(sks_bytes).unwrap());
let sk = SigningKey::from(sks);
Ok(env
.byte_array_from_slice(sk.to_pkcs8_der_v1().unwrap().as_bytes())
.unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
// SigningKeySeed bytes -> PEM string
#[no_mangle]
#[cfg(feature = "pem")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getSigningKeySeedPEM<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
sks_bytes: JByteArray<'local>,
) -> JString<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut sks = [0u8; 32];
sks.copy_from_slice(&env.convert_byte_array(sks_bytes).unwrap());
let sk = SigningKey::from(sks);
let output = env
.new_string(&*sk.to_pkcs8_pem_v1(LineEnding::default()).unwrap())
.expect("Couldn't create SKS PEM string!");
Ok(output.into())
}));
unwrap_exc_or_default(&mut env, res)
}
// DER bytes -> SigningKeySeed bytes
#[no_mangle]
#[cfg(feature = "pkcs8")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_generatePrivate<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
der_bytes: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut der_data = [0u8; 48];
der_data.copy_from_slice(&env.convert_byte_array(der_bytes).unwrap());
let sk = SigningKey::from_pkcs8_der(der_data.as_ref()).unwrap();
Ok(env.byte_array_from_slice(&sk.as_ref()).unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
// PEM string -> SigningKeySeed bytes
#[no_mangle]
#[cfg(feature = "pem")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_generatePrivatePEM<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
pem_java_string: JString<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let pem_string: String = env
.get_string(&pem_java_string)
.expect("Couldn't get PEM Java string!")
.into();
let sk = SigningKey::from_pkcs8_pem(&pem_string).unwrap();
Ok(env.byte_array_from_slice(&sk.as_ref()).unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
// VerificationKeyBytes bytes -> DER bytes
#[no_mangle]
#[cfg(feature = "pkcs8")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getVerificationKeyBytesEncoded<
'local,
>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
vk_bytes: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut vk_data = [0u8; 32];
vk_data.copy_from_slice(&env.convert_byte_array(vk_bytes).unwrap());
let vkb = VerificationKeyBytes::try_from(vk_data).unwrap();
let vk = VerificationKey::try_from(vkb).unwrap();
Ok(env
.byte_array_from_slice(vk.to_public_key_der().unwrap().as_ref())
.unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
// VerificationKeyBytes bytes -> PEM string
#[no_mangle]
#[cfg(feature = "pem")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_getVerificationKeyBytesPEM<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
vk_bytes: JByteArray<'local>,
) -> JString<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut vkb = [0u8; 32];
vkb.copy_from_slice(&env.convert_byte_array(vk_bytes).unwrap());
let vk = VerificationKey::try_from(VerificationKeyBytes::from(vkb)).unwrap();
let output = env
.new_string(vk.to_public_key_pem(LineEnding::default()).unwrap())
.expect("Couldn't create VKB PEM string!");
Ok(output.into())
}));
unwrap_exc_or_default(&mut env, res)
}
// DER bytes -> VerificationKeyBytes bytes
#[no_mangle]
#[cfg(feature = "pkcs8")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_generatePublic<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
der_bytes: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut der_data = [0u8; 44];
der_data.copy_from_slice(&env.convert_byte_array(der_bytes).unwrap());
let vk = VerificationKey::from_public_key_der(der_data.as_ref()).unwrap();
Ok(env.byte_array_from_slice(&vk.as_ref()).unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
// PEM string -> VerificationKeyBytes bytes
#[no_mangle]
#[cfg(feature = "pem")]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_generatePublicPEM<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
pem_java_string: JString<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let pem_string: String = env
.get_string(&pem_java_string)
.expect("Couldn't get VKB PEM Java string!")
.into();
let vk = VerificationKey::from_public_key_pem(&pem_string).unwrap();
Ok(env.byte_array_from_slice(&vk.as_ref()).unwrap())
}));
unwrap_exc_or_default(&mut env, res)
}
#[no_mangle]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_sign(
env: JNIEnv<'_>,
_: JClass<'_>,
sk_seed_bytes: jbyteArray,
msg: jbyteArray,
) -> jbyteArray {
let res = panic::catch_unwind(|| {
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_sign<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
sk_seed_bytes: JByteArray<'local>,
msg: JByteArray<'local>,
) -> JByteArray<'local> {
let res = panic::catch_unwind(AssertUnwindSafe(|| {
let mut seed_data = [0u8; 32];
seed_data.copy_from_slice(&env.convert_byte_array(sk_seed_bytes).unwrap());
let sk = SigningKey::from(seed_data);
@ -62,17 +237,17 @@ pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_sign(
};
Ok(env.byte_array_from_slice(&signature).unwrap())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}));
unwrap_exc_or_default(&mut env, res)
}
#[no_mangle]
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_verify(
env: JNIEnv<'_>,
_: JClass<'_>,
vk_bytes: jbyteArray,
signature: jbyteArray,
msg: jbyteArray,
pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_verify<'local>(
env: JNIEnv<'local>,
_: JClass<'local>,
vk_bytes: JByteArray<'local>,
signature: JByteArray<'local>,
msg: JByteArray<'local>,
) -> jboolean {
let mut vk_data = [0u8; 32];
vk_data.copy_from_slice(&env.convert_byte_array(vk_bytes).unwrap());
@ -87,7 +262,7 @@ pub extern "system" fn Java_org_zfnd_ed25519_Ed25519Interface_verify(
data
};
let vkb = VerificationKeyBytes::try_from(VerificationKeyBytes::from(vk_data)).unwrap();
let vkb = VerificationKeyBytes::try_from(vk_data).unwrap();
let vk = VerificationKey::try_from(vkb).unwrap();
let resbool = vk.verify(&signature, &msg).is_ok();
resbool as _

View File

@ -21,7 +21,7 @@ type ExceptionResult<T> = thread::Result<Result<T, Error>>;
// Returns value or "throws" exception. `error_val` is returned, because exception will be thrown
// at the Java side. So this function should be used only for the `panic::catch_unwind` result.
pub fn unwrap_exc_or<T>(env: &JNIEnv, res: ExceptionResult<T>, error_val: T) -> T {
pub fn unwrap_exc_or<T>(env: &mut JNIEnv, res: ExceptionResult<T>, error_val: T) -> T {
match res {
Ok(Ok(val)) => val,
Ok(Err(jni_error)) => {
@ -40,25 +40,24 @@ pub fn unwrap_exc_or<T>(env: &JNIEnv, res: ExceptionResult<T>, error_val: T) ->
}
}
/// Same as `unwrap_exc_or` but returns default value.
pub fn unwrap_exc_or_default<T: Default>(env: &mut JNIEnv, res: ExceptionResult<T>) -> T {
unwrap_exc_or(env, res, T::default())
}
// Calls a corresponding `JNIEnv` method, so exception will be thrown when execution returns to
// the Java side.
fn throw(env: &JNIEnv, description: &str) {
fn throw(env: &mut JNIEnv, description: &str) {
// We cannot throw exception from this function, so errors should be written in log instead.
let exception = match env.find_class("java/lang/RuntimeException") {
Ok(val) => val,
Err(e) => {
eprintln!(
"Unable to find 'RuntimeException' class: {}",
e
);
eprintln!("Unable to find 'RuntimeException' class: {}", e);
return;
}
};
if let Err(e) = env.throw_new(exception, description) {
eprintln!(
"Unable to find 'RuntimeException' class: {}",
e
);
eprintln!("Unable to find 'RuntimeException' class: {}", e);
}
}
@ -110,6 +109,6 @@ mod tests {
}
fn panic_error<T: Send + 'static>(val: T) -> Box<dyn Any + Send> {
panic::catch_unwind(panic::AssertUnwindSafe(|| panic!(val))).unwrap_err()
panic::catch_unwind(panic::AssertUnwindSafe(|| std::panic::panic_any(val))).unwrap_err()
}
}

View File

@ -13,13 +13,17 @@ script_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
ed25519jni_jvm_dir="${script_dir}/../jvm"
ed25519jni_rust_dir="${script_dir}/../rust"
# Script to run in order to compile a JAR with the Ed25519 JNI libraries from Rust.
# Script to place Rust libraries in a library readable by the JVM (run tests or build a JAR).
# Assumes SciJava's NativeLoader will be used.
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
nativeDir="${ed25519jni_jvm_dir}/natives/linux_64"
nativeSuffix="so"
elif [[ "$OSTYPE" == "darwin"* ]]; then
nativeDir="${ed25519jni_jvm_dir}/natives/osx_64"
if [[ "$(uname -m)" == 'x86_64' ]]; then
nativeDir="${ed25519jni_jvm_dir}/natives/osx_64"
else
nativeDir="${ed25519jni_jvm_dir}/natives/osx_arm64"
fi
nativeSuffix="dylib"
else
echo "JNI is unsupported on this OS. Exiting."

View File

@ -61,7 +61,8 @@ use hashbrown::HashMap;
use rand_core::{CryptoRng, RngCore};
use sha2::Sha512;
use crate::{Error, Signature, VerificationKey, VerificationKeyBytes};
use crate::{Error, VerificationKey, VerificationKeyBytes};
use ed25519::Signature;
// Shim to generate a u128 without importing `rand`.
fn gen_u128<R: RngCore + CryptoRng>(mut rng: R) -> u128 {
@ -88,7 +89,7 @@ impl<'msg, M: AsRef<[u8]> + ?Sized> From<(VerificationKeyBytes, Signature, &'msg
// Compute k now to avoid dependency on the msg lifetime.
let k = Scalar::from_hash(
Sha512::default()
.chain(&sig.R_bytes[..])
.chain(&sig.r_bytes()[..])
.chain(&vk_bytes.0[..])
.chain(msg),
);
@ -183,10 +184,10 @@ impl Verifier {
let mut A_coeff = Scalar::ZERO;
for (k, sig) in sigs.iter() {
let R = CompressedEdwardsY(sig.R_bytes)
let R = CompressedEdwardsY(*sig.r_bytes())
.decompress()
.ok_or(Error::InvalidSignature)?;
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(sig.s_bytes))
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(*sig.s_bytes()))
.ok_or(Error::InvalidSignature)?;
let z = Scalar::from(gen_u128(&mut rng));
B_coeff -= z * s;

View File

@ -10,11 +10,10 @@ extern crate std;
pub mod batch;
mod error;
mod signature;
mod signing_key;
mod verification_key;
pub use error::Error;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use verification_key::{VerificationKey, VerificationKeyBytes};
pub use ed25519::Signature;

View File

@ -1,54 +0,0 @@
use crate::Error;
use core::convert::TryFrom;
/// An Ed25519 signature.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_snake_case)]
pub struct Signature {
pub(crate) R_bytes: [u8; 32],
pub(crate) s_bytes: [u8; 32],
}
impl core::fmt::Debug for Signature {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
fmt.debug_struct("Signature")
.field("R_bytes", &hex::encode(&self.R_bytes))
.field("s_bytes", &hex::encode(&self.s_bytes))
.finish()
}
}
impl From<[u8; 64]> for Signature {
#[allow(non_snake_case)]
fn from(bytes: [u8; 64]) -> Signature {
let mut R_bytes = [0; 32];
R_bytes.copy_from_slice(&bytes[0..32]);
let mut s_bytes = [0; 32];
s_bytes.copy_from_slice(&bytes[32..64]);
Signature { R_bytes, s_bytes }
}
}
impl TryFrom<&[u8]> for Signature {
type Error = Error;
fn try_from(slice: &[u8]) -> Result<Signature, Error> {
if slice.len() == 64 {
let mut bytes = [0u8; 64];
bytes[..].copy_from_slice(slice);
Ok(bytes.into())
} else {
Err(Error::InvalidSliceLength)
}
}
}
impl From<Signature> for [u8; 64] {
fn from(sig: Signature) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&sig.R_bytes[..]);
bytes[32..64].copy_from_slice(&sig.s_bytes[..]);
bytes
}
}

View File

@ -1,11 +1,39 @@
use core::convert::TryFrom;
#[cfg(feature = "pkcs8")]
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); // RFC 8410
#[cfg(feature = "pkcs8")]
const ALGORITHM_ID: AlgorithmIdentifierRef = AlgorithmIdentifierRef {
oid: OID,
parameters: None,
};
use crate::Error;
use core::convert::{TryFrom, TryInto};
use curve25519_dalek::{constants, digest::Update, scalar::Scalar};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
use zeroize::Zeroize;
use crate::{Error, Signature, VerificationKey, VerificationKeyBytes};
pub use ed25519::{
signature::{Signer, Verifier},
ComponentBytes, Error as Ed25519Error, KeypairBytes, PublicKeyBytes, Signature,
};
#[cfg(all(feature = "pem", feature = "pkcs8"))]
use der::pem::LineEnding;
#[cfg(feature = "pkcs8")]
use pkcs8::der::SecretDocument;
#[cfg(feature = "pkcs8")]
use pkcs8::{
spki::AlgorithmIdentifierRef, DecodePrivateKey, DecodePublicKey, Document, EncodePrivateKey,
EncodePublicKey, ObjectIdentifier, PrivateKeyInfo,
};
#[cfg(all(feature = "pem", feature = "pkcs8"))]
use zeroize::Zeroizing;
#[cfg(all(feature = "pem", feature = "pkcs8"))]
use pkcs8::der::pem::PemLabel;
use crate::{VerificationKey, VerificationKeyBytes};
/// An Ed25519 signing key.
///
@ -55,13 +83,13 @@ impl From<SigningKey> for [u8; 32] {
impl TryFrom<&[u8]> for SigningKey {
type Error = Error;
fn try_from(slice: &[u8]) -> Result<SigningKey, Error> {
fn try_from(slice: &[u8]) -> Result<SigningKey, Self::Error> {
if slice.len() == 32 {
let mut bytes = [0u8; 32];
bytes[..].copy_from_slice(slice);
Ok(bytes.into())
} else {
Err(Error::InvalidSliceLength)
Err(Self::Error::InvalidSliceLength)
}
}
}
@ -104,6 +132,124 @@ impl From<[u8; 32]> for SigningKey {
}
}
#[cfg(feature = "pkcs8")]
impl<'a> TryFrom<PrivateKeyInfo<'a>> for SigningKey {
type Error = Error;
fn try_from(pki: PrivateKeyInfo) -> Result<Self, Self::Error> {
if pki.algorithm == ALGORITHM_ID {
SigningKey::try_from(pki.private_key)
} else {
Err(Self::Error::MalformedSecretKey)
}
}
}
#[cfg(feature = "pkcs8")]
impl EncodePublicKey for SigningKey {
/// Serialize the public key for a [`SigningKey`] to an ASN.1 DER-encoded document.
fn to_public_key_der(&self) -> pkcs8::spki::Result<Document> {
self.vk.to_public_key_der()
}
}
impl Signer<Signature> for SigningKey {
/// Generate a [`Signature`] using a given [`SigningKey`].
fn try_sign(&self, message: &[u8]) -> Result<Signature, ed25519::signature::Error> {
Ok(self.sign(message))
}
}
#[cfg(feature = "pkcs8")]
impl TryFrom<KeypairBytes> for SigningKey {
type Error = pkcs8::Error;
fn try_from(pkcs8_key: KeypairBytes) -> pkcs8::Result<Self> {
SigningKey::try_from(&pkcs8_key)
}
}
#[cfg(feature = "pkcs8")]
impl TryFrom<&KeypairBytes> for SigningKey {
type Error = pkcs8::Error;
fn try_from(pkcs8_key: &KeypairBytes) -> pkcs8::Result<Self> {
let signing_key = SigningKey::from_der(&pkcs8_key.secret_key);
// Validate the public key in the PKCS#8 document if present
if let Some(public_bytes) = &pkcs8_key.public_key {
let expected_verifying_key =
VerificationKey::from_public_key_der(public_bytes.as_ref())
.map_err(|_| pkcs8::Error::KeyMalformed)?;
if VerificationKey::try_from(&signing_key.unwrap())
.unwrap()
.A_bytes
!= expected_verifying_key.into()
{
return Err(pkcs8::Error::KeyMalformed);
}
}
signing_key
}
}
impl From<SigningKey> for KeypairBytes {
fn from(signing_key: SigningKey) -> KeypairBytes {
KeypairBytes::from(&signing_key)
}
}
impl From<&SigningKey> for KeypairBytes {
fn from(signing_key: &SigningKey) -> KeypairBytes {
KeypairBytes {
secret_key: signing_key.s.to_bytes(),
public_key: Some(PublicKeyBytes(signing_key.vk.try_into().unwrap())),
}
}
}
#[cfg(feature = "pkcs8")]
impl EncodePrivateKey for SigningKey {
/// Serialize [`SigningKey`] to an ASN.1 DER-encoded secret document. Note that this
/// will generate a v2 (RFC 5958) DER encoding with a public key.
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
// In RFC 8410, the octet string containing the private key is encapsulated by
// another octet string. Just add octet string bytes to the key when building
// the document.
let mut final_key = [0u8; 34];
final_key[..2].copy_from_slice(&[0x04, 0x20]);
final_key[2..].copy_from_slice(&self.seed);
SecretDocument::try_from(PrivateKeyInfo {
algorithm: ALGORITHM_ID,
private_key: &final_key,
public_key: Some(self.vk.A_bytes.0.as_slice()),
})
}
}
#[cfg(feature = "pkcs8")]
impl DecodePrivateKey for SigningKey {
/// Create a [`SigningKey`] from an ASN.1 DER-encoded bytes. The bytes may include an
/// accompanying public key, as defined in RFC 5958 (v1 and v2), but the call will
/// fail if the public key doesn't match the private key's true accompanying public
/// key.
fn from_pkcs8_der(bytes: &[u8]) -> pkcs8::Result<Self> {
let keypair = KeypairBytes::from_pkcs8_der(bytes).unwrap();
let sk = SigningKey::try_from(keypair.secret_key).unwrap();
match keypair.public_key {
Some(vk2) => {
if sk.vk.A_bytes.0 == vk2.to_bytes() {
Ok(sk)
} else {
Err(pkcs8::Error::KeyMalformed)
}
}
None => Ok(sk),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct SerdeHelper([u8; 32]);
@ -145,6 +291,38 @@ impl SigningKey {
let s_bytes = (r + k * self.s).to_bytes();
Signature { R_bytes, s_bytes }
Signature::from_components(R_bytes, s_bytes)
}
/// Parse [`SigningKey`] from ASN.1 DER bytes.
#[cfg(feature = "pkcs8")]
pub fn from_der(bytes: &[u8]) -> pkcs8::Result<Self> {
bytes
.try_into()
.map_err(|_| pkcs8::Error::ParametersMalformed)
}
/// Serialize [`SigningKey`] to an ASN.1 DER-encoded secret document. Note that this
/// will generate a v1 (RFC 5958) DER encoding without a public key.
#[cfg(feature = "pkcs8")]
pub fn to_pkcs8_der_v1(&self) -> pkcs8::Result<SecretDocument> {
// In RFC 8410, the octet string containing the private key is encapsulated by
// another octet string. Just add octet string bytes to the key when building
// the document.
let mut final_key = [0u8; 34];
final_key[..2].copy_from_slice(&[0x04, 0x20]);
final_key[2..].copy_from_slice(&self.seed);
SecretDocument::try_from(PrivateKeyInfo::new(ALGORITHM_ID, &final_key))
}
/// Serialize [`SigningKey`] as a PEM-encoded PKCS#8 string. Note that this
/// will generate a v1 (RFC 5958) PEM encoding without a public key.
#[cfg(all(feature = "pem", feature = "pkcs8"))]
pub fn to_pkcs8_pem_v1(
&self,
line_ending: LineEnding,
) -> Result<Zeroizing<String>, pkcs8::Error> {
let doc = self.to_pkcs8_der_v1()?;
Ok(doc.to_pem(PrivateKeyInfo::PEM_LABEL, line_ending)?)
}
}

View File

@ -1,5 +1,4 @@
use core::convert::{TryFrom, TryInto};
use curve25519_dalek::{
digest::Update,
edwards::{CompressedEdwardsY, EdwardsPoint},
@ -9,7 +8,21 @@ use curve25519_dalek::{
use sha2::Sha512;
use zeroize::DefaultIsZeroes;
use crate::{Error, Signature};
pub use ed25519::{
signature::{Signer, Verifier},
Signature,
};
#[cfg(feature = "pkcs8")]
use pkcs8::der::asn1::BitStringRef;
#[cfg(feature = "pkcs8")]
use pkcs8::spki::{
AlgorithmIdentifierRef, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfoRef,
};
#[cfg(feature = "pkcs8")]
use pkcs8::{Document, ObjectIdentifier};
use crate::Error;
/// A refinement type for `[u8; 32]` indicating that the bytes represent an
/// encoding of an Ed25519 verification key.
@ -38,7 +51,7 @@ pub struct VerificationKeyBytes(pub(crate) [u8; 32]);
impl core::fmt::Debug for VerificationKeyBytes {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
fmt.debug_tuple("VerificationKeyBytes")
.field(&hex::encode(&self.0))
.field(&hex::encode(self.0))
.finish()
}
}
@ -51,7 +64,7 @@ impl AsRef<[u8]> for VerificationKeyBytes {
impl TryFrom<&[u8]> for VerificationKeyBytes {
type Error = Error;
fn try_from(slice: &[u8]) -> Result<VerificationKeyBytes, Error> {
fn try_from(slice: &[u8]) -> Result<VerificationKeyBytes, Self::Error> {
if slice.len() == 32 {
let mut bytes = [0u8; 32];
bytes[..].copy_from_slice(slice);
@ -74,6 +87,15 @@ impl From<VerificationKeyBytes> for [u8; 32] {
}
}
#[cfg(feature = "pkcs8")]
impl<'a> TryFrom<SubjectPublicKeyInfoRef<'a>> for VerificationKeyBytes {
type Error = Error;
fn try_from(spki: SubjectPublicKeyInfoRef) -> Result<VerificationKeyBytes, Error> {
Ok(VerificationKeyBytes::try_from(spki.subject_public_key.as_bytes().unwrap()).unwrap())
}
}
/// A valid Ed25519 verification key.
///
/// This is also called a public key by other implementations.
@ -164,6 +186,44 @@ impl TryFrom<[u8; 32]> for VerificationKey {
}
}
#[cfg(feature = "pkcs8")]
impl EncodePublicKey for VerificationKey {
/// Serialize [`VerificationKey`] to an ASN.1 DER-encoded document.
fn to_public_key_der(&self) -> pkcs8::spki::Result<Document> {
let alg_info = AlgorithmIdentifierRef {
oid: ObjectIdentifier::new_unwrap("1.3.101.112"), // RFC 8410
parameters: None,
};
SubjectPublicKeyInfoRef {
algorithm: alg_info,
subject_public_key: BitStringRef::from_bytes(&self.A_bytes.0[..])?,
}
.try_into()
}
}
#[cfg(feature = "pkcs8")]
impl DecodePublicKey for VerificationKey {
/// Deserialize [`VerificationKey`] from ASN.1 DER bytes (32 bytes).
fn from_public_key_der(bytes: &[u8]) -> Result<Self, pkcs8::spki::Error> {
let spki = SubjectPublicKeyInfoRef::try_from(bytes).unwrap();
let pk_bytes = spki.subject_public_key.as_bytes().unwrap();
Ok(Self::try_from(pk_bytes).unwrap())
}
}
impl Verifier<Signature> for VerificationKey {
/// Verify a [`Signature`] object against a given [`VerificationKey`].
fn verify(
&self,
message: &[u8],
signature: &Signature,
) -> Result<(), ed25519::signature::Error> {
self.verify(signature, message)
.map_err(|_| ed25519::signature::Error::new())
}
}
impl VerificationKey {
/// Verify a purported `signature` on the given `msg`.
///
@ -189,7 +249,7 @@ impl VerificationKey {
pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
let k = Scalar::from_hash(
Sha512::default()
.chain(&signature.R_bytes[..])
.chain(&signature.r_bytes()[..])
.chain(&self.A_bytes.0[..])
.chain(msg),
);
@ -201,10 +261,10 @@ impl VerificationKey {
#[allow(non_snake_case)]
pub(crate) fn verify_prehashed(&self, signature: &Signature, k: Scalar) -> Result<(), Error> {
// `s_bytes` MUST represent an integer less than the prime `l`.
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(signature.s_bytes))
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(*signature.s_bytes()))
.ok_or(Error::InvalidSignature)?;
// `R_bytes` MUST be an encoding of a point on the twisted Edwards form of Curve25519.
let R = CompressedEdwardsY(signature.R_bytes)
let R = CompressedEdwardsY(*signature.r_bytes())
.decompress()
.ok_or(Error::InvalidSignature)?;
// We checked the encoding of A_bytes when constructing `self`.

90
tests/decoding.rs Normal file
View File

@ -0,0 +1,90 @@
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use ed25519_zebra::*;
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use hex;
#[cfg(feature = "pkcs8")]
use pkcs8::{DecodePrivateKey, DecodePublicKey};
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V1_PEM: &str = include_str!("examples/pkcs8-v1.pem");
/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V2_PEM: &str = include_str!("examples/pkcs8-v2.pem");
/// Ed25519 PKCS#8 v2 private key + mismatched public key encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PKCS8_V2_DER_BAD: &[u8] = include_bytes!("examples/pkcs8-v2-bad-ver-key.der");
/// Ed25519 PKCS#8 v2 private key + mismatched public key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V2_PEM_BAD: &str = include_str!("examples/pkcs8-v2-bad-ver-key.pem");
/// Ed25519 SubjectPublicKeyInfo encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der");
/// Ed25519 SubjectPublicKeyInfo encoded as PEM.
#[cfg(feature = "pem")]
const PUBLIC_KEY_PEM: &str = include_str!("examples/pubkey.pem");
#[test]
#[cfg(feature = "pkcs8")]
fn decode_der_to_signing_key() {
// Test against a v1 DER key.
let sk1 = SigningKey::from_pkcs8_der(PKCS8_V1_DER).unwrap();
let sk_bytes_string_1 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
assert_eq!(hex::decode(sk_bytes_string_1).unwrap(), sk1.as_ref());
// Test against a v2 DER key.
let sk2 = SigningKey::from_pkcs8_der(PKCS8_V2_DER).unwrap();
let sk_bytes_string_2 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
assert_eq!(hex::decode(sk_bytes_string_2).unwrap(), sk2.as_ref());
// Test against a v2 DER key with a mismatched public key.
assert!(SigningKey::from_pkcs8_der(PKCS8_V2_DER_BAD).is_err());
}
#[test]
#[cfg(feature = "pem")]
fn decode_doc_to_signing_key() {
// Test against a v1 PEM key.
let sk1 = SigningKey::from_pkcs8_pem(PKCS8_V1_PEM).unwrap();
let sk_bytes_string_1 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
assert_eq!(hex::decode(sk_bytes_string_1).unwrap(), sk1.as_ref());
// Test against a valid v2 PEM key.
let sk2 = SigningKey::from_pkcs8_pem(PKCS8_V2_PEM).unwrap();
let sk_bytes_string_2 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
assert_eq!(hex::decode(sk_bytes_string_2).unwrap(), sk2.as_ref());
// Test against a v2 DER key with a mismatched public key.
assert!(SigningKey::from_pkcs8_pem(PKCS8_V2_PEM_BAD).is_err());
}
#[test]
#[cfg(feature = "pkcs8")]
fn decode_der_to_verification_key() {
let vk = VerificationKey::from_public_key_der(PUBLIC_KEY_DER).unwrap();
let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1";
assert_eq!(hex::decode(vk_bytes_string).unwrap(), vk.as_ref());
}
#[test]
#[cfg(feature = "pem")]
fn decode_doc_to_verification_key() {
let vk = VerificationKey::from_public_key_pem(PUBLIC_KEY_PEM).unwrap();
let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1";
assert_eq!(hex::decode(vk_bytes_string).unwrap(), vk.as_ref());
}

97
tests/encoding.rs Normal file
View File

@ -0,0 +1,97 @@
#[cfg(feature = "pem")]
use der::pem::LineEnding;
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use ed25519_zebra::*;
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use hex;
#[cfg(feature = "pkcs8")]
pub use pkcs8::{
spki::AlgorithmIdentifierRef, EncodePrivateKey, EncodePublicKey, ObjectIdentifier,
PrivateKeyInfo,
};
#[cfg(any(feature = "pem", feature = "pkcs8"))]
use std::convert::TryFrom;
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V1_PEM: &str = include_str!("examples/pkcs8-v1.pem");
/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V2_PEM: &str = include_str!("examples/pkcs8-v2.pem");
/// Ed25519 SubjectPublicKeyInfo encoded as ASN.1 DER.
#[cfg(feature = "pkcs8")]
const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der");
/// Ed25519 SubjectPublicKeyInfo encoded as PEM.
#[cfg(feature = "pem")]
const PUBLIC_KEY_PEM: &str = include_str!("examples/pubkey.pem");
#[test]
#[cfg(feature = "pkcs8")]
fn encode_signing_key_to_der() {
let sk_bytes_string = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
let mut sk_array = [0u8; 32];
hex::decode_to_slice(sk_bytes_string, &mut sk_array as &mut [u8]).ok();
let sk = SigningKey::from(sk_array);
let vk = sk.to_public_key_der().unwrap();
assert_eq!(sk.to_pkcs8_der_v1().unwrap().as_bytes(), PKCS8_V1_DER);
assert_eq!(sk.to_pkcs8_der().unwrap().as_bytes(), PKCS8_V2_DER);
assert_eq!(vk.as_bytes(), PUBLIC_KEY_DER);
}
#[test]
#[cfg(feature = "pem")]
fn encode_signing_key_to_pem() {
let sk_bytes_string = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842";
let mut sk_array = [0u8; 32];
hex::decode_to_slice(sk_bytes_string, &mut sk_array as &mut [u8]).ok();
let sk = SigningKey::from(sk_array);
let vk = sk.to_public_key_pem(LineEnding::default()).unwrap();
assert_eq!(
sk.to_pkcs8_pem_v1(LineEnding::default())
.unwrap()
.as_bytes(),
PKCS8_V1_PEM.as_bytes()
);
assert_eq!(
sk.to_pkcs8_pem(LineEnding::default()).unwrap().as_bytes(),
PKCS8_V2_PEM.as_bytes()
);
assert_eq!(vk, PUBLIC_KEY_PEM);
}
#[test]
#[cfg(feature = "pkcs8")]
fn encode_verification_key_to_der() {
let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1";
let mut vk_array = [0u8; 32];
hex::decode_to_slice(vk_bytes_string, &mut vk_array as &mut [u8]).ok();
let vk = VerificationKey::try_from(vk_array).unwrap();
let pkd = vk.to_public_key_der().unwrap();
assert_eq!(pkd.as_ref(), PUBLIC_KEY_DER);
}
#[test]
#[cfg(feature = "pem")]
fn encode_verification_key_to_pem() {
let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1";
let mut vk_array = [0u8; 32];
hex::decode_to_slice(vk_bytes_string, &mut vk_array as &mut [u8]).ok();
let vk = VerificationKey::try_from(vk_array).unwrap();
let pem = vk.to_public_key_pem(LineEnding::default()).unwrap();
assert_eq!(pem, PUBLIC_KEY_PEM);
}

BIN
tests/examples/pkcs8-v1.der Normal file

Binary file not shown.

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
-----END PRIVATE KEY-----

Binary file not shown.

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
Z9xLlshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----

BIN
tests/examples/pkcs8-v2.der Normal file

Binary file not shown.

View File

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MFECAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
gSEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----

BIN
tests/examples/pubkey.der Normal file

Binary file not shown.

View File

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PUBLIC KEY-----

View File

@ -5,13 +5,15 @@
//! in consensus.rs.
use bincode;
use ed25519::Signature;
use ed25519_zebra::*;
use hex;
fn rfc8032_test_case(sk_bytes: Vec<u8>, pk_bytes: Vec<u8>, sig_bytes: Vec<u8>, msg: Vec<u8>) {
let sk: SigningKey = bincode::deserialize(&sk_bytes).expect("sk should deserialize");
let pk: VerificationKey = bincode::deserialize(&pk_bytes).expect("pk should deserialize");
let sig: Signature = bincode::deserialize(&sig_bytes).expect("sig should deserialize");
let sig: Signature =
Signature::from_slice(sig_bytes.as_slice()).expect("sig should deserialize");
assert!(pk.verify(&sig, &msg).is_ok(), "verification failed");

View File

@ -89,7 +89,9 @@ fn conformance() -> Result<(), Report> {
#[test]
fn individual_matches_batch_verification() -> Result<(), Report> {
use core::convert::TryFrom;
use ed25519_zebra::{batch, Signature, VerificationKey, VerificationKeyBytes};
use ed25519::Signature;
use ed25519_zebra::{batch, VerificationKey, VerificationKeyBytes};
for case in SMALL_ORDER_SIGS.iter() {
let msg = b"Zcash";
let sig = Signature::from(case.sig_bytes);

View File

@ -32,12 +32,10 @@ fn parsing() {
let sk3: SigningKey = bincode::deserialize(sk.as_ref()).unwrap();
let pk3: VerificationKey = bincode::deserialize(pk.as_ref()).unwrap();
let pkb3: VerificationKeyBytes = bincode::deserialize(pkb.as_ref()).unwrap();
let sig3: Signature = bincode::deserialize(<[u8; 64]>::from(sig).as_ref()).unwrap();
assert_eq!(&sk_array[..], sk3.as_ref());
assert_eq!(&pk_array[..], pk3.as_ref());
assert_eq!(&pkb_array[..], pkb3.as_ref());
assert_eq!(&sig_array[..], <[u8; 64]>::from(sig3).as_ref());
}
#[test]