use crate::provider::{CryptoError, SecureSeed};
const PKCS8_V2_EXPLICIT_LEN: usize = 85;
const PKCS8_V2_IMPLICIT_LEN: usize = 83;
const PKCS8_V1_LEN: usize = 48;
const PKCS8_V1_UNWRAPPED_LEN: usize = 46;
const SEED_OFFSET: usize = 16;
const SEED_OFFSET_UNWRAPPED: usize = 14;
const PUBKEY_OFFSET_EXPLICIT: usize = 53;
const PUBKEY_OFFSET_IMPLICIT: usize = 51;
pub fn parse_ed25519_seed(bytes: &[u8]) -> Result<SecureSeed, CryptoError> {
match bytes.len() {
PKCS8_V2_EXPLICIT_LEN | PKCS8_V2_IMPLICIT_LEN | PKCS8_V1_LEN => {
extract_seed_at(bytes, SEED_OFFSET)
}
PKCS8_V1_UNWRAPPED_LEN => extract_seed_at(bytes, SEED_OFFSET_UNWRAPPED),
32 => {
let mut buf = [0u8; 32];
buf.copy_from_slice(bytes);
Ok(SecureSeed::new(buf))
}
34 if bytes[0] == 0x04 && bytes[1] == 0x20 => {
let mut buf = [0u8; 32];
buf.copy_from_slice(&bytes[2..]);
Ok(SecureSeed::new(buf))
}
_ => Err(CryptoError::InvalidPrivateKey(format!(
"Unrecognized Ed25519 key format ({} bytes)",
bytes.len()
))),
}
}
pub fn parse_ed25519_key_material(
bytes: &[u8],
) -> Result<(SecureSeed, Option<[u8; 32]>), CryptoError> {
let seed = parse_ed25519_seed(bytes)?;
let pubkey = match bytes.len() {
PKCS8_V2_EXPLICIT_LEN => {
let mut pk = [0u8; 32];
pk.copy_from_slice(&bytes[PUBKEY_OFFSET_EXPLICIT..PUBKEY_OFFSET_EXPLICIT + 32]);
Some(pk)
}
PKCS8_V2_IMPLICIT_LEN => {
let mut pk = [0u8; 32];
pk.copy_from_slice(&bytes[PUBKEY_OFFSET_IMPLICIT..PUBKEY_OFFSET_IMPLICIT + 32]);
Some(pk)
}
_ => None,
};
Ok((seed, pubkey))
}
pub fn build_ed25519_pkcs8_v2(seed: &[u8; 32], pubkey: &[u8; 32]) -> Vec<u8> {
let mut buf = Vec::with_capacity(PKCS8_V2_EXPLICIT_LEN);
buf.extend_from_slice(&[0x30, 0x53]);
buf.extend_from_slice(&[0x02, 0x01, 0x01]);
buf.extend_from_slice(&[0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70]);
buf.extend_from_slice(&[0x04, 0x22, 0x04, 0x20]);
buf.extend_from_slice(seed);
buf.extend_from_slice(&[0xa1, 0x23, 0x03, 0x21, 0x00]);
buf.extend_from_slice(pubkey);
buf
}
fn extract_seed_at(bytes: &[u8], offset: usize) -> Result<SecureSeed, CryptoError> {
if bytes.len() < offset + 32 {
return Err(CryptoError::InvalidPrivateKey(
"Key bytes too short for seed extraction".to_string(),
));
}
let mut buf = [0u8; 32];
buf.copy_from_slice(&bytes[offset..offset + 32]);
Ok(SecureSeed::new(buf))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_raw_32_byte_seed() {
let seed_bytes = [42u8; 32];
let seed = parse_ed25519_seed(&seed_bytes).unwrap();
assert_eq!(seed.as_bytes(), &seed_bytes);
}
#[test]
fn test_octet_wrapped_34_bytes() {
let mut wrapped = vec![0x04, 0x20];
wrapped.extend_from_slice(&[7u8; 32]);
let seed = parse_ed25519_seed(&wrapped).unwrap();
assert_eq!(seed.as_bytes(), &[7u8; 32]);
}
#[test]
fn test_pkcs8_v2_roundtrip() {
let seed_bytes = [1u8; 32];
let pubkey_bytes = [2u8; 32];
let pkcs8 = build_ed25519_pkcs8_v2(&seed_bytes, &pubkey_bytes);
assert_eq!(pkcs8.len(), 85);
let (seed, maybe_pk) = parse_ed25519_key_material(&pkcs8).unwrap();
assert_eq!(seed.as_bytes(), &seed_bytes);
assert_eq!(maybe_pk.unwrap(), pubkey_bytes);
}
#[test]
fn test_pkcs8_v2_seed_extraction() {
let seed_bytes = [3u8; 32];
let pubkey_bytes = [4u8; 32];
let pkcs8 = build_ed25519_pkcs8_v2(&seed_bytes, &pubkey_bytes);
let seed = parse_ed25519_seed(&pkcs8).unwrap();
assert_eq!(seed.as_bytes(), &seed_bytes);
}
#[test]
fn test_invalid_length_rejected() {
let bad = vec![0u8; 50];
assert!(parse_ed25519_seed(&bad).is_err());
}
#[test]
fn test_ring_83_byte_pkcs8_v2() {
let seed_bytes = [6u8; 32];
let pubkey_bytes = [7u8; 32];
let mut buf = Vec::with_capacity(83);
buf.extend_from_slice(&[0x30, 0x51]);
buf.extend_from_slice(&[0x02, 0x01, 0x01]);
buf.extend_from_slice(&[0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70]);
buf.extend_from_slice(&[0x04, 0x22, 0x04, 0x20]);
buf.extend_from_slice(&seed_bytes);
buf.extend_from_slice(&[0x81, 0x21, 0x00]);
buf.extend_from_slice(&pubkey_bytes);
assert_eq!(buf.len(), 83);
let seed = parse_ed25519_seed(&buf).unwrap();
assert_eq!(seed.as_bytes(), &seed_bytes);
let (seed2, maybe_pk) = parse_ed25519_key_material(&buf).unwrap();
assert_eq!(seed2.as_bytes(), &seed_bytes);
assert_eq!(maybe_pk.unwrap(), pubkey_bytes);
}
#[test]
fn test_non_pkcs8_v2_returns_none_pubkey() {
let seed_bytes = [5u8; 32];
let (seed, maybe_pk) = parse_ed25519_key_material(&seed_bytes).unwrap();
assert_eq!(seed.as_bytes(), &seed_bytes);
assert!(maybe_pk.is_none());
}
}