askar_crypto/kdf/
ecdh_1pu.rs

1//! ECDH-1PU key derivation
2
3use sha2::Sha256;
4use zeroize::Zeroize;
5
6use super::{
7    concat::{ConcatKDFHash, ConcatKDFParams},
8    KeyDerivation, KeyExchange,
9};
10use crate::{
11    buffer::{WriteBuffer, Writer},
12    error::Error,
13};
14
15/// An instantiation of the ECDH-1PU key derivation
16#[derive(Debug)]
17pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> {
18    ephem_key: &'d Key,
19    send_key: &'d Key,
20    recip_key: &'d Key,
21    alg: &'d [u8],
22    apu: &'d [u8],
23    apv: &'d [u8],
24    cc_tag: &'d [u8],
25    receive: bool,
26}
27
28impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> {
29    /// Create a new KDF instance
30    #[allow(clippy::too_many_arguments)]
31    pub fn new(
32        ephem_key: &'d Key,
33        send_key: &'d Key,
34        recip_key: &'d Key,
35        alg: &'d [u8],
36        apu: &'d [u8],
37        apv: &'d [u8],
38        cc_tag: &'d [u8],
39        receive: bool,
40    ) -> Self {
41        Self {
42            ephem_key,
43            send_key,
44            recip_key,
45            alg,
46            apu,
47            apv,
48            cc_tag,
49            receive,
50        }
51    }
52}
53
54impl<Key: KeyExchange + ?Sized> KeyDerivation for Ecdh1PU<'_, Key> {
55    fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> {
56        let output_len = key_output.len();
57        // one-pass KDF only produces 256 bits of output
58        if output_len > 32 {
59            return Err(err_msg!(Unsupported, "Exceeded maximum output length"));
60        }
61        if self.cc_tag.len() > 128 {
62            return Err(err_msg!(Unsupported, "Exceeded maximum length for cc_tag"));
63        }
64        let mut kdf = ConcatKDFHash::<Sha256>::new();
65        kdf.start_pass();
66
67        // hash Zs and Ze directly into the KDF
68        if self.receive {
69            self.recip_key
70                .write_key_exchange(self.ephem_key, &mut kdf)?;
71            self.recip_key.write_key_exchange(self.send_key, &mut kdf)?;
72        } else {
73            self.ephem_key
74                .write_key_exchange(self.recip_key, &mut kdf)?;
75            self.send_key.write_key_exchange(self.recip_key, &mut kdf)?;
76        }
77
78        // the authentication tag is appended to pub_info, if any.
79        let mut pub_info = [0u8; 132];
80        let mut pub_w = Writer::from_slice(&mut pub_info[..]);
81        pub_w.buffer_write(&((output_len as u32) * 8).to_be_bytes())?; // output length in bits
82        if !self.cc_tag.is_empty() {
83            pub_w.buffer_write(&(self.cc_tag.len() as u32).to_be_bytes())?;
84            pub_w.buffer_write(self.cc_tag)?;
85        }
86
87        kdf.hash_params(ConcatKDFParams {
88            alg: self.alg,
89            apu: self.apu,
90            apv: self.apv,
91            pub_info: pub_w.as_ref(),
92            prv_info: &[],
93        });
94
95        let mut key = kdf.finish_pass();
96        key_output.copy_from_slice(&key[..output_len]);
97        key.zeroize();
98
99        Ok(())
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    #[allow(unused_imports)]
106    use super::*;
107
108    #[cfg(feature = "p256")]
109    #[test]
110    // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03#appendix-A
111    fn expected_1pu_direct_output() {
112        use crate::alg::p256::P256KeyPair;
113        use crate::jwk::FromJwk;
114
115        let alice_sk = P256KeyPair::from_jwk(
116            r#"{"kty":"EC",
117            "crv":"P-256",
118            "x":"WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis",
119            "y":"y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE",
120            "d":"Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg"}"#,
121        )
122        .unwrap();
123        let bob_sk = P256KeyPair::from_jwk(
124            r#"{"kty":"EC",
125            "crv":"P-256",
126            "x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
127            "y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck",
128            "d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"}"#,
129        )
130        .unwrap();
131        let ephem_sk = P256KeyPair::from_jwk(
132            r#"{"kty":"EC",
133            "crv":"P-256",
134            "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
135            "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps",
136            "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"}"#,
137        )
138        .unwrap();
139
140        let mut key_output = [0u8; 32];
141
142        Ecdh1PU::new(
143            &ephem_sk,
144            &alice_sk,
145            &bob_sk,
146            b"A256GCM",
147            b"Alice",
148            b"Bob",
149            &[],
150            false,
151        )
152        .derive_key_bytes(&mut key_output)
153        .unwrap();
154
155        assert_eq!(
156            key_output,
157            hex!("6caf13723d14850ad4b42cd6dde935bffd2fff00a9ba70de05c203a5e1722ca7")
158        );
159    }
160
161    #[cfg(feature = "ed25519")]
162    #[test]
163    // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-04#appendix-B
164    fn expected_1pu_wrapped_output() {
165        use crate::alg::x25519::X25519KeyPair;
166        use crate::jwk::FromJwk;
167
168        let alice_sk = X25519KeyPair::from_jwk(
169            r#"{"kty": "OKP",
170            "crv": "X25519",
171            "x": "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4",
172            "d": "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU"}"#,
173        )
174        .unwrap();
175        let bob_sk = X25519KeyPair::from_jwk(
176            r#"{"kty": "OKP",
177            "crv": "X25519",
178            "x": "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw",
179            "d": "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg"}"#,
180        )
181        .unwrap();
182        let ephem_sk = X25519KeyPair::from_jwk(
183            r#"{"kty": "OKP",
184            "crv": "X25519",
185            "x": "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc",
186            "d": "x8EVZH4Fwk673_mUujnliJoSrLz0zYzzCWp5GUX2fc8"}"#,
187        )
188        .unwrap();
189
190        let mut key_output = [0u8; 16];
191
192        Ecdh1PU::new(
193            &ephem_sk,
194            &alice_sk,
195            &bob_sk,
196            b"ECDH-1PU+A128KW",
197            b"Alice",
198            b"Bob and Charlie",
199            &hex!(
200                "1cb6f87d3966f2ca469a28f74723acda
201                02780e91cce21855470745fe119bdd64"
202            ),
203            false,
204        )
205        .derive_key_bytes(&mut key_output)
206        .unwrap();
207
208        assert_eq!(key_output, hex!("df4c37a0668306a11e3d6b0074b5d8df"));
209    }
210}