paseto_v4/core/
pke.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3
4use cipher::StreamCipher;
5use curve25519_dalek::EdwardsPoint;
6use digest::Mac;
7use generic_array::typenum::U32;
8use paseto_core::PasetoError;
9use paseto_core::key::HasKey;
10use paseto_core::paserk::{PkeSealingVersion, PkeUnsealingVersion};
11use paseto_core::version::{PkePublic, PkeSecret, Public, Secret};
12
13use super::{LocalKey, PublicKey, SecretKey, V4};
14
15impl HasKey<PkePublic> for V4 {
16    type Key = PublicKey;
17    fn decode(bytes: &[u8]) -> Result<PublicKey, PasetoError> {
18        <V4 as HasKey<Public>>::decode(bytes)
19    }
20    fn encode(key: &PublicKey) -> Box<[u8]> {
21        <V4 as HasKey<Public>>::encode(key)
22    }
23}
24
25impl HasKey<PkeSecret> for V4 {
26    type Key = SecretKey;
27    fn decode(bytes: &[u8]) -> Result<SecretKey, PasetoError> {
28        <V4 as HasKey<Secret>>::decode(bytes)
29    }
30    fn encode(key: &SecretKey) -> Box<[u8]> {
31        <V4 as HasKey<Secret>>::encode(key)
32    }
33}
34
35impl PkeSealingVersion for V4 {
36    fn seal_key(sealing_key: &PublicKey, key: LocalKey) -> Result<Box<[u8]>, PasetoError> {
37        use cipher::KeyIvInit;
38        use curve25519_dalek::edwards::CompressedEdwardsY;
39        use curve25519_dalek::scalar::{Scalar, clamp_integer};
40        use digest::Digest;
41
42        // Given a plaintext data key (pdk), and an Ed25519 public key (pk).
43        let pk = CompressedEdwardsY(*sealing_key.0.as_bytes());
44
45        // step 1: Calculate the birationally-equivalent X25519 public key (xpk) from pk.
46        let xpk = pk.decompress().unwrap().to_montgomery();
47
48        let esk = Scalar::from_bytes_mod_order(clamp_integer({
49            let mut esk = [0; 32];
50            getrandom::fill(&mut esk).map_err(|_| PasetoError::CryptoError)?;
51            esk
52        }));
53        let epk = EdwardsPoint::mul_base(&esk).to_montgomery();
54
55        // diffie hellman exchange
56        let xk = esk * xpk;
57
58        let mut ek = blake2::Blake2b::new();
59        ek.update(b"\x01k4.seal.");
60        ek.update(xk.as_bytes());
61        ek.update(epk.as_bytes());
62        ek.update(xpk.as_bytes());
63        let ek = ek.finalize();
64
65        let mut n = blake2::Blake2b::new();
66        n.update(epk.as_bytes());
67        n.update(xpk.as_bytes());
68        let n = n.finalize();
69
70        let mut edk = key.0;
71        chacha20::XChaCha20::new(&ek, &n).apply_keystream(&mut edk);
72
73        let mut ak = blake2::Blake2b::<U32>::new();
74        ak.update(b"\x02k4.seal.");
75        ak.update(xk.as_bytes());
76        ak.update(epk.as_bytes());
77        ak.update(xpk.as_bytes());
78        let ak = ak.finalize();
79
80        let mut tag = blake2::Blake2bMac::<U32>::new_from_slice(&ak).unwrap();
81        tag.update(b"k4.seal.");
82        tag.update(epk.as_bytes());
83        tag.update(&edk);
84        let tag = tag.finalize().into_bytes();
85
86        let mut output = Vec::with_capacity(96);
87        output.extend_from_slice(&tag);
88        output.extend_from_slice(epk.as_bytes());
89        output.extend_from_slice(&edk);
90
91        Ok(output.into_boxed_slice())
92    }
93}
94
95impl PkeUnsealingVersion for V4 {
96    fn unseal_key(
97        unsealing_key: &SecretKey,
98        mut key_data: Box<[u8]>,
99    ) -> Result<LocalKey, PasetoError> {
100        use cipher::KeyIvInit;
101        use digest::Digest;
102
103        let (tag, key_data) = key_data
104            .split_first_chunk_mut::<32>()
105            .ok_or(PasetoError::InvalidKey)?;
106        let (epk, edk) = key_data
107            .split_first_chunk_mut::<32>()
108            .ok_or(PasetoError::InvalidKey)?;
109        let edk: &mut [u8; 32] = edk.try_into().map_err(|_| PasetoError::InvalidKey)?;
110
111        let epk = curve25519_dalek::MontgomeryPoint(*epk);
112
113        // expand pk/sk pair from ed25519 to x25519
114        let xpk = EdwardsPoint::mul_base(&unsealing_key.1.scalar).to_montgomery();
115
116        // diffie hellman exchange
117        let xk = unsealing_key.1.scalar * epk;
118
119        let mut ak = blake2::Blake2b::<U32>::new();
120        ak.update(b"\x02k4.seal.");
121        ak.update(xk.as_bytes());
122        ak.update(epk.as_bytes());
123        ak.update(xpk.as_bytes());
124        let ak = ak.finalize();
125
126        let mut t2 = blake2::Blake2bMac::<U32>::new_from_slice(&ak).unwrap();
127        t2.update(b"k4.seal.");
128        t2.update(epk.as_bytes());
129        t2.update(edk);
130
131        // step 6: Compare t2 with t, using a constant-time compare function. If it does not match, abort.
132        t2.verify((&*tag).into())
133            .map_err(|_| PasetoError::CryptoError)?;
134
135        let mut ek = blake2::Blake2b::new();
136        ek.update(b"\x01k4.seal.");
137        ek.update(xk.as_bytes());
138        ek.update(epk.as_bytes());
139        ek.update(xpk.as_bytes());
140        let ek = ek.finalize();
141
142        let mut n = blake2::Blake2b::new();
143        n.update(epk.as_bytes());
144        n.update(xpk.as_bytes());
145        let n = n.finalize();
146
147        chacha20::XChaCha20::new(&ek, &n).apply_keystream(edk);
148
149        Ok(LocalKey(*edk))
150    }
151}