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
4 changes: 2 additions & 2 deletions ed25519/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ be accompanied by a minor version bump.

- All on-by-default features of this library are covered by SemVer
- MSRV is considered exempt from SemVer as noted above
- The `pkcs8` crate is exempted as it's a pre-1.0 dependency, however, upgrades
to this crate will be accompanied by a minor version bump.
- The `pkcs8` module is exempted as it uses a pre-1.0 dependency, however,
breaking changes to this module will be accompanied by a minor version bump.

## License

Expand Down
7 changes: 6 additions & 1 deletion ed25519/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,12 @@
html_root_url = "https://docs.rs/ed25519/1.4.0"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
#![warn(
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]

#[cfg(feature = "alloc")]
extern crate alloc;
Expand Down
240 changes: 209 additions & 31 deletions ed25519/src/pkcs8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,32 @@
//!
//! Implements Ed25519 PKCS#8 private keys as described in RFC8410 Section 7:
//! <https://datatracker.ietf.org/doc/html/rfc8410#section-7>
//!
//! ## SemVer Notes
//!
//! The `pkcs8` module of this crate is exempted from SemVer as it uses a
//! pre-1.0 dependency (the `pkcs8` crate).
//!
//! However, breaking changes to this module will be accompanied by a minor
//! version bump.
//!
//! Please lock to a specific minor version of the `ed25519` crate to avoid
//! breaking changes when using this module.

pub use pkcs8::DecodePrivateKey;
pub use pkcs8::{DecodePrivateKey, DecodePublicKey};

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

use core::fmt;
use pkcs8::ObjectIdentifier;

#[cfg(feature = "pem")]
use {
alloc::string::{String, ToString},
core::str,
};

#[cfg(feature = "zeroize")]
use zeroize::Zeroize;

Expand All @@ -20,13 +37,28 @@ use zeroize::Zeroize;
/// <http://oid-info.com/get/1.3.101.112>
pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new("1.3.101.112");

/// Ed25519 Algorithm Identifier.
pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifier<'static> = pkcs8::AlgorithmIdentifier {
oid: ALGORITHM_OID,
parameters: None,
};

/// Ed25519 keypair serialized as bytes.
///
/// This type is primarily useful for decoding/encoding PKCS#8 private key
/// files (either DER or PEM) encoded using the following traits:
///
/// - [`DecodePrivateKey`]: decode DER or PEM encoded PKCS#8 private key.
/// - [`EncodePrivateKey`]: encode DER or PEM encoded PKCS#8 private key.
///
/// PKCS#8 private key files encoded with PEM begin with:
///
/// ```text
/// -----BEGIN PRIVATE KEY-----
/// ```
///
/// Note that this type operates on raw bytes and performs no validation that
/// keys represent valid Ed25519 field elements.
pub struct KeypairBytes {
/// Ed25519 secret key.
///
Expand All @@ -45,6 +77,15 @@ impl KeypairBytes {
/// Size of an Ed25519 keypair when serialized as bytes.
const BYTE_SIZE: usize = 64;

/// Parse raw keypair from a 64-byte input.
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
let (sk, pk) = bytes.split_at(Self::BYTE_SIZE / 2);
Self {
secret_key: sk.try_into().expect("secret key size error"),
public_key: Some(pk.try_into().expect("public key size error")),
}
}

/// Serialize as a 64-byte keypair.
///
/// # Returns
Expand All @@ -64,6 +105,39 @@ impl KeypairBytes {
}
}

impl DecodePrivateKey for KeypairBytes {}

impl Drop for KeypairBytes {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.secret_key.zeroize()
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl EncodePrivateKey for KeypairBytes {
fn to_pkcs8_der(&self) -> pkcs8::Result<pkcs8::PrivateKeyDocument> {
// Serialize private key as nested OCTET STRING
let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
private_key[0] = 0x04;
private_key[1] = 0x20;
private_key[2..].copy_from_slice(&self.secret_key);

let result = pkcs8::PrivateKeyInfo {
algorithm: ALGORITHM_ID,
private_key: &private_key,
public_key: self.public_key.as_ref().map(AsRef::as_ref),
}
.to_der();

#[cfg(feature = "zeroize")]
private_key.zeroize();

result
}
}

impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for KeypairBytes {
type Error = pkcs8::Error;

Expand Down Expand Up @@ -100,53 +174,157 @@ impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for KeypairBytes {
}
}

impl DecodePrivateKey for KeypairBytes {}
impl TryFrom<&[u8]> for KeypairBytes {
type Error = pkcs8::Error;

fn try_from(der_bytes: &[u8]) -> pkcs8::Result<Self> {
Self::from_pkcs8_der(der_bytes)
}
}

impl fmt::Debug for KeypairBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeypairBytes")
.field("public_key", &self.public_key)
.finish_non_exhaustive()
}
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl str::FromStr for KeypairBytes {
type Err = pkcs8::Error;

fn from_str(pem: &str) -> pkcs8::Result<Self> {
Self::from_pkcs8_pem(pem)
}
}

