1use 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#[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 #[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 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 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 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())?; 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 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 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}