dimpl 0.7.1

DTLS 1.2/1.3 implementation (Sans‑IO, Sync)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
//! Cryptographic provider traits for pluggable crypto backends.
//!
//! This module defines the trait-based interface for cryptographic operations
//! in dimpl, allowing users to provide custom crypto implementations.
//!
//! # Overview
//!
//! The crypto provider system is inspired by rustls's design and uses a component-based
//! approach where the [`CryptoProvider`] struct holds static references to various
//! trait objects, each representing a specific cryptographic capability.
//!
//! # Architecture
//!
//! The provider system is organized into these main components:
//!
//! - **Cipher Suites** ([`SupportedDtls12CipherSuite`]): Factory for AEAD ciphers
//! - **Key Exchange Groups** ([`SupportedKxGroup`]): Factory for ECDHE key exchanges
//! - **Signature Verification** ([`SignatureVerifier`]): Verify signatures in certificates
//! - **Key Provider** ([`KeyProvider`]): Parse and load private keys
//! - **Secure Random** ([`SecureRandom`]): Cryptographically secure RNG
//! - **Hash Provider** ([`HashProvider`]): Factory for hash contexts
//! - **HMAC Provider** ([`HmacProvider`]): Compute HMAC signatures (also drives PRF and HKDF)
//!
//! # Using a Custom Provider
//!
//! To use a custom crypto provider, create one and pass it to the [`Config`](crate::Config):
//!
//! ```
//! # #[cfg(all(feature = "aws-lc-rs", feature = "rcgen"))]
//! # fn main() {
//! use std::sync::Arc;
//! use std::time::Instant;
//! use dimpl::{Config, Dtls, certificate};
//! use dimpl::crypto::aws_lc_rs;
//!
//! let cert = certificate::generate_self_signed_certificate().unwrap();
//! // Use the default aws-lc-rs provider (implicit)
//! let config = Arc::new(Config::default());
//!
//! // Or explicitly set the provider
//! let config = Arc::new(
//!     Config::builder()
//!         .with_crypto_provider(aws_lc_rs::default_provider())
//!         .build()
//!         .unwrap()
//! );
//!
//! // Or use your own custom provider
//! // let config = Arc::new(
//! //     Config::builder()
//! //         .with_crypto_provider(my_custom_provider())
//! //         .build()
//! //         .unwrap()
//! // );
//!
//! let dtls = Dtls::new_12(config, cert, Instant::now());
//! # }
//! # #[cfg(not(all(feature = "aws-lc-rs", feature = "rcgen")))]
//! # fn main() {}
//! ```
//!
//! # Implementing a Custom Provider
//!
//! To implement a custom provider, you need to:
//!
//! 1. Implement the required traits for your crypto backend
//! 2. Create static instances of your implementations
//! 3. Build a [`CryptoProvider`] struct with references to those statics
//!
//! ## Example: Custom Cipher Suite
//!
//! ```
//! use dimpl::CryptoError;
//! use dimpl::crypto::{SupportedDtls12CipherSuite, Cipher, Dtls12CipherSuite, HashAlgorithm};
//! use dimpl::crypto::{Buf, TmpBuf};
//! use dimpl::crypto::{Aad, Nonce};
//!
//! #[derive(Debug)]
//! struct MyCipher;
//!
//! impl MyCipher {
//!     fn new(_key: &[u8]) -> Result<Self, CryptoError> {
//!         Ok(Self)
//!     }
//! }
//!
//! impl Cipher for MyCipher {
//!     fn encrypt(&mut self, _: &mut Buf, _: Aad, _: Nonce) -> Result<(), CryptoError> {
//!         Ok(())
//!     }
//!     fn decrypt(&mut self, _: &mut TmpBuf, _: Aad, _: Nonce) -> Result<(), CryptoError> {
//!         Ok(())
//!     }
//! }
//!
//! #[derive(Debug)]
//! struct MyDtls12CipherSuite;
//!
//! impl SupportedDtls12CipherSuite for MyDtls12CipherSuite {
//!     fn suite(&self) -> Dtls12CipherSuite {
//!         Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256
//!     }
//!
//!     fn hash_algorithm(&self) -> HashAlgorithm {
//!         HashAlgorithm::SHA256
//!     }
//!
//!     fn key_lengths(&self) -> (usize, usize, usize) {
//!         (0, 16, 4) // (mac_key_len, enc_key_len, fixed_iv_len)
//!     }
//!
//!     fn explicit_nonce_len(&self) -> usize {
//!         8 // AES-GCM: 8-byte explicit nonce per record
//!     }
//!
//!     fn tag_len(&self) -> usize {
//!         16 // 128-bit authentication tag
//!     }
//!
//!     fn create_cipher(&self, key: &[u8]) -> Result<Box<dyn Cipher>, CryptoError> {
//!         // Create your cipher implementation here
//!         Ok(Box::new(MyCipher::new(key)?))
//!     }
//! }
//!
//! static MY_CIPHER_SUITE: MyDtls12CipherSuite = MyDtls12CipherSuite;
//! static ALL_CIPHER_SUITES: &[&dyn SupportedDtls12CipherSuite] = &[&MY_CIPHER_SUITE];
//! ```
//!
//! # Requirements
//!
//! For DTLS 1.2, implementations must support:
//!
//! - **Cipher suites**: ECDHE_ECDSA with AES-128-GCM, AES-256-GCM, or CHACHA20_POLY1305
//! - **Key exchange**: ECDHE with X25519, P-256, or P-384 curves
//! - **Signatures**: ECDSA with P-256/SHA-256 or P-384/SHA-384
//! - **Hash**: SHA-256 and SHA-384
//! - **HMAC**: HMAC-SHA256 and HMAC-SHA384 (used for PRF, HKDF, and cookies)
//!
//! # Thread Safety
//!
//! All provider traits require `Send + Sync + UnwindSafe + RefUnwindSafe` to ensure
//! safe usage across threads and panic boundaries.