/// Ed25519 public key serialized as bytes.
///
/// This type is primarily useful for decoding/encoding SPKI public key
/// files (either DER or PEM) encoded using the following traits:
///
/// - [`DecodePublicKey`]: decode DER or PEM encoded PKCS#8 private key.
/// - [`EncodePublicKey`]: encode DER or PEM encoded PKCS#8 private key.
///
/// SPKI public key files encoded with PEM begin with:
///
/// ```text
/// -----BEGIN PUBLIC KEY-----
/// ```
///
/// Note that this type operates on raw bytes and performs no validation that
/// public keys represent valid compressed Ed25519 y-coordinates.
pub struct PublicKeyBytes(pub [u8; Self::BYTE_SIZE]);

impl PublicKeyBytes {
/// Size of an Ed25519 public key when serialized as bytes.
const BYTE_SIZE: usize = 32;

/// Returns the raw bytes of the public key.
pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
self.0
}
}

impl AsRef<[u8; Self::BYTE_SIZE]> for PublicKeyBytes {
fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
&self.0
}
}

impl DecodePublicKey for PublicKeyBytes {}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl EncodePrivateKey for KeypairBytes {
fn to_pkcs8_der(&self) -> pkcs8::Result<pkcs8::PrivateKeyDocument> {
let algorithm = pkcs8::AlgorithmIdentifier {
oid: ALGORITHM_OID,
parameters: None,
};
impl EncodePublicKey for PublicKeyBytes {
fn to_public_key_der(&self) -> pkcs8::spki::Result<pkcs8::PublicKeyDocument> {
pkcs8::SubjectPublicKeyInfo {
algorithm: ALGORITHM_ID,
subject_public_key: &self.0,
}
.try_into()
}
}

// Serialize private key as nested OCTET STRING
let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
private_key[0] = 0x04;
private_key[1] = 0x20;
private_key[2..].copy_from_slice(&self.secret_key);
impl TryFrom<pkcs8::spki::SubjectPublicKeyInfo<'_>> for PublicKeyBytes {
type Error = pkcs8::spki::Error;

let result = pkcs8::PrivateKeyInfo {
algorithm,
private_key: &private_key,
public_key: self.public_key.as_ref().map(AsRef::as_ref),
fn try_from(spki: pkcs8::spki::SubjectPublicKeyInfo<'_>) -> pkcs8::spki::Result<Self> {
spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;

if spki.algorithm.parameters.is_some() {
return Err(pkcs8::spki::Error::KeyMalformed);
}
.to_der();

#[cfg(feature = "zeroize")]
private_key.zeroize();
spki.subject_public_key
.try_into()
.map(Self)
.map_err(|_| pkcs8::spki::Error::KeyMalformed)
}
}

result
impl TryFrom<&[u8]> for PublicKeyBytes {
type Error = pkcs8::spki::Error;

fn try_from(der_bytes: &[u8]) -> pkcs8::spki::Result<Self> {
Self::from_public_key_der(der_bytes)
}
}

impl<'a> fmt::Debug for KeypairBytes {
impl TryFrom<KeypairBytes> for PublicKeyBytes {
type Error = pkcs8::spki::Error;

fn try_from(keypair: KeypairBytes) -> pkcs8::spki::Result<PublicKeyBytes> {
PublicKeyBytes::try_from(&keypair)
}
}

impl TryFrom<&KeypairBytes> for PublicKeyBytes {
type Error = pkcs8::spki::Error;

fn try_from(keypair: &KeypairBytes) -> pkcs8::spki::Result<PublicKeyBytes> {
keypair
.public_key
.map(PublicKeyBytes)
.ok_or(pkcs8::spki::Error::KeyMalformed)
}
}

impl fmt::Debug for PublicKeyBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeypairBytes")
.field("public_key", &self.public_key)
.finish() // TODO: use `finish_non_exhaustive` when MSRV 1.53
f.write_str("PublicKeyBytes(")?;

for &byte in self.as_ref() {
write!(f, "{:02X}", byte)?;
}

f.write_str(")")
}
}

#[cfg(feature = "zeroize")]
#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
impl Drop for KeypairBytes {
fn drop(&mut self) {
self.secret_key.zeroize()
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl str::FromStr for PublicKeyBytes {
type Err = pkcs8::spki::Error;

fn from_str(pem: &str) -> pkcs8::spki::Result<Self> {
Self::from_public_key_pem(pem)
}
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl ToString for PublicKeyBytes {
fn to_string(&self) -> String {
self.to_public_key_pem(Default::default())
.expect("PEM serialization error")
}
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[cfg(test)]
mod tests {
use super::KeypairBytes;
Expand Down
Binary file added ed25519/tests/examples/pubkey.der
Binary file not shown.
3 changes: 3 additions & 0 deletions ed25519/tests/examples/pubkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PUBLIC KEY-----
Loading