Skip to content

Commit 0e44116

Browse files
committed
feat: impl DynSignatureAlgorithmIdentifier for ed5519-dalek
feat: add x509 feature flag which enables ed25519-dalek to be used as a x509-cert signer
1 parent cbf794d commit 0e44116

File tree

6 files changed

+131
-1
lines changed

6 files changed

+131
-1
lines changed

ed25519-dalek/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ merlin = { version = "3", default-features = false, optional = true }
3737
rand_core = { version = "0.6.4", default-features = false, optional = true }
3838
serde = { version = "1.0", default-features = false, optional = true }
3939
zeroize = { version = "1.5", default-features = false, optional = true }
40+
x509-cert = { version = "0.2.5", features = ["builder"], optional = true }
4041

4142
[dev-dependencies]
4243
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false, features = ["digest", "rand_core"] }
@@ -71,6 +72,7 @@ digest = ["signature/digest"]
7172
hazmat = []
7273
# Turns off stricter checking for scalar malleability in signatures
7374
legacy_compatibility = ["curve25519-dalek/legacy_compatibility"]
75+
x509 = ["pkcs8", "alloc", "dep:x509-cert"]
7476
pkcs8 = ["ed25519/pkcs8"]
7577
pem = ["alloc", "ed25519/pem", "pkcs8"]
7678
rand_core = ["dep:rand_core"]

ed25519-dalek/src/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,52 @@ pub use crate::verifying::*;
288288
#[cfg(feature = "digest")]
289289
pub use ed25519::signature::{DigestSigner, DigestVerifier};
290290
pub use ed25519::signature::{Signer, Verifier};
291+
292+
#[cfg(not(feature = "x509"))]
291293
pub use ed25519::Signature;
292294

295+
#[cfg(feature = "x509")]
296+
pub use signature_wrapper::Signature;
297+
298+
#[cfg(feature = "x509")]
299+
mod signature_wrapper {
300+
use core::ops::Deref;
301+
use core::ops::DerefMut;
302+
303+
/// Wrapper over ed25519::Signature to enable additional trait implementations required to build x509 certificates
304+
#[derive(Copy, Clone, Eq, PartialEq)]
305+
#[repr(C)]
306+
pub struct Signature(pub ed25519::Signature);
307+
308+
impl Signature {
309+
/// Parse an Ed25519 signature from a byte slice.
310+
pub fn from_bytes(bytes: &ed25519::SignatureBytes) -> Self {
311+
Self(ed25519::Signature::from_bytes(bytes))
312+
}
313+
}
314+
315+
impl TryFrom<&[u8]> for Signature {
316+
type Error = ed25519::Error;
317+
318+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
319+
Ok(Self(ed25519::Signature::try_from(value)?))
320+
}
321+
}
322+
323+
impl Deref for Signature {
324+
type Target = ed25519::Signature;
325+
326+
fn deref(&self) -> &Self::Target {
327+
&self.0
328+
}
329+
}
330+
331+
impl DerefMut for Signature {
332+
fn deref_mut(&mut self) -> &mut Self::Target {
333+
&mut self.0
334+
}
335+
}
336+
}
337+
293338
#[cfg(feature = "pkcs8")]
294339
pub use ed25519::pkcs8;

ed25519-dalek/src/signature.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,19 @@ impl From<InternalSignature> for ed25519::Signature {
175175
ed25519::Signature::from_components(*sig.R.as_bytes(), *sig.s.as_bytes())
176176
}
177177
}
178+
179+
#[cfg(feature = "x509")]
180+
impl From<InternalSignature> for crate::Signature {
181+
fn from(value: InternalSignature) -> Self {
182+
crate::Signature(ed25519::Signature::from(value))
183+
}
184+
}
185+
186+
#[cfg(feature = "x509")]
187+
impl ed25519::pkcs8::spki::SignatureBitStringEncoding for crate::Signature {
188+
fn to_bitstring(&self) -> x509_cert::der::Result<x509_cert::der::asn1::BitString> {
189+
let signature: ed25519::Signature = self.0.into();
190+
191+
x509_cert::der::asn1::BitString::new(0, signature.to_vec())
192+
}
193+
}

ed25519-dalek/src/signing.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,15 @@ impl pkcs8::EncodePrivateKey for SigningKey {
665665
}
666666
}
667667