use std::fmt::Debug;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::OnceLock;

use crate::buffer::{Buf, TmpBuf};
use crate::crypto::{Aad, Nonce};
use crate::dtls12::message::Dtls12CipherSuite;
use crate::types::{Dtls13CipherSuite, HashAlgorithm, NamedGroup, SignatureAlgorithm};
use crate::{CertificateError, CryptoError};

/// OID for the P-256 elliptic curve (secp256r1 / prime256v1).
#[cfg(feature = "_crypto-common")]
pub const OID_P256: spki::ObjectIdentifier =
    spki::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");

/// OID for the P-384 elliptic curve (secp384r1).
#[cfg(feature = "_crypto-common")]
pub const OID_P384: spki::ObjectIdentifier = spki::ObjectIdentifier::new_unwrap("1.3.132.0.34");

// ============================================================================
// Marker Trait
// ============================================================================

/// Marker trait for types that are safe to use in crypto provider components.
///
/// This trait combines the common bounds required for crypto provider trait objects:
/// - [`Send`] + [`Sync`]: Thread-safe
/// - [`Debug`]: Support debugging
/// - [`UnwindSafe`] + [`RefUnwindSafe`]: Panic-safe
///
/// This trait is automatically implemented for all types that satisfy these bounds.
pub trait CryptoSafe: Send + Sync + Debug + UnwindSafe + RefUnwindSafe {}

/// Blanket implementation: any type satisfying the bounds implements [`CryptoSafe`].
impl<T: Send + Sync + Debug + UnwindSafe + RefUnwindSafe> CryptoSafe for T {}

// ============================================================================
// Instance Traits (Level 2 - created by factories)
// ============================================================================

/// AEAD cipher for in-place encryption/decryption.
pub trait Cipher: CryptoSafe {
    /// Encrypt plaintext in-place, appending authentication tag.
    fn encrypt(&mut self, plaintext: &mut Buf, aad: Aad, nonce: Nonce) -> Result<(), CryptoError>;

    /// Decrypt ciphertext in-place, verifying and removing authentication tag.
    fn decrypt(
        &mut self,
        ciphertext: &mut TmpBuf,
        aad: Aad,
        nonce: Nonce,
    ) -> Result<(), CryptoError>;
}

/// Stateful hash context for incremental hashing.
pub trait HashContext: CryptoSafe {
    /// Update the hash with new data.
    fn update(&mut self, data: &[u8]);

    /// Clone the context and finalize it, writing the hash to `out`.
    /// The original context can continue to be updated.
    fn clone_and_finalize(&self, out: &mut Buf);
}

