pub use pkcs8::{
DecodePrivateKey, DecodePublicKey, Error, KeyError, ObjectIdentifier, PrivateKeyInfoRef,
Result, spki,
};
#[cfg(feature = "alloc")]
pub use pkcs8::{EncodePrivateKey, spki::EncodePublicKey};
#[cfg(feature = "alloc")]
pub use pkcs8::der::{
Document, SecretDocument,
asn1::{BitStringRef, OctetStringRef},
};
use core::fmt;
#[cfg(feature = "pem")]
use core::str;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[cfg(feature = "zerocopy")]
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifierRef<'static> = pkcs8::AlgorithmIdentifierRef {
oid: ALGORITHM_OID,
parameters: None,
};
pub struct KeypairBytes {
pub secret_key: [u8; Self::BYTE_SIZE / 2],
pub public_key: Option<PublicKeyBytes>,
}
impl KeypairBytes {
const BYTE_SIZE: usize = 64;
#[must_use]
#[allow(clippy::missing_panics_doc, reason = "MSRV TODO")]
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(PublicKeyBytes(
pk.try_into().expect("public key size error"),
)),
}
}
#[must_use]
pub fn to_bytes(&self) -> Option<[u8; Self::BYTE_SIZE]> {
if let Some(public_key) = &self.public_key {
let mut result = [0u8; Self::BYTE_SIZE];
let (sk, pk) = result.split_at_mut(Self::BYTE_SIZE / 2);
sk.copy_from_slice(&self.secret_key);
pk.copy_from_slice(public_key.as_ref());
Some(result)
} else {
None
}
}
}
impl Drop for KeypairBytes {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.secret_key.zeroize();
}
}
#[cfg(feature = "alloc")]
impl EncodePrivateKey for KeypairBytes {
fn to_pkcs8_der(&self) -> Result<SecretDocument> {
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 private_key_info = PrivateKeyInfoRef {
algorithm: ALGORITHM_ID,
private_key: OctetStringRef::new(&private_key)?,
public_key: self
.public_key
.as_ref()
.map(|pk| BitStringRef::new(0, &pk.0))
.transpose()?,
};
let result = SecretDocument::encode_msg(&private_key_info)?;
#[cfg(feature = "zeroize")]
private_key.zeroize();
Ok(result)
}
}
impl TryFrom<PrivateKeyInfoRef<'_>> for KeypairBytes {
type Error = Error;
fn try_from(private_key: PrivateKeyInfoRef<'_>) -> Result<Self> {
private_key.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
if private_key.algorithm.parameters.is_some() {
return Err(Error::ParametersMalformed);
}
let secret_key = match private_key.private_key.as_bytes() {
[0x04, 0x20, rest @ ..] => rest.try_into().map_err(|_| KeyError::Invalid),
_ => Err(KeyError::Invalid),
}?;
let public_key = private_key
.public_key
.and_then(|bs| bs.as_bytes())
.map(|bytes| bytes.try_into().map_err(|_| KeyError::Invalid))
.transpose()?
.map(PublicKeyBytes);
Ok(Self {
secret_key,
public_key,
})
}
}
impl TryFrom<&[u8]> for KeypairBytes {
type Error = Error;
fn try_from(der_bytes: &[u8]) -> 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")]
impl str::FromStr for KeypairBytes {
type Err = Error;
fn from_str(pem: &str) -> Result<Self> {
Self::from_pkcs8_pem(pem)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[cfg_attr(
feature = "zerocopy",
derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable,)
)]
#[repr(transparent)]
pub struct PublicKeyBytes(pub [u8; Self::BYTE_SIZE]);
impl PublicKeyBytes {
const BYTE_SIZE: usize = 32;
#[must_use]
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
}
}
#[cfg(feature = "alloc")]
impl EncodePublicKey for PublicKeyBytes {
fn to_public_key_der(&self) -> spki::Result<Document> {
pkcs8::SubjectPublicKeyInfoRef {
algorithm: ALGORITHM_ID,
subject_public_key: BitStringRef::new(0, &self.0)?,
}
.try_into()
}
}
impl TryFrom<spki::SubjectPublicKeyInfoRef<'_>> for PublicKeyBytes {
type Error = spki::Error;
fn try_from(spki: spki::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
if spki.algorithm.parameters.is_some() {
return Err(spki::Error::KeyMalformed);
}
spki.subject_public_key
.as_bytes()
.ok_or(spki::Error::KeyMalformed)?
.try_into()
.map(Self)
.map_err(|_| spki::Error::KeyMalformed)
}
}
impl TryFrom<&[u8]> for PublicKeyBytes {
type Error = spki::Error;
fn try_from(der_bytes: &[u8]) -> spki::Result<Self> {
Self::from_public_key_der(der_bytes)
}
}
impl TryFrom<KeypairBytes> for PublicKeyBytes {
type Error = spki::Error;
fn try_from(keypair: KeypairBytes) -> spki::Result<PublicKeyBytes> {
PublicKeyBytes::try_from(&keypair)
}
}
impl TryFrom<&KeypairBytes> for PublicKeyBytes {
type Error = spki::Error;
fn try_from(keypair: &KeypairBytes) -> spki::Result<PublicKeyBytes> {
keypair.public_key.ok_or(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, "{byte:02X}")?;
}
f.write_str(")")
}
}
#[cfg(feature = "pem")]
impl str::FromStr for PublicKeyBytes {
type Err = spki::Error;
fn from_str(pem: &str) -> spki::Result<Self> {
Self::from_public_key_pem(pem)
}
}
#[cfg(feature = "pem")]
impl fmt::Display for PublicKeyBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
&self
.to_public_key_pem(Default::default())
.expect("PEM serialization error"),
)
}
}
#[cfg(feature = "pem")]
#[cfg(test)]
mod tests {
use super::{KeypairBytes, PublicKeyBytes};
use hex_literal::hex;
const SECRET_KEY_BYTES: [u8; 32] =
hex!("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842");
const PUBLIC_KEY_BYTES: [u8; 32] =
hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1");
#[test]
fn to_bytes() {
let valid_keypair = KeypairBytes {
secret_key: SECRET_KEY_BYTES,
public_key: Some(PublicKeyBytes(PUBLIC_KEY_BYTES)),
};
assert_eq!(
valid_keypair.to_bytes().expect("to_bytes"),
hex!(
"D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F4475584219BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"
)
);
let invalid_keypair = KeypairBytes {
secret_key: SECRET_KEY_BYTES,
public_key: None,
};
assert_eq!(invalid_keypair.to_bytes(), None);
}
}