1use std::error::Error;
2use std::fs;
3
4use hkdf::Hkdf;
5use p256::pkcs8::{DecodePrivateKey, DecodePublicKey};
6use pkcs8::der::asn1::OctetStringRef;
7use pkcs8::der::{Document, SecretDocument};
8use pkcs8::spki::{ObjectIdentifier, SubjectPublicKeyInfoRef};
9use pkcs8::PrivateKeyInfoRef;
10use sha2::Sha256;
11
12const X25519_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.110");
13
14/// RFC 8410: the PKCS#8 privateKey field wraps the 32 raw bytes in one more OCTET STRING.
15fn x25519_private_key(pem: &str) -> Result<x25519_dalek::StaticSecret, Box<dyn Error>> {
16 let (_, document) = SecretDocument::from_pem(pem)?;
17 let info: PrivateKeyInfoRef = document.decode_msg()?;
18 if info.algorithm.oid != X25519_OID {
19 return Err("not an X25519 private key".into());
20 }
21 let inner: &OctetStringRef = info.private_key.decode_into()?;
22 let raw: [u8; 32] = inner.as_bytes().try_into()?;
23 Ok(x25519_dalek::StaticSecret::from(raw))
24}
25
26fn x25519_public_key(pem: &str) -> Result<x25519_dalek::PublicKey, Box<dyn Error>> {
27 let (_, document) = Document::from_pem(pem)?;
28 let spki: SubjectPublicKeyInfoRef = document.decode_msg()?;
29 if spki.algorithm.oid != X25519_OID {
30 return Err("not an X25519 public key".into());
31 }
32 let raw: [u8; 32] = spki.subject_public_key.as_bytes().ok_or("bad BIT STRING")?.try_into()?;
33 Ok(x25519_dalek::PublicKey::from(raw))
34}
35
36/// HKDF-SHA256, empty salt, 32-byte output.
37fn derive_key(shared_secret: &[u8]) -> Result<[u8; 32], hkdf::InvalidLength> {
38 let mut key = [0u8; 32];
39 Hkdf::<Sha256>::new(None, shared_secret).expand(b"AES-256-GCM key v1", &mut key)?;
40 Ok(key)
41}
42
43fn main() -> Result<(), Box<dyn Error>> {
44 let alice = x25519_private_key(&fs::read_to_string("alice-x25519.pem")?)?;
45 let bob = x25519_public_key(&fs::read_to_string("bob-x25519.pub.pem")?)?;
46 let shared = alice.diffie_hellman(&bob);
47 if !shared.was_contributory() {
48 return Err("X25519 produced the all-zero secret: reject this peer key".into());
49 }
50 println!("X25519 shared secret: {}", hex::encode(shared.as_bytes()));
51 println!("X25519 derived key: {}", hex::encode(derive_key(shared.as_bytes())?));
52
53 let alice = p256::SecretKey::from_pkcs8_pem(&fs::read_to_string("alice-p256.pem")?)?;
54 let bob = p256::PublicKey::from_public_key_pem(&fs::read_to_string("bob-p256.pub.pem")?)?;
55 let shared = p256::ecdh::diffie_hellman(alice.to_nonzero_scalar(), bob.as_affine());
56 println!("P-256 shared secret: {}", hex::encode(shared.raw_secret_bytes()));
57 println!("P-256 derived key: {}", hex::encode(derive_key(shared.raw_secret_bytes())?));
58 Ok(())
59}