mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
ed25519: add pkcs8::PublicKeyBytes type (#455)
Adds a type for representing serialized Ed25519 public keys. The type implements the serialization traits from the `spki` crate, which is used for representing the public keys associated with a PKCS#8-encoded private key.
This commit is contained in:
+2
-2
@@ -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
|
||||
|
||||
|
||||
+6
-1
@@ -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;
|
||||
|
||||
+214
-36
@@ -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;
|
||||
|
||||
@@ -20,6 +37,12 @@ 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
|
||||
@@ -27,6 +50,15 @@ pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new("1.3.101.112")
|
||||
///
|
||||
/// - [`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.
|
||||
///
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -100,53 +174,157 @@ impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for KeypairBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodePrivateKey for KeypairBytes {}
|
||||
impl TryFrom<&[u8]> for KeypairBytes {
|
||||
type Error = pkcs8::Error;
|
||||
|
||||
#[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,
|
||||
};
|
||||
|
||||
// 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,
|
||||
private_key: &private_key,
|
||||
public_key: self.public_key.as_ref().map(AsRef::as_ref),
|
||||
}
|
||||
.to_der();
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
private_key.zeroize();
|
||||
|
||||
result
|
||||
fn try_from(der_bytes: &[u8]) -> pkcs8::Result<Self> {
|
||||
Self::from_pkcs8_der(der_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for KeypairBytes {
|
||||
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() // TODO: use `finish_non_exhaustive` when MSRV 1.53
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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 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()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<pkcs8::spki::SubjectPublicKeyInfo<'_>> for PublicKeyBytes {
|
||||
type Error = pkcs8::spki::Error;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
spki.subject_public_key
|
||||
.try_into()
|
||||
.map(Self)
|
||||
.map_err(|_| pkcs8::spki::Error::KeyMalformed)
|
||||
}
|
||||
}
|
||||
|
||||
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 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.write_str("PublicKeyBytes(")?;
|
||||
|
||||
for &byte in self.as_ref() {
|
||||
write!(f, "{:02X}", byte)?;
|
||||
}
|
||||
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
|
||||
-----END PUBLIC KEY-----
|
||||
+33
-10
@@ -2,49 +2,64 @@
|
||||
|
||||
#![cfg(feature = "pkcs8")]
|
||||
|
||||
use ed25519::pkcs8::{DecodePrivateKey, KeypairBytes};
|
||||
use ed25519::pkcs8::{DecodePrivateKey, DecodePublicKey, KeypairBytes, PublicKeyBytes};
|
||||
use hex_literal::hex;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use ed25519::pkcs8::EncodePrivateKey;
|
||||
use ed25519::pkcs8::{EncodePrivateKey, EncodePublicKey};
|
||||
|
||||
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER
|
||||
/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER.
|
||||
const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der");
|
||||
|
||||
/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER
|
||||
/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER.
|
||||
const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der");
|
||||
|
||||
/// Ed25519 SubjectPublicKeyInfo encoded as ASN.1 DER.
|
||||
const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der");
|
||||
|
||||
#[test]
|
||||
fn decode_pkcs8_v1() {
|
||||
let pk = KeypairBytes::from_pkcs8_der(PKCS8_V1_DER).unwrap();
|
||||
let keypair = KeypairBytes::from_pkcs8_der(PKCS8_V1_DER).unwrap();
|
||||
|
||||
// Extracted with:
|
||||
// $ openssl asn1parse -inform der -in tests/examples/p256-priv.der
|
||||
assert_eq!(
|
||||
pk.secret_key,
|
||||
keypair.secret_key,
|
||||
&hex!("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842")[..]
|
||||
);
|
||||
|
||||
assert_eq!(pk.public_key, None);
|
||||
assert_eq!(keypair.public_key, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pkcs8_v2() {
|
||||
let pk = KeypairBytes::from_pkcs8_der(PKCS8_V2_DER).unwrap();
|
||||
let keypair = KeypairBytes::from_pkcs8_der(PKCS8_V2_DER).unwrap();
|
||||
|
||||
// Extracted with:
|
||||
// $ openssl asn1parse -inform der -in tests/examples/p256-priv.der
|
||||
assert_eq!(
|
||||
pk.secret_key,
|
||||
keypair.secret_key,
|
||||
&hex!("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842")[..]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pk.public_key.unwrap(),
|
||||
keypair.public_key.unwrap(),
|
||||
hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_public_key() {
|
||||
let public_key = PublicKeyBytes::from_public_key_der(PUBLIC_KEY_DER).unwrap();
|
||||
|
||||
// Extracted with:
|
||||
// $ openssl pkey -inform der -in pkcs8-v1.der -pubout -text
|
||||
assert_eq!(
|
||||
public_key.as_ref(),
|
||||
&hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1")
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn encode_pkcs8_v1() {
|
||||
@@ -61,3 +76,11 @@ fn encode_pkcs8_v2() {
|
||||
assert_eq!(pk.secret_key, pk2.secret_key);
|
||||
assert_eq!(pk.public_key, pk2.public_key);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn encode_public_key() {
|
||||
let pk = PublicKeyBytes::from_public_key_der(PUBLIC_KEY_DER).unwrap();
|
||||
let pk_der = pk.to_public_key_der().unwrap();
|
||||
assert_eq!(pk_der.as_ref(), PUBLIC_KEY_DER);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user