/// Signing key for generating digital signatures.
pub trait SigningKey: CryptoSafe {
    /// Sign data using the specified hash algorithm and return the signature.
    fn sign(
        &mut self,
        data: &[u8],
        hash_alg: HashAlgorithm,
        out: &mut Buf,
    ) -> Result<(), CryptoError>;

    /// Signature algorithm used by this key.
    fn algorithm(&self) -> SignatureAlgorithm;

    /// Default hash algorithm for this key.
    fn hash_algorithm(&self) -> HashAlgorithm;

    /// Hash algorithms this key can sign with.
    ///
    /// Used during negotiation to intersect with the peer's offered
    /// algorithms. Backends that lock the hash at key-load time (e.g.
    /// aws-lc-rs) return only the locked hash; backends that support
    /// arbitrary prehash signing (e.g. RustCrypto) may return several.
    fn supported_hash_algorithms(&self) -> &[HashAlgorithm];
}

/// Active key exchange instance (ephemeral keypair for one handshake).
pub trait ActiveKeyExchange: CryptoSafe {
    /// Get the public key for this exchange.
    fn pub_key(&self) -> &[u8];

    /// Complete exchange with peer's public key, returning shared secret.
    fn complete(self: Box<Self>, peer_pub: &[u8], out: &mut Buf) -> Result<(), CryptoError>;

    /// Get the named group for this exchange.
    fn group(&self) -> NamedGroup;
}

// ============================================================================
// Factory Traits (Level 1 - used by CryptoProvider)
// ============================================================================

/// Cipher suite support (factory for Cipher instances).
pub trait SupportedDtls12CipherSuite: CryptoSafe {
    /// The cipher suite this supports.
    fn suite(&self) -> Dtls12CipherSuite;

    /// Hash algorithm used by this suite.
    fn hash_algorithm(&self) -> HashAlgorithm;

    /// Key material lengths: (mac_key_len, enc_key_len, fixed_iv_len).
    fn key_lengths(&self) -> (usize, usize, usize);

    /// Length in bytes of the per-record explicit nonce (carried in the record body).
    ///
    /// AES-GCM suites carry an 8-byte explicit nonce; ChaCha20-Poly1305 carries none.
    fn explicit_nonce_len(&self) -> usize;

    /// AEAD authentication tag length in bytes.
    fn tag_len(&self) -> usize;

    /// Minimum length, in bytes, of a protected record's encrypted fragment.
    ///
    /// For AEAD suites this equals explicit nonce + authentication tag; a CBC
    /// suite would override this to `IV + MAC + 1` (one padding byte). Records
    /// shorter than this cannot be valid regardless of cipher mode and are
    /// rejected at the record boundary.
    fn min_protected_fragment_len(&self) -> usize {
        self.explicit_nonce_len() + self.tag_len()
    }

    /// Create a cipher instance with the given key.
    fn create_cipher(&self, key: &[u8]) -> Result<Box<dyn Cipher>, CryptoError>;
}

/// Key exchange group support (factory for ActiveKeyExchange).
pub trait SupportedKxGroup: CryptoSafe {
    /// Named group for this key exchange group.
    fn name(&self) -> NamedGroup;

    /// Start a new key exchange, generating ephemeral keypair.
    /// The provided `buf` will be used to store the public key.
    fn start_exchange(&self, buf: Buf) -> Result<Box<dyn ActiveKeyExchange>, CryptoError>;
}

/// Signature verification against certificates.
pub trait SignatureVerifier: CryptoSafe {
    /// Verify a signature on data using a DER-encoded X.509 certificate.
    fn verify_signature(
        &self,
        cert_der: &[u8],
        data: &[u8],
        signature: &[u8],
        hash_alg: HashAlgorithm,
        sig_alg: SignatureAlgorithm,
    ) -> Result<(), CryptoError>;
}