668+
#[cfg(feature = "pkcs8")]
669+
impl pkcs8::spki::DynSignatureAlgorithmIdentifier for SigningKey {
670+
fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<pkcs8::spki::AlgorithmIdentifierOwned> {
671+
// From https://datatracker.ietf.org/doc/html/rfc8410
672+
// `id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }`
673+
Ok(pkcs8::spki::AlgorithmIdentifier { oid: ed25519::pkcs8::ALGORITHM_OID, parameters: None })
674+
}
675+
}
676+
668677
#[cfg(feature = "pkcs8")]
669678
impl TryFrom<pkcs8::KeypairBytes> for SigningKey {
670679
type Error = pkcs8::Error;

ed25519-dalek/src/verifying.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,15 @@ impl pkcs8::EncodePublicKey for VerifyingKey {
580580
}
581581
}
582582

583+
#[cfg(feature = "pkcs8")]
584+
impl pkcs8::spki::DynSignatureAlgorithmIdentifier for VerifyingKey {
585+
fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<pkcs8::spki::AlgorithmIdentifierOwned> {
586+
// From https://datatracker.ietf.org/doc/html/rfc8410
587+
// `id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }`
588+
Ok(ed25519::pkcs8::spki::AlgorithmIdentifierOwned { oid: ed25519::pkcs8::ALGORITHM_OID, parameters: None })
589+
}
590+
}
591+
583592
#[cfg(feature = "pkcs8")]
584593
impl TryFrom<pkcs8::PublicKeyBytes> for VerifyingKey {
585594
type Error = pkcs8::spki::Error;

ed25519-dalek/tests/pkcs8.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
//! RFC5958 (PKCS#8) and RFC5280 (SPKI).
55
66
#![cfg(feature = "pkcs8")]
7-
87
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
98
use ed25519_dalek::{SigningKey, VerifyingKey};
109
use hex_literal::hex;
1110

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

14+
#[cfg(feature = "x509")]
15+
use x509_cert::builder::Builder;
16+
#[cfg(feature = "x509")]
17+
use x509_cert::der::EncodePem;
18+
#[cfg(feature = "x509")]
19+
use x509_cert::spki::DynSignatureAlgorithmIdentifier;
20+
1521
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
1622
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");
1723

@@ -69,3 +75,46 @@ fn encode_verifying_key() {
6975
let verifying_key2 = VerifyingKey::from_public_key_der(verifying_key_der.as_bytes()).unwrap();
7076
assert_eq!(verifying_key, verifying_key2);
7177
}
78+
79+
#[cfg(feature = "x509")]
80+
#[test]
81+
fn build_valid_x509_cert() {
82+
use std::time::Duration;
83+
use std::str::FromStr;
84+
use x509_cert::{
85+
builder::{CertificateBuilder, Profile},
86+
name::Name,
87+
serial_number::SerialNumber,
88+
spki:: SubjectPublicKeyInfoOwned,
89+
time::Validity,
90+
};
91+
let profile = Profile::Root;
92+
let serial_number = SerialNumber::from(42u32);
93+
let validity = Validity::from_now(Duration::new(360, 0)).unwrap();
94+
let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
95+
let signing = SigningKey::from_bytes(&SK_BYTES);
96+
let verifying_key = VerifyingKey::from_bytes(&PK_BYTES).unwrap();
97+
let public_key = verifying_key.to_public_key_der().unwrap();
98+
let key_info =
99+
SubjectPublicKeyInfoOwned::try_from(&public_key.as_bytes()[..]).unwrap();
100+
101+
let builder = CertificateBuilder::new(
102+
profile,
103+
serial_number,
104+
validity,
105+
subject,
106+
key_info,
107+
&signing,
108+
)
109+
.expect("should create certificate");
110+
111+
let certificate = builder.build().unwrap();
112+
certificate.to_pem(x509_cert::der::pem::LineEnding::LF).expect("should generate pem");
113+
114+
// 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
115+
// The tool is installed via `go install github.com/zmap/zlint/v3/cmd/zlint@latest`.
116+
//
117+
// TODO: Blocked by: https://github.com/zmap/zlint/issues/883
118+
// let ignored = &[];
119+
// x509_cert_test_support::zlint::check_certificate(pem.as_bytes(), ignored);
120+
}

0 commit comments

Comments
 (0)