Skip to main content

aws_lc_rs/
evp_pkey.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use crate::aws_lc::{
5    EVP_DigestSign, EVP_DigestSignInit, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new,
6    EVP_PKEY_CTX_new_id, EVP_PKEY_bits, EVP_PKEY_cmp, EVP_PKEY_derive, EVP_PKEY_derive_init,
7    EVP_PKEY_derive_set_peer, EVP_PKEY_get0_EC_KEY, EVP_PKEY_get0_RSA,
8    EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_id, EVP_PKEY_keygen,
9    EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY_new_raw_public_key,
10    EVP_PKEY_pqdsa_new_raw_private_key, EVP_PKEY_pqdsa_new_raw_public_key, EVP_PKEY_sign,
11    EVP_PKEY_sign_init, EVP_PKEY_size, EVP_PKEY_up_ref, EVP_PKEY_verify, EVP_PKEY_verify_init,
12    EVP_marshal_private_key, EVP_marshal_private_key_v2, EVP_marshal_public_key,
13    EVP_parse_private_key, EVP_parse_public_key, EC_KEY, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_ED25519,
14    EVP_PKEY_PQDSA, NID_MLDSA44, NID_MLDSA65, NID_MLDSA87, RSA,
15};
16use crate::cbb::LcCBB;
17use crate::digest::digest_ctx::DigestContext;
18use crate::digest::Digest;
19use crate::error::{KeyRejected, Unspecified};
20use crate::fips::indicator_check;
21use crate::pkcs8::Version;
22use crate::ptr::{ConstPointer, LcPtr};
23use crate::{cbs, digest};
24use core::ffi::c_int;
25use std::ptr::{null, null_mut};
26use zeroize::Zeroizing;
27
28impl PartialEq<Self> for LcPtr<EVP_PKEY> {
29    /// Only compares params and public key
30    fn eq(&self, other: &Self) -> bool {
31        // EVP_PKEY_cmp only compares params and public key
32        1 == unsafe { EVP_PKEY_cmp(self.as_const_ptr(), other.as_const_ptr()) }
33    }
34}
35
36#[allow(non_camel_case_types)]
37pub(crate) trait EVP_PKEY_CTX_consumer: Fn(*mut EVP_PKEY_CTX) -> Result<(), ()> {}
38
39impl<T> EVP_PKEY_CTX_consumer for T where T: Fn(*mut EVP_PKEY_CTX) -> Result<(), ()> {}
40
41#[allow(non_upper_case_globals, clippy::type_complexity)]
42pub(crate) const No_EVP_PKEY_CTX_consumer: Option<fn(*mut EVP_PKEY_CTX) -> Result<(), ()>> = None;
43
44impl ConstPointer<'_, EVP_PKEY> {
45    pub(crate) fn validate_as_ed25519(&self) -> Result<(), KeyRejected> {
46        const ED25519_KEY_TYPE: c_int = EVP_PKEY_ED25519;
47        const ED25519_MIN_BITS: c_int = 253;
48        const ED25519_MAX_BITS: c_int = 256;
49
50        let key_type = self.id();
51        if key_type != ED25519_KEY_TYPE {
52            return Err(KeyRejected::wrong_algorithm());
53        }
54
55        let bits: c_int = self.key_size_bits().try_into().unwrap();
56        if bits < ED25519_MIN_BITS {
57            return Err(KeyRejected::too_small());
58        }
59
60        if bits > ED25519_MAX_BITS {
61            return Err(KeyRejected::too_large());
62        }
63        Ok(())
64    }
65
66    // EVP_PKEY_NONE = 0;
67    // EVP_PKEY_RSA = 6;
68    // EVP_PKEY_RSA_PSS = 912;
69    // EVP_PKEY_DSA = 116;
70    // EVP_PKEY_EC = 408;
71    // EVP_PKEY_ED25519 = 949;
72    // EVP_PKEY_X25519 = 948;
73    // EVP_PKEY_KYBER512 = 970;
74    // EVP_PKEY_HKDF = 969;
75    // EVP_PKEY_DH = 28;
76    // EVP_PKEY_RSA2 = 19;
77    // EVP_PKEY_X448 = 961;
78    // EVP_PKEY_ED448 = 960;
79    pub(crate) fn id(&self) -> i32 {
80        unsafe { EVP_PKEY_id(self.as_const_ptr()) }
81    }
82
83    pub(crate) fn key_size_bytes(&self) -> usize {
84        self.key_size_bits() / 8
85    }
86
87    pub(crate) fn key_size_bits(&self) -> usize {
88        unsafe { EVP_PKEY_bits(self.as_const_ptr()) }
89            .try_into()
90            .unwrap()
91    }
92
93    pub(crate) fn signature_size_bytes(&self) -> usize {
94        unsafe { EVP_PKEY_size(self.as_const_ptr()) }
95            .try_into()
96            .unwrap()
97    }
98
99    #[allow(dead_code)]
100    pub(crate) fn get_ec_key(&self) -> Result<ConstPointer<'_, EC_KEY>, KeyRejected> {
101        self.project_const_lifetime(unsafe {
102            |evp_pkey| EVP_PKEY_get0_EC_KEY(evp_pkey.as_const_ptr())
103        })
104        .map_err(|()| KeyRejected::wrong_algorithm())
105    }
106
107    pub(crate) fn get_rsa(&self) -> Result<ConstPointer<'_, RSA>, KeyRejected> {
108        self.project_const_lifetime(unsafe {
109            |evp_pkey| EVP_PKEY_get0_RSA(evp_pkey.as_const_ptr())
110        })
111        .map_err(|()| KeyRejected::wrong_algorithm())
112    }
113
114    pub(crate) fn marshal_rfc5280_public_key(&self) -> Result<Vec<u8>, Unspecified> {
115        // Data shows that the SubjectPublicKeyInfo is roughly 356% to 375% increase in size compared to the RSA key
116        // size in bytes for keys ranging from 2048-bit to 4096-bit. So size the initial capacity to be roughly
117        // 500% as a conservative estimate to avoid needing to reallocate for any key in that range.
118        let mut cbb = LcCBB::new(self.key_size_bytes() * 5);
119        if 1 != unsafe { EVP_marshal_public_key(cbb.as_mut_ptr(), self.as_const_ptr()) } {
120            return Err(Unspecified);
121        }
122        cbb.into_vec()
123    }
124
125    pub(crate) fn marshal_rfc5208_private_key(
126        &self,
127        version: Version,
128    ) -> Result<Vec<u8>, Unspecified> {
129        let key_size_bytes =
130            TryInto::<usize>::try_into(unsafe { EVP_PKEY_bits(self.as_const_ptr()) })
131                .expect("fit in usize")
132                / 8;
133        let mut cbb = LcCBB::new(key_size_bytes * 5);
134        match version {
135            Version::V1 => {
136                if 1 != unsafe { EVP_marshal_private_key(cbb.as_mut_ptr(), self.as_const_ptr()) } {
137                    return Err(Unspecified);
138                }
139            }
140            Version::V2 => {
141                if 1 != unsafe { EVP_marshal_private_key_v2(cbb.as_mut_ptr(), self.as_const_ptr()) }
142                {
143                    return Err(Unspecified);
144                }
145            }
146        }
147        cbb.into_vec()
148    }
149
150    pub(crate) fn marshal_raw_private_key(&self) -> Result<Vec<u8>, Unspecified> {
151        let mut size = 0;
152        if 1 != unsafe { EVP_PKEY_get_raw_private_key(self.as_const_ptr(), null_mut(), &mut size) }
153        {
154            return Err(Unspecified);
155        }
156        let mut buffer = vec![0u8; size];
157        let buffer_size = self.marshal_raw_private_to_buffer(&mut buffer)?;
158        debug_assert_eq!(buffer_size, size);
159        Ok(buffer)
160    }
161
162    pub(crate) fn marshal_raw_private_to_buffer(
163        &self,
164        buffer: &mut [u8],
165    ) -> Result<usize, Unspecified> {
166        let mut key_len = buffer.len();
167        if 1 == unsafe {
168            EVP_PKEY_get_raw_private_key(self.as_const_ptr(), buffer.as_mut_ptr(), &mut key_len)
169        } {
170            Ok(key_len)
171        } else {
172            Err(Unspecified)
173        }
174    }
175
176    #[allow(dead_code)]
177    pub(crate) fn marshal_raw_public_key(&self) -> Result<Vec<u8>, Unspecified> {
178        let mut size = 0;
179        if 1 != unsafe { EVP_PKEY_get_raw_public_key(self.as_const_ptr(), null_mut(), &mut size) } {
180            return Err(Unspecified);
181        }
182        let mut buffer = vec![0u8; size];
183        let buffer_size = self.marshal_raw_public_to_buffer(&mut buffer)?;
184        debug_assert_eq!(buffer_size, size);
185        Ok(buffer)
186    }
187
188    pub(crate) fn marshal_raw_public_to_buffer(
189        &self,
190        buffer: &mut [u8],
191    ) -> Result<usize, Unspecified> {
192        let mut key_len = buffer.len();
193        if 1 == unsafe {
194            // `EVP_PKEY_get_raw_public_key` writes the total length
195            // to `encapsulate_key_size` in the event that the buffer we provide is larger then
196            // required.
197            EVP_PKEY_get_raw_public_key(self.as_const_ptr(), buffer.as_mut_ptr(), &mut key_len)
198        } {
199            Ok(key_len)
200        } else {
201            Err(Unspecified)
202        }
203    }
204}
205
206impl LcPtr<EVP_PKEY> {
207    #[inline]
208    pub unsafe fn as_mut_unsafe_ptr(&self) -> *mut EVP_PKEY {
209        self.as_const_ptr().cast_mut()
210    }
211
212    pub(crate) fn parse_rfc5280_public_key(
213        bytes: &[u8],
214        evp_pkey_type: c_int,
215    ) -> Result<Self, KeyRejected> {
216        let mut cbs = cbs::build_CBS(bytes);
217        // Also checks the validity of the key
218        let evp_pkey = LcPtr::new(unsafe { EVP_parse_public_key(&mut cbs) })
219            .map_err(|()| KeyRejected::invalid_encoding())?;
220        evp_pkey
221            .as_const()
222            .id()
223            .eq(&evp_pkey_type)
224            .then_some(evp_pkey)
225            .ok_or(KeyRejected::wrong_algorithm())
226    }
227
228    pub(crate) fn parse_rfc5208_private_key(
229        bytes: &[u8],
230        evp_pkey_type: c_int,
231    ) -> Result<Self, KeyRejected> {
232        let mut cbs = cbs::build_CBS(bytes);
233        // Also checks the validity of the key
234        let evp_pkey = LcPtr::new(unsafe { EVP_parse_private_key(&mut cbs) })
235            .map_err(|()| KeyRejected::invalid_encoding())?;
236        evp_pkey
237            .as_const()
238            .id()
239            .eq(&evp_pkey_type)
240            .then_some(evp_pkey)
241            .ok_or(KeyRejected::wrong_algorithm())
242    }
243
244    #[allow(non_snake_case)]
245    pub(crate) fn create_EVP_PKEY_CTX(&self) -> Result<LcPtr<EVP_PKEY_CTX>, ()> {
246        // The only modification made by EVP_PKEY_CTX_new to `priv_key` is to increment its
247        // refcount. AWS-LC's refcount operations are thread-safe: lock-free `_Atomic` CAS on the
248        // C11-atomic build, `InterlockedIncrement`-style atomics on Windows, and a mutex-protected
249        // fallback otherwise. See:
250        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_c11.c
251        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_win.c
252        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_lock.c
253        LcPtr::new(unsafe { EVP_PKEY_CTX_new(self.as_mut_unsafe_ptr(), null_mut()) })
254    }
255
256    pub(crate) fn parse_raw_private_key(
257        bytes: &[u8],
258        evp_pkey_type: c_int,
259    ) -> Result<Self, KeyRejected> {
260        if evp_pkey_type == EVP_PKEY_PQDSA {
261            return match bytes.len() {
262                2560 => Self::new(unsafe {
263                    EVP_PKEY_pqdsa_new_raw_private_key(NID_MLDSA44, bytes.as_ptr(), bytes.len())
264                }),
265                4032 => Self::new(unsafe {
266                    EVP_PKEY_pqdsa_new_raw_private_key(NID_MLDSA65, bytes.as_ptr(), bytes.len())
267                }),
268                4896 => Self::new(unsafe {
269                    EVP_PKEY_pqdsa_new_raw_private_key(NID_MLDSA87, bytes.as_ptr(), bytes.len())
270                }),
271                _ => Err(()),
272            }
273            .map_err(|()| KeyRejected::invalid_encoding());
274        }
275
276        Self::new(unsafe {
277            EVP_PKEY_new_raw_private_key(evp_pkey_type, null_mut(), bytes.as_ptr(), bytes.len())
278        })
279        .map_err(|()| KeyRejected::unspecified())
280    }
281
282    pub(crate) fn parse_raw_public_key(
283        bytes: &[u8],
284        evp_pkey_type: c_int,
285    ) -> Result<Self, KeyRejected> {
286        if evp_pkey_type == EVP_PKEY_PQDSA {
287            return match bytes.len() {
288                1312 => Self::new(unsafe {
289                    EVP_PKEY_pqdsa_new_raw_public_key(NID_MLDSA44, bytes.as_ptr(), bytes.len())
290                }),
291                1952 => Self::new(unsafe {
292                    EVP_PKEY_pqdsa_new_raw_public_key(NID_MLDSA65, bytes.as_ptr(), bytes.len())
293                }),
294                2592 => Self::new(unsafe {
295                    EVP_PKEY_pqdsa_new_raw_public_key(NID_MLDSA87, bytes.as_ptr(), bytes.len())
296                }),
297                _ => Err(()),
298            }
299            .map_err(|()| KeyRejected::unspecified());
300        }
301
302        Self::new(unsafe {
303            EVP_PKEY_new_raw_public_key(evp_pkey_type, null_mut(), bytes.as_ptr(), bytes.len())
304        })
305        .map_err(|()| KeyRejected::invalid_encoding())
306    }
307
308    pub(crate) fn sign<F>(
309        &self,
310        message: &[u8],
311        digest: Option<&'static digest::Algorithm>,
312        padding_fn: Option<F>,
313    ) -> Result<Box<[u8]>, Unspecified>
314    where
315        F: EVP_PKEY_CTX_consumer,
316    {
317        let mut md_ctx = DigestContext::new_uninit();
318        let evp_md = if let Some(alg) = digest {
319            digest::match_digest_type(&alg.id).as_const_ptr()
320        } else {
321            null()
322        };
323        let mut pctx = null_mut::<EVP_PKEY_CTX>();
324        if 1 != unsafe {
325            // EVP_DigestSignInit does not mutate |pkey| for thread-safety purposes and may be
326            // used concurrently with other non-mutating functions on |pkey|.
327            // https://github.com/aws/aws-lc/blob/9b4b5a15a97618b5b826d742419ccd54c819fa42/include/openssl/evp.h#L297-L313
328            EVP_DigestSignInit(
329                md_ctx.as_mut_ptr(),
330                &mut pctx,
331                evp_md,
332                null_mut(),
333                self.as_mut_unsafe_ptr(),
334            )
335        } {
336            return Err(Unspecified);
337        }
338
339        if let Some(pad_fn) = padding_fn {
340            pad_fn(pctx)?;
341        }
342
343        // Determine the maximum length of the signature.
344        let mut sig_len = 0;
345        if 1 != unsafe {
346            EVP_DigestSign(
347                md_ctx.as_mut_ptr(),
348                null_mut(),
349                &mut sig_len,
350                message.as_ptr(),
351                message.len(),
352            )
353        } {
354            return Err(Unspecified);
355        }
356        if sig_len == 0 {
357            return Err(Unspecified);
358        }
359
360        let mut signature = vec![0u8; sig_len];
361        if 1 != indicator_check!(unsafe {
362            EVP_DigestSign(
363                md_ctx.as_mut_ptr(),
364                signature.as_mut_ptr(),
365                &mut sig_len,
366                message.as_ptr(),
367                message.len(),
368            )
369        }) {
370            return Err(Unspecified);
371        }
372        signature.truncate(sig_len);
373        Ok(signature.into_boxed_slice())
374    }
375
376    pub(crate) fn sign_digest<F>(
377        &self,
378        digest: &Digest,
379        padding_fn: Option<F>,
380    ) -> Result<Box<[u8]>, Unspecified>
381    where
382        F: EVP_PKEY_CTX_consumer,
383    {
384        let mut pctx = self.create_EVP_PKEY_CTX()?;
385
386        if 1 != unsafe { EVP_PKEY_sign_init(pctx.as_mut_ptr()) } {
387            return Err(Unspecified);
388        }
389
390        if let Some(pad_fn) = padding_fn {
391            pad_fn(pctx.as_mut_ptr())?;
392        }
393
394        let msg_digest = digest.as_ref();
395        let mut sig_len = 0;
396        if 1 != unsafe {
397            EVP_PKEY_sign(
398                pctx.as_mut_ptr(),
399                null_mut(),
400                &mut sig_len,
401                msg_digest.as_ptr(),
402                msg_digest.len(),
403            )
404        } {
405            return Err(Unspecified);
406        }
407
408        let mut signature = vec![0u8; sig_len];
409        if 1 != indicator_check!(unsafe {
410            EVP_PKEY_sign(
411                pctx.as_mut_ptr(),
412                signature.as_mut_ptr(),
413                &mut sig_len,
414                msg_digest.as_ptr(),
415                msg_digest.len(),
416            )
417        }) {
418            return Err(Unspecified);
419        }
420        signature.truncate(sig_len);
421
422        Ok(signature.into_boxed_slice())
423    }
424
425    pub(crate) fn verify<F>(
426        &self,
427        msg: &[u8],
428        digest: Option<&'static digest::Algorithm>,
429        padding_fn: Option<F>,
430        signature: &[u8],
431    ) -> Result<(), Unspecified>
432    where
433        F: EVP_PKEY_CTX_consumer,
434    {
435        let mut md_ctx = DigestContext::new_uninit();
436
437        let evp_md = if let Some(alg) = digest {
438            digest::match_digest_type(&alg.id).as_const_ptr()
439        } else {
440            null()
441        };
442
443        let mut pctx = null_mut::<EVP_PKEY_CTX>();
444
445        if 1 != unsafe {
446            EVP_DigestVerifyInit(
447                md_ctx.as_mut_ptr(),
448                &mut pctx,
449                evp_md,
450                null_mut(),
451                self.as_mut_unsafe_ptr(),
452            )
453        } {
454            return Err(Unspecified);
455        }
456        if let Some(pad_fn) = padding_fn {
457            pad_fn(pctx)?;
458        }
459
460        if 1 != indicator_check!(unsafe {
461            EVP_DigestVerify(
462                md_ctx.as_mut_ptr(),
463                signature.as_ptr(),
464                signature.len(),
465                msg.as_ptr(),
466                msg.len(),
467            )
468        }) {
469            return Err(Unspecified);
470        }
471
472        Ok(())
473    }
474
475    pub(crate) fn verify_digest_sig<F>(
476        &self,
477        digest: &Digest,
478        padding_fn: Option<F>,
479        signature: &[u8],
480    ) -> Result<(), Unspecified>
481    where
482        F: EVP_PKEY_CTX_consumer,
483    {
484        let mut pctx = self.create_EVP_PKEY_CTX()?;
485
486        if 1 != unsafe { EVP_PKEY_verify_init(pctx.as_mut_ptr()) } {
487            return Err(Unspecified);
488        }
489
490        if let Some(pad_fn) = padding_fn {
491            pad_fn(pctx.as_mut_ptr())?;
492        }
493
494        let msg_digest = digest.as_ref();
495
496        if 1 == unsafe {
497            indicator_check!(EVP_PKEY_verify(
498                pctx.as_mut_ptr(),
499                signature.as_ptr(),
500                signature.len(),
501                msg_digest.as_ptr(),
502                msg_digest.len(),
503            ))
504        } {
505            Ok(())
506        } else {
507            Err(Unspecified)
508        }
509    }
510
511    pub(crate) fn agree(&self, peer_key: &mut Self) -> Result<Zeroizing<Vec<u8>>, Unspecified> {
512        let mut pctx = self.create_EVP_PKEY_CTX()?;
513
514        if 1 != unsafe { EVP_PKEY_derive_init(pctx.as_mut_ptr()) } {
515            return Err(Unspecified);
516        }
517
518        let mut secret_len = 0;
519        if 1 != unsafe { EVP_PKEY_derive_set_peer(pctx.as_mut_ptr(), peer_key.as_mut_ptr()) } {
520            return Err(Unspecified);
521        }
522
523        if 1 != unsafe { EVP_PKEY_derive(pctx.as_mut_ptr(), null_mut(), &mut secret_len) } {
524            return Err(Unspecified);
525        }
526
527        // Own the allocation before the fallible derive call so every exit path scrubs it.
528        let mut secret = Zeroizing::new(vec![0u8; secret_len]);
529        if 1 != indicator_check!(unsafe {
530            EVP_PKEY_derive(pctx.as_mut_ptr(), secret.as_mut_ptr(), &mut secret_len)
531        }) {
532            return Err(Unspecified);
533        }
534        // Preserve the allocation so `Zeroizing` can scrub its full capacity.
535        secret.truncate(secret_len);
536
537        Ok(secret)
538    }
539
540    pub(crate) fn generate<F>(pkey_type: c_int, params_fn: Option<F>) -> Result<Self, Unspecified>
541    where
542        F: EVP_PKEY_CTX_consumer,
543    {
544        let mut pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new_id(pkey_type, null_mut()) })?;
545
546        if 1 != unsafe { EVP_PKEY_keygen_init(pkey_ctx.as_mut_ptr()) } {
547            return Err(Unspecified);
548        }
549
550        if let Some(pad_fn) = params_fn {
551            pad_fn(pkey_ctx.as_mut_ptr())?;
552        }
553
554        let mut pkey = null_mut::<EVP_PKEY>();
555
556        if 1 != indicator_check!(unsafe { EVP_PKEY_keygen(pkey_ctx.as_mut_ptr(), &mut pkey) }) {
557            return Err(Unspecified);
558        }
559
560        Ok(LcPtr::new(pkey)?)
561    }
562}
563
564impl Clone for LcPtr<EVP_PKEY> {
565    fn clone(&self) -> Self {
566        // EVP_PKEY_up_ref increments the refcount using AWS-LC's thread-safe refcount
567        // implementation: lock-free `_Atomic` CAS on the C11-atomic build, `InterlockedIncrement`-
568        // style atomics on Windows, and a mutex-protected fallback otherwise. See:
569        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_c11.c
570        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_win.c
571        // https://github.com/aws/aws-lc/blob/main/crypto/refcount_lock.c
572        assert_eq!(
573            1,
574            unsafe { EVP_PKEY_up_ref(self.as_mut_unsafe_ptr()) },
575            "infallible AWS-LC function"
576        );
577        Self::new(unsafe { self.as_mut_unsafe_ptr() }).expect("non-null AWS-LC EVP_PKEY pointer")
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584    use crate::aws_lc::EVP_PKEY_X25519;
585
586    fn generate_ed25519() -> LcPtr<EVP_PKEY> {
587        LcPtr::<EVP_PKEY>::generate(EVP_PKEY_ED25519, No_EVP_PKEY_CTX_consumer)
588            .expect("ed25519 keygen")
589    }
590
591    fn generate_x25519() -> LcPtr<EVP_PKEY> {
592        LcPtr::<EVP_PKEY>::generate(EVP_PKEY_X25519, No_EVP_PKEY_CTX_consumer)
593            .expect("x25519 keygen")
594    }
595
596    #[test]
597    fn agree_computes_matching_shared_secret_from_both_sides() {
598        let mut key_a = generate_x25519();
599        let mut key_b = generate_x25519();
600
601        let secret_ab = key_a.agree(&mut key_b).expect("agree a->b");
602        let secret_ba = key_b.agree(&mut key_a).expect("agree b->a");
603
604        assert!(!secret_ab.is_empty());
605        assert_eq!(secret_ab.as_slice(), secret_ba.as_slice());
606    }
607
608    #[test]
609    fn agree_rejects_mismatched_key_types() {
610        let x25519_key = generate_x25519();
611        let mut ed25519_key = generate_ed25519();
612
613        assert!(x25519_key.agree(&mut ed25519_key).is_err());
614    }
615}