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
5 changes: 3 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- run: cargo test --target ${{ matrix.target }} --features batch
- run: cargo test --target ${{ matrix.target }} --features batch_deterministic
- run: cargo test --target ${{ matrix.target }} --features serde
- run: cargo test --target ${{ matrix.target }} --features pkcs8

build-simd:
name: Test simd backend (nightly)
Expand All @@ -46,7 +47,7 @@ jobs:
run: cargo build --target x86_64-unknown-linux-gnu

msrv:
name: Current MSRV is 1.56.1
name: Current MSRV is 1.57.0
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -56,7 +57,7 @@ jobs:
- run: cargo -Z minimal-versions check --no-default-features --features serde
# Now check that `cargo build` works with respect to the oldest possible
# deps and the stated MSRV
- uses: dtolnay/rust-toolchain@1.56.1
- uses: dtolnay/rust-toolchain@1.57.0
- run: cargo build

bench:
Expand Down
17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ keywords = ["cryptography", "ed25519", "curve25519", "signature", "ECC"]
categories = ["cryptography", "no-std"]
description = "Fast and efficient ed25519 EdDSA key generations, signing, and verification in pure Rust."
exclude = [ ".gitignore", "TESTVECTORS", "res/*" ]
rust-version = "1.57"

[badges]
travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"}

