Skip to content

Commit

Permalink
feat: impl DynSignatureAlgorithmIdentifier for ed5519-dalek
Browse files Browse the repository at this point in the history
feat: add x509 feature flag which enables ed25519-dalek to be used as a x509-cert signer
  • Loading branch information
juliusl committed Oct 3, 2024
1 parent cbf794d commit 0e44116
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 1 deletion.
2 changes: 2 additions & 0 deletions ed25519-dalek/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ merlin = { version = "3", default-features = false, optional = true }
rand_core = { version = "0.6.4", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, optional = true }
zeroize = { version = "1.5", default-features = false, optional = true }
x509-cert = { version = "0.2.5", features = ["builder"], optional = true }

[dev-dependencies]
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false, features = ["digest", "rand_core"] }
Expand Down Expand Up @@ -71,6 +72,7 @@ digest = ["signature/digest"]
hazmat = []
# Turns off stricter checking for scalar malleability in signatures
legacy_compatibility = ["curve25519-dalek/legacy_compatibility"]
x509 = ["pkcs8", "alloc", "dep:x509-cert"]
pkcs8 = ["ed25519/pkcs8"]
pem = ["alloc", "ed25519/pem", "pkcs8"]
rand_core = ["dep:rand_core"]
Expand Down
45 changes: 45 additions & 0 deletions ed25519-dalek/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,52 @@ pub use crate::verifying::*;
#[cfg(feature = "digest")]
pub use ed25519::signature::{DigestSigner, DigestVerifier};
pub use ed25519::signature::{Signer, Verifier};

#[cfg(not(feature = "x509"))]
pub use ed25519::Signature;

#[cfg(feature = "x509")]
pub use signature_wrapper::Signature;

#[cfg(feature = "x509")]
mod signature_wrapper {
use core::ops::Deref;
use core::ops::DerefMut;

/// Wrapper over ed25519::Signature to enable additional trait implementations required to build x509 certificates
#[derive(Copy, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Signature(pub ed25519::Signature);

impl Signature {
/// Parse an Ed25519 signature from a byte slice.
pub fn from_bytes(bytes: &ed25519::SignatureBytes) -> Self {
Self(ed25519::Signature::from_bytes(bytes))
}
}

impl TryFrom<&[u8]> for Signature {
type Error = ed25519::Error;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(Self(ed25519::Signature::try_from(value)?))
}
}

impl Deref for Signature {
type Target = ed25519::Signature;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Signature {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
}

#[cfg(feature = "pkcs8")]
pub use ed25519::pkcs8;
16 changes: 16 additions & 0 deletions ed25519-dalek/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,19 @@ impl From<InternalSignature> for ed25519::Signature {
ed25519::Signature::from_components(*sig.R.as_bytes(), *sig.s.as_bytes())
}
}

#[cfg(feature = "x509")]
impl From<InternalSignature> for crate::Signature {
fn from(value: InternalSignature) -> Self {
crate::Signature(ed25519::Signature::from(value))
}
}

#[cfg(feature = "x509")]
impl ed25519::pkcs8::spki::SignatureBitStringEncoding for crate::Signature {
fn to_bitstring(&self) -> x509_cert::der::Result<x509_cert::der::asn1::BitString> {
let signature: ed25519::Signature = self.0.into();

x509_cert::der::asn1::BitString::new(0, signature.to_vec())
}
}
9 changes: 9 additions & 0 deletions ed25519-dalek/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,15 @@ impl pkcs8::EncodePrivateKey for SigningKey {
}
}

#[cfg(feature = "pkcs8")]
impl pkcs8::spki::DynSignatureAlgorithmIdentifier for SigningKey {
fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<pkcs8::spki::AlgorithmIdentifierOwned> {
// From https://datatracker.ietf.org/doc/html/rfc8410
// `id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }`
Ok(pkcs8::spki::AlgorithmIdentifier { oid: ed25519::pkcs8::ALGORITHM_OID, parameters: None })
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::KeypairBytes> for SigningKey {
type Error = pkcs8::Error;
Expand Down
9 changes: 9 additions & 0 deletions ed25519-dalek/src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,15 @@ impl pkcs8::EncodePublicKey for VerifyingKey {
}
}

#[cfg(feature = "pkcs8")]
impl pkcs8::spki::DynSignatureAlgorithmIdentifier for VerifyingKey {
fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<pkcs8::spki::AlgorithmIdentifierOwned> {
// From https://datatracker.ietf.org/doc/html/rfc8410
// `id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }`
Ok(ed25519::pkcs8::spki::AlgorithmIdentifierOwned { oid: ed25519::pkcs8::ALGORITHM_OID, parameters: None })
}
}

#[cfg(feature = "pkcs8")]
impl TryFrom<pkcs8::PublicKeyBytes> for VerifyingKey {
type Error = pkcs8::spki::Error;
Expand Down
51 changes: 50 additions & 1 deletion ed25519-dalek/tests/pkcs8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
//! RFC5958 (PKCS#8) and RFC5280 (SPKI).

#![cfg(feature = "pkcs8")]

use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
use ed25519_dalek::{SigningKey, VerifyingKey};
use hex_literal::hex;

#[cfg(feature = "alloc")]
use ed25519_dalek::pkcs8::{EncodePrivateKey, EncodePublicKey};

#[cfg(feature = "x509")]
use x509_cert::builder::Builder;
#[cfg(feature = "x509")]
use x509_cert::der::EncodePem;
#[cfg(feature = "x509")]
use x509_cert::spki::DynSignatureAlgorithmIdentifier;

/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");

Expand Down Expand Up @@ -69,3 +75,46 @@ fn encode_verifying_key() {
let verifying_key2 = VerifyingKey::from_public_key_der(verifying_key_der.as_bytes()).unwrap();
assert_eq!(verifying_key, verifying_key2);
}

#[cfg(feature = "x509")]
#[test]
fn build_valid_x509_cert() {
use std::time::Duration;
use std::str::FromStr;
use x509_cert::{
builder::{CertificateBuilder, Profile},
name::Name,
serial_number::SerialNumber,
spki:: SubjectPublicKeyInfoOwned,
time::Validity,
};
let profile = Profile::Root;
let serial_number = SerialNumber::from(42u32);
let validity = Validity::from_now(Duration::new(360, 0)).unwrap();
let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
let signing = SigningKey::from_bytes(&SK_BYTES);
let verifying_key = VerifyingKey::from_bytes(&PK_BYTES).unwrap();
let public_key = verifying_key.to_public_key_der().unwrap();
let key_info =
SubjectPublicKeyInfoOwned::try_from(&public_key.as_bytes()[..]).unwrap();

let builder = CertificateBuilder::new(
profile,
serial_number,
validity,
subject,
key_info,
&signing,
)
.expect("should create certificate");

let certificate = builder.build().unwrap();
certificate.to_pem(x509_cert::der::pem::LineEnding::LF).expect("should generate pem");

// Note: In order to verify the certificate the same way the x509_cert crate does it via `x509-cert-test-support`, it requires an additional `zlint` tool to be installed
// The tool is installed via `go install github.com/zmap/zlint/v3/cmd/zlint@latest`.
//
// TODO: Blocked by: https://github.com/zmap/zlint/issues/883
// let ignored = &[];
// x509_cert_test_support::zlint::check_certificate(pem.as_bytes(), ignored);
}

0 comments on commit 0e44116

Please sign in to comment.