/// Allow-list of supported (signature, hash, curve) combinations for
/// DTLS 1.2 signature verification.
///
/// In DTLS 1.2 the hash algorithm and the certificate's curve are
/// independent choices, so all cross-combinations are valid.
///
///  Signature | Hash    | Curve
/// -----------+---------+-----------
///  ECDSA     | SHA-256 | P-256
///  ECDSA     | SHA-256 | P-384
///  ECDSA     | SHA-384 | P-256
///  ECDSA     | SHA-384 | P-384
const SUPPORTED_VERIFY_SCHEMES: &[(SignatureAlgorithm, HashAlgorithm, NamedGroup)] = &[
    (
        SignatureAlgorithm::ECDSA,
        HashAlgorithm::SHA256,
        NamedGroup::Secp256r1,
    ),
    (
        SignatureAlgorithm::ECDSA,
        HashAlgorithm::SHA256,
        NamedGroup::Secp384r1,
    ),
    (
        SignatureAlgorithm::ECDSA,
        HashAlgorithm::SHA384,
        NamedGroup::Secp256r1,
    ),
    (
        SignatureAlgorithm::ECDSA,
        HashAlgorithm::SHA384,
        NamedGroup::Secp384r1,
    ),
];

/// Check that a (signature, hash, curve) combination is in the allow-list.
pub fn check_verify_scheme(
    sig_alg: SignatureAlgorithm,
    hash_alg: HashAlgorithm,
    group: NamedGroup,
) -> Result<(), CryptoError> {
    if SUPPORTED_VERIFY_SCHEMES
        .iter()
        .any(|(s, h, g)| *s == sig_alg && *h == hash_alg && *g == group)
    {
        Ok(())
    } else {
        Err(CryptoError::UnsupportedSignatureVerification {
            signature: sig_alg,
            hash: hash_alg,
            group,
        })
    }
}

/// Extract the EC curve ([`NamedGroup`]) from a DER-encoded X.509 certificate.
///
/// Used by DTLS 1.3 to verify that the [`SignatureScheme`](crate::types::SignatureScheme)
/// in `CertificateVerify` is consistent with the peer's certificate key.
#[cfg(feature = "_crypto-common")]
pub fn cert_named_group(cert_der: &[u8]) -> Result<NamedGroup, CertificateError> {
    use der::Decode;
    use spki::ObjectIdentifier;
    use x509_cert::Certificate as X509Certificate;

    let cert = X509Certificate::from_der(cert_der).map_err(|_| CertificateError::ParseFailed)?;
    let spki = &cert.tbs_certificate.subject_public_key_info;

    let curve_oid: ObjectIdentifier = spki
        .algorithm
        .parameters
        .as_ref()
        .ok_or(CertificateError::MissingEcCurveParameter)?
        .decode_as()
        .map_err(|_| CertificateError::InvalidEcCurveParameter)?;

    match curve_oid {
        OID_P256 => Ok(NamedGroup::Secp256r1),
        OID_P384 => Ok(NamedGroup::Secp384r1),
        _ => Err(CertificateError::UnsupportedEcCurve),
    }
}

/// Private key parser (factory for SigningKey).
pub trait KeyProvider: CryptoSafe {
    /// Parse and load a private key from DER/PEM bytes.
    fn load_private_key(&self, key_der: &[u8]) -> Result<Box<dyn SigningKey>, CryptoError>;
}

/// Secure random number generator.
pub trait SecureRandom: CryptoSafe {
    /// Fill buffer with cryptographically secure random bytes.
    fn fill(&self, buf: &mut [u8]) -> Result<(), CryptoError>;
}

/// Hash provider (factory for HashContext).
pub trait HashProvider: CryptoSafe {
    /// Create a new hash context for the specified algorithm.
    fn create_hash(&self, algorithm: HashAlgorithm) -> Box<dyn HashContext>;
}

/// HMAC provider for computing HMAC signatures.
pub trait HmacProvider: CryptoSafe {
    /// Compute HMAC-SHA256(key, data) and return the result.
    fn hmac_sha256(&self, key: &[u8], data: &[u8]) -> Result<[u8; 32], CryptoError> {
        let mut out = [0u8; 32];
        self.hmac(HashAlgorithm::SHA256, key, data, &mut out)?;
        Ok(out)
    }

    /// Compute HMAC for the given hash algorithm, writing the result to `out`.
    ///
    /// Returns the number of bytes written.
    fn hmac(
        &self,
        hash: HashAlgorithm,
        key: &[u8],
        data: &[u8],
        out: &mut [u8],
    ) -> Result<usize, CryptoError>;
}

// ============================================================================
// DTLS 1.3 Factory Traits
// ============================================================================

/// Cipher suite support for DTLS 1.3 (factory for Cipher instances).
///
/// Unlike DTLS 1.2 cipher suites, TLS 1.3 cipher suites only specify the
/// AEAD algorithm and hash function. Key exchange is negotiated separately.
pub trait SupportedDtls13CipherSuite: CryptoSafe {
    /// The cipher suite this supports.
    fn suite(&self) -> Dtls13CipherSuite;

    /// Hash algorithm used by this suite.
    fn hash_algorithm(&self) -> HashAlgorithm;

    /// AEAD key length in bytes.
    fn key_len(&self) -> usize;

    /// AEAD nonce/IV length in bytes.
    fn iv_len(&self) -> usize;

    /// AEAD tag length in bytes.
    fn tag_len(&self) -> usize;

    /// Minimum length, in bytes, of a protected record's encrypted fragment.
    /// DTLS 1.3 has no explicit nonce in the record, so this equals
    /// [`Self::tag_len`]. Records shorter than this cannot hold a valid
    /// ciphertext + tag and are rejected at the record boundary.
    fn min_protected_fragment_len(&self) -> usize {
        self.tag_len()
    }

    /// Create a cipher instance with the given key.
    fn create_cipher(&self, key: &[u8]) -> Result<Box<dyn Cipher>, CryptoError>;

    /// Compute a mask for record number encryption (RFC 9147 Section 4.2.3).
    ///
    /// The mask is XORed over the sequence number bytes in the header.
    /// `sample` is the first 16 bytes of the ciphertext.
    ///
    /// For AES-based suites: `mask = AES-ECB(sn_key, sample)`.
    ///
    /// For ChaCha20-based suites (RFC 9001 Section 5.4.4):
    /// `counter = sample[0..4]` (LE u32), `nonce = sample[4..16]`,
    /// `mask = ChaCha20(sn_key, counter, nonce, <zero bytes>)`.
    fn encrypt_sn(&self, sn_key: &[u8], sample: &[u8; 16]) -> [u8; 16];
}

// ============================================================================
// Core Provider Struct
// ============================================================================

/// Cryptographic provider for DTLS operations.
///
/// This struct holds references to all cryptographic components needed
/// for DTLS. Users can provide custom implementations of each component
/// to replace the default aws-lc-rs-based provider.
///
/// # Version-Specific Components
///
/// Shared components like `kx_groups`, `signature_verification`, `key_provider`,
/// `secure_random`, `hash_provider`, and `hmac_provider` are used by both versions.
/// PRF (TLS 1.2) and HKDF (TLS 1.3) key derivation are built generically on top
/// of `hmac_provider` — see the [`prf_hkdf`](super::prf_hkdf) module.
///
/// # Design
///
/// The provider uses static trait object references (`&'static dyn Trait`) which
/// provides zero runtime overhead for trait dispatch. This design is inspired by
/// rustls's CryptoProvider and ensures efficient crypto operations.
///
/// # Example
///
/// ```
/// # #[cfg(feature = "aws-lc-rs")]
/// # fn main() {
/// use dimpl::crypto::{CryptoProvider, aws_lc_rs};
///
/// // Use the default provider
/// let provider = aws_lc_rs::default_provider();
///
/// // Or build a custom one (using defaults for demonstration)
/// let custom_provider = CryptoProvider {
///     // Shared components
///     kx_groups: provider.kx_groups,
///     signature_verification: provider.signature_verification,
///     key_provider: provider.key_provider,
///     secure_random: provider.secure_random,
///     hash_provider: provider.hash_provider,
///     hmac_provider: provider.hmac_provider,
///     // DTLS 1.2 components
///     cipher_suites: provider.cipher_suites,
///     // DTLS 1.3 components
///     dtls13_cipher_suites: provider.dtls13_cipher_suites,
/// };
/// # }
/// # #[cfg(not(feature = "aws-lc-rs"))]
/// # fn main() {}
/// ```
#[derive(Debug, Clone)]
pub struct CryptoProvider {
    // =========================================================================
    // Shared components (used by both DTLS 1.2 and DTLS 1.3)
    // =========================================================================
    /// Supported key exchange groups (P-256, P-384, X25519).
    ///
    /// Used for ECDHE key exchange in both DTLS versions.
    pub kx_groups: &'static [&'static dyn SupportedKxGroup],

    /// Signature verification for certificates.
    pub signature_verification: &'static dyn SignatureVerifier,

    /// Key provider for parsing private keys.
    pub key_provider: &'static dyn KeyProvider,

    /// Secure random number generator.
    pub secure_random: &'static dyn SecureRandom,

    /// Hash provider for handshake hashing.
    pub hash_provider: &'static dyn HashProvider,

    /// HMAC provider for computing HMAC signatures.
    pub hmac_provider: &'static dyn HmacProvider,

    // =========================================================================
    // DTLS 1.2 specific components
    // =========================================================================
    /// Supported DTLS 1.2 cipher suites (for negotiation).
    ///
    /// These cipher suites bundle key exchange, authentication, encryption,
    /// and MAC algorithms together (e.g., TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256).
    pub cipher_suites: &'static [&'static dyn SupportedDtls12CipherSuite],

    // =========================================================================
    // DTLS 1.3 specific components
    // =========================================================================
    /// Supported DTLS 1.3 cipher suites (for negotiation).
    ///
    /// TLS 1.3 cipher suites only specify the AEAD and hash algorithms
    /// (e.g., TLS_AES_128_GCM_SHA256). Key exchange is negotiated separately.
    pub dtls13_cipher_suites: &'static [&'static dyn SupportedDtls13CipherSuite],
}

/// Static storage for the default crypto provider.
///
/// This is set by `install_default()` and retrieved by `get_default()`.
static DEFAULT: OnceLock<CryptoProvider> = OnceLock::new();

impl CryptoProvider {
    /// Install a default crypto provider for the process.
    ///
    /// This sets a global default provider that will be used by
    /// [`Config::builder()`](crate::Config::builder)
    /// when no explicit provider is specified. This is useful for applications that want
    /// to override the default provider per process.
    ///
    /// # Panics
    ///
    /// Panics if called more than once. The default provider can only be set once per process.
    ///
    /// # Example
    ///
    /// ```
    /// # #[cfg(feature = "aws-lc-rs")]
    /// # fn main() {
    /// use dimpl::crypto::{CryptoProvider, aws_lc_rs};
    ///
    /// // Install a default provider (can only be called once per process)
    /// CryptoProvider::install_default(aws_lc_rs::default_provider());
    /// # }
    /// # #[cfg(not(feature = "aws-lc-rs"))]
    /// # fn main() {}
    /// ```
    pub fn install_default(provider: CryptoProvider) {
        DEFAULT
            .set(provider)
            .expect("CryptoProvider::install_default() called more than once");
    }

    /// Get the default crypto provider, if one has been installed.
    ///
    /// Returns `Some(&provider)` if a default provider has been installed via
    /// [`Self::install_default()`], or `None` if no default provider is available.
    ///
    /// This method does not panic. Use [`Config::builder()`](crate::Config::builder) which will handle
    /// the fallback logic automatically.
    ///
    /// # Example
    ///
    /// ```
    /// use dimpl::crypto::CryptoProvider;
    ///
    /// if let Some(provider) = CryptoProvider::get_default() {
    ///     // Use the installed default provider
    /// }
    /// ```
    pub fn get_default() -> Option<&'static CryptoProvider> {
        DEFAULT.get()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(feature = "rcgen")]
    fn cert_named_group_p256() {
        use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P256_SHA256};

        let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
        let params = CertificateParams::new(Vec::<String>::new()).unwrap();
        let cert = params.self_signed(&key_pair).unwrap();

        let group = cert_named_group(cert.der()).unwrap();
        assert_eq!(group, NamedGroup::Secp256r1);
    }

    #[test]
    #[cfg(feature = "rcgen")]
    fn cert_named_group_p384() {
        use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P384_SHA384};

        let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
        let params = CertificateParams::new(Vec::<String>::new()).unwrap();
        let cert = params.self_signed(&key_pair).unwrap();

        let group = cert_named_group(cert.der()).unwrap();
        assert_eq!(group, NamedGroup::Secp384r1);
    }

    #[test]
    #[cfg(feature = "rcgen")]
    fn cert_named_group_invalid_der() {
        let result = cert_named_group(&[0x00, 0x01, 0x02]);
        assert!(result.is_err());
    }
}