[package.metadata.docs.rs]
# Disabled for now since this is borked; tracking https://github.com/rust-lang/docs.rs/issues/302
# rustdoc-args = ["--html-in-header", ".cargo/registry/src/github.zerozr99.workers.dev-1ecc6299db9ec823/curve25519-dalek-0.13.2/rustdoc-include-katex-header.html"]
features = ["nightly", "batch"]
rustdoc-args = ["--cfg", "docsrs"]
features = ["nightly", "batch", "pkcs8"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nightly doesn't exist anymore, but we can get that in another PR


[dependencies]
curve25519-dalek = { version = "=4.0.0-pre.2", default-features = false, features = ["digest", "rand_core"] }
ed25519 = { version = "=2.0.0-pre.0", default-features = false }
curve25519-dalek = { version = "=4.0.0-pre.3", default-features = false, features = ["digest", "rand_core"] }
ed25519 = { version = "=2.0.0-pre.1", default-features = false }
merlin = { version = "3", default-features = false, optional = true }
rand = { version = "0.8", default-features = false, optional = true }
rand_core = { version = "0.6", default-features = false, optional = true }
Expand All @@ -37,6 +39,7 @@ hex = "^0.4"
bincode = "1.0"
serde_json = "1.0"
criterion = "0.3"
hex-literal = "0.3"
rand = "0.8"
serde_crate = { package = "serde", version = "1.0", features = ["derive"] }
toml = { version = "0.5" }
Expand All @@ -49,15 +52,13 @@ required-features = ["batch"]
[features]
default = ["std", "rand"]
std = ["alloc", "ed25519/std", "serde_crate/std", "sha2/std", "rand/std"]
alloc = ["curve25519-dalek/alloc", "rand/alloc", "zeroize/alloc"]
alloc = ["curve25519-dalek/alloc", "ed25519/alloc", "rand/alloc", "zeroize/alloc"]
serde = ["serde_crate", "serde_bytes", "ed25519/serde"]
batch = ["alloc", "merlin", "rand/std"]
# This feature enables deterministic batch verification.
batch_deterministic = ["alloc", "merlin", "rand", "rand_core"]
asm = ["sha2/asm"]
# This features turns off stricter checking for scalar malleability in signatures
legacy_compatibility = []

[patch.crates-io]
curve25519-dalek = { git = "https://github.com/dalek-cryptography/curve25519-dalek.git", branch = "release/4.0" }
ed25519 = { git = "https://github.com/RustCrypto/signatures.git"}
pkcs8 = ["ed25519/pkcs8"]
pem = ["alloc", "ed25519/pem", "pkcs8"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ version = "1"

# Minimum Supported Rust Version

This crate requires Rust 1.56.1 at a minimum. 1.x releases of this crate supported an MSRV of 1.41.
This crate requires Rust 1.57.0 at a minimum. 1.x releases of this crate supported an MSRV of 1.41.

In the future, MSRV changes will be accompanied by a minor version bump.

Expand Down
80 changes: 80 additions & 0 deletions src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

//! ed25519 keypairs.

#[cfg(feature = "pkcs8")]
use ed25519::pkcs8::{self, DecodePrivateKey};

#[cfg(feature = "rand")]
use rand::{CryptoRng, RngCore};

Expand Down Expand Up @@ -431,6 +434,83 @@ impl Verifier<ed25519::Signature> for Keypair {
}
}

impl TryFrom<&[u8]> for Keypair {
type Error = SignatureError;

fn try_from(bytes: &[u8]) -> Result<Keypair, SignatureError> {
Keypair::from_bytes(bytes)
Comment thread
rozbb marked this conversation as resolved.
}
}

#[cfg(feature = "pkcs8")]
impl DecodePrivateKey for Keypair {}

#[cfg(all(feature = "alloc", feature = "pkcs8"))]
impl pkcs8::EncodePrivateKey for Keypair {
fn to_pkcs8_der(&self) -> pkcs8::Result<pkcs8::SecretDocument> {
pkcs8::KeypairBytes::from(self).to_pkcs8_der()
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::KeypairBytes> for Keypair {
type Error = pkcs8::Error;

fn try_from(pkcs8_key: pkcs8::KeypairBytes) -> pkcs8::Result<Self> {
Keypair::try_from(&pkcs8_key)
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<&pkcs8::KeypairBytes> for Keypair {
type Error = pkcs8::Error;

fn try_from(pkcs8_key: &pkcs8::KeypairBytes) -> pkcs8::Result<Self> {
let secret = SecretKey::from_bytes(&pkcs8_key.secret_key)
.map_err(|_| pkcs8::Error::KeyMalformed)?;

let public = PublicKey::from(&secret);

// Validate the public key in the PKCS#8 document if present
if let Some(public_bytes) = pkcs8_key.public_key {
let pk = PublicKey::from_bytes(public_bytes.as_ref())
.map_err(|_| pkcs8::Error::KeyMalformed)?;

if public != pk {
Comment thread
rozbb marked this conversation as resolved.
return Err(pkcs8::Error::KeyMalformed);
}
}

Ok(Keypair { secret, public })
}
}

#[cfg(feature = "pkcs8")]
impl From<Keypair> for pkcs8::KeypairBytes {
fn from(keypair: Keypair) -> pkcs8::KeypairBytes {
pkcs8::KeypairBytes::from(&keypair)
}
}

#[cfg(feature = "pkcs8")]
impl From<&Keypair> for pkcs8::KeypairBytes {
fn from(keypair: &Keypair) -> pkcs8::KeypairBytes {
pkcs8::KeypairBytes {
secret_key: keypair.secret.to_bytes(),
public_key: Some(pkcs8::PublicKeyBytes(keypair.public.to_bytes())),
}
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for Keypair {
type Error = pkcs8::Error;

fn try_from(private_key: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result<Self> {
pkcs8::KeypairBytes::try_from(private_key)?.try_into()
}
}

#[cfg(feature = "serde")]
impl Serialize for Keypair {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand Down
43 changes: 43 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,44 @@
//! # }
//! ```
//!
//! ### PKCS#8 Key Encoding
//!
//! PKCS#8 is a private key format with support for multiple algorithms.
//! It can be encoded as binary (DER) or text (PEM).
//!
//! You can recognize PEM-encoded PKCS#8 keys by the following:
//!
//! ```text
//! -----BEGIN PRIVATE KEY-----
//! ```
//!
//! To use PKCS#8, you need to enable the `pkcs8` crate feature.
//!
//! The following traits can be used to decode/encode [`Keypair`] and
//! [`PublicKey`] as PKCS#8. Note that [`pkcs8`] is re-exported from the
//! toplevel of the crate:
//!
//! - [`pkcs8::DecodePrivateKey`]: decode private keys from PKCS#8
//! - [`pkcs8::EncodePrivateKey`]: encode private keys to PKCS#8
//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8
//! - [`pkcs8::EncodePublicKey`]: encode public keys to PKCS#8
//!
//! #### Example
//!
//! NOTE: this requires the `pem` crate feature.
//!
#![cfg_attr(feature = "pem", doc = "```")]
#![cfg_attr(not(feature = "pem"), doc = "```ignore")]
//! use ed25519_dalek::{PublicKey, pkcs8::DecodePublicKey};
//!
//! let pem = "-----BEGIN PUBLIC KEY-----
//! MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
//! -----END PUBLIC KEY-----";
//!
//! let public_key = PublicKey::from_public_key_pem(pem)
//! .expect("invalid public key PEM");
//! ```
//!
//! ### Using Serde
//!
//! If you prefer the bytes to be wrapped in another serialisation format, all
Expand Down Expand Up @@ -208,6 +246,8 @@
#![warn(future_incompatible, rust_2018_idioms)]
#![deny(missing_docs)] // refuse to compile if documentation is missing
#![cfg_attr(not(test), forbid(unsafe_code))]
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))]
#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))]

#[cfg(any(feature = "batch", feature = "batch_deterministic"))]
extern crate alloc;
Expand Down Expand Up @@ -243,3 +283,6 @@ pub use crate::secret::*;
// Re-export the `Signer` and `Verifier` traits from the `signature` crate
pub use ed25519::signature::{Signer, Verifier};
pub use ed25519::Signature;

#[cfg(feature = "pkcs8")]
pub use ed25519::pkcs8;
62 changes: 62 additions & 0 deletions src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ use ed25519::signature::Verifier;

pub use sha2::Sha512;

#[cfg(feature = "pkcs8")]
use ed25519::pkcs8::{self, DecodePublicKey};

#[cfg(feature = "serde")]
use serde::de::Error as SerdeError;
#[cfg(feature = "serde")]
Expand Down Expand Up @@ -350,6 +353,65 @@ impl Verifier<ed25519::Signature> for PublicKey {
}
}

impl TryFrom<&[u8]> for PublicKey {
type Error = SignatureError;

fn try_from(bytes: &[u8]) -> Result<PublicKey, SignatureError> {
PublicKey::from_bytes(bytes)
Comment thread
rozbb marked this conversation as resolved.
}
}

#[cfg(feature = "pkcs8")]
impl DecodePublicKey for PublicKey {}

#[cfg(all(feature = "alloc", feature = "pkcs8"))]
impl pkcs8::EncodePublicKey for PublicKey {
fn to_public_key_der(&self) -> pkcs8::spki::Result<pkcs8::Document> {
pkcs8::PublicKeyBytes::from(self).to_public_key_der()
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::PublicKeyBytes> for PublicKey {
type Error = pkcs8::spki::Error;

fn try_from(pkcs8_key: pkcs8::PublicKeyBytes) -> pkcs8::spki::Result<Self> {
PublicKey::try_from(&pkcs8_key)
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<&pkcs8::PublicKeyBytes> for PublicKey {
type Error = pkcs8::spki::Error;

fn try_from(pkcs8_key: &pkcs8::PublicKeyBytes) -> pkcs8::spki::Result<Self> {
PublicKey::from_bytes(pkcs8_key.as_ref()).map_err(|_| pkcs8::spki::Error::KeyMalformed)
}
}

#[cfg(feature = "pkcs8")]
impl From<PublicKey> for pkcs8::PublicKeyBytes {
fn from(public_key: PublicKey) -> pkcs8::PublicKeyBytes {
pkcs8::PublicKeyBytes::from(&public_key)
}
}

#[cfg(feature = "pkcs8")]
impl From<&PublicKey> for pkcs8::PublicKeyBytes {
fn from(public_key: &PublicKey) -> pkcs8::PublicKeyBytes {
pkcs8::PublicKeyBytes(public_key.to_bytes())
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::spki::SubjectPublicKeyInfo<'_>> for PublicKey {
type Error = pkcs8::spki::Error;

fn try_from(public_key: pkcs8::spki::SubjectPublicKeyInfo<'_>) -> pkcs8::spki::Result<Self> {
pkcs8::PublicKeyBytes::try_from(public_key)?.try_into()
}
}

#[cfg(feature = "serde")]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand Down
2 changes: 1 addition & 1 deletion src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn check_scalar(bytes: [u8; 32]) -> Result<Scalar, SignatureError> {
return Ok(Scalar::from_bits(bytes));
}

match Scalar::from_canonical_bytes(bytes) {
match Scalar::from_canonical_bytes(bytes).into() {
None => return Err(InternalError::ScalarFormatError.into()),
Some(x) => return Ok(x),
};
Expand Down
2 changes: 1 addition & 1 deletion tests/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ mod vectors {
fn non_null_scalar() -> Scalar {
let mut rng = rand::rngs::OsRng;
let mut s_candidate = Scalar::random(&mut rng);
while s_candidate == Scalar::zero() {
while s_candidate == Scalar::ZERO {
s_candidate = Scalar::random(&mut rng);
}
s_candidate
Expand Down
Binary file added tests/examples/pkcs8-v1.der
Binary file not shown.
Binary file added tests/examples/pkcs8-v2.der
Binary file not shown.
Binary file added tests/examples/pubkey.der
Binary file not shown.
Loading