libcoap_rs/crypto/pki_rpk/
mod.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright © The libcoap-rs Contributors, all rights reserved.
4 * This file is part of the libcoap-rs project, see the README file for
5 * general information on this project and the NOTICE.md and LICENSE files
6 * for information regarding copyright ownership and terms of use.
7 *
8 * crypto/pki_rpk/mod.rs - Interfaces and types for PKI/RPK support in libcoap-rs.
9 */
10//! Types and traits related to (D)TLS with raw public keys and/or public key infrastructure support
11//! for CoAP.
12//!
13//! In order to configure PKI and/or RPK support, the following general steps need to be followed:
14//! 1. Create a key definition for the desired DTLS variant, see [`PkiKeyDef`](pki_rpk::PkiKeyDef)
15//!    and [`RpkKeyDef`](pki_rpk::RpkKeyDef) for more detailed information.
16//! 2. Create a [`PkiRpkContextBuilder`](pki_rpk::PkiRpkContextBuilder) using the provided key and
17//!    (optionally) make some additional configuration changes (see the builder struct
18//!    documentation).
19//! 3. Call [`PkiRpkContextBuilder::build`](pki_rpk::PkiRpkContextBuilder::build) to create a
20//!    [`PkiRpkContext`](pki_rpk::PkiRpkContext).
21//! 4. Provide the created context to [`CoapClientSession::connect_dtls`](crate::session::CoapClientSession::connect_dtls)
22//!    (for client-side sessions) or [`CoapContext::set_pki_rpk_context`](crate::CoapContext::set_pki_rpk_context)
23//!    (for server-side sessions).
24//! 5. On servers, run [`CoapContext::add_endpoint_dtls`](crate::CoapContext::add_endpoint_dtls) to
25//!    add a DTLS endpoint.
26//!
27//! Note that [`PkiRpkContextBuilder`](pki_rpk::PkiRpkContextBuilder) uses generics with the marker
28//! structs [`Pki`](pki_rpk::Pki) and [`Rpk`](pki_rpk::Rpk) to statically indicate the DTLS variant
29//! and [`NonCertVerifying`](pki_rpk::NonCertVerifying) and [`CertVerifying`](pki_rpk::CertVerifying)
30//! to indicate whether the peer certificate should be verified (PKI only, RPK will always use
31//! [`NonCertVerifying`](pki_rpk::NonCertVerifying)).
32//!
33//! # Examples
34#![cfg_attr(
35    feature = "dtls-rpk",
36    doc = r##"
37Creating and connecting a client-side session with DTLS RPK configured:
38```no_run
39use libcoap_rs::CoapContext;
40use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
41use libcoap_rs::session::{CoapClientSession, CoapSession};
42
43// RPK is only supported if the key is provided as a byte array in memory, providing file paths
44// directly is unsupported.
45let client_private_key = Vec::from(include_str!("../../../resources/test-keys/client/client.key.pem"));
46let client_public_key = Vec::from(include_str!("../../../resources/test-keys/client/client.pub.pem"));
47
48// Create key definition.
49let client_key_def = RpkKeyDef::with_pem_memory(client_public_key, client_private_key);
50
51// Create the cryptography context. Note that you might have to explicitly specify that
52// PKI certificate validation should not be performed, even though enabling it while passing a
53// RPK key definition is impossible due to a lack of a constructor for
54// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
55// This is a type system limitation.
56let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(client_key_def);
57
58let key_validator = |asn1_public_key: &[u8], session: &CoapSession, validated: bool| {
59    if !validated {
60        false
61    } else {
62        // Here, you would add code to validate that the peer's public key is actually the one you
63        // expect, and return either true (accept key) or false (reject key).
64        // `asn1_encoded_key` should be the certificate structure defined in
65        // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
66        // might be able to parse with crates like
67        // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
68        // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
69        // SubjectPublicKeyInformation encoded within.
70        //
71        // Instead of using a lambda like this, you could also implement `RpkValidator` on any
72        // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
73        // keys.
74        # true
75    }
76};
77
78// Set the RPK validator and build the context.
79let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
80
81let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
82let session = CoapClientSession::connect_dtls(&mut coap_ctx, "example.com:5684".parse().unwrap(), crypto_ctx);
83
84// The session might not be immediately established, but you can already create and send
85// requests as usual after this point.
86// To check for errors and/or disconnections, you might want to call and check the return value
87// of `session.state()` occasionally.
88// For error handling, you might also want to register an event handler with the CoAP context.
89// Remaining code omitted for brevity, see the crate-level docs for a full example of client
90// operation.
91```
92
93Creating a server that supports DTLS RPK configured:
94```no_run
95use libcoap_rs::CoapContext;
96use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
97use libcoap_rs::session::{CoapClientSession, CoapSession};
98
99fn key_validator(asn1_public_key: &[u8], session: &CoapSession, validated: bool) -> bool {
100    // Here, you would add code to validate that the peer's public key is actually the one you
101    // expect, and return either true (accept key) or false (reject key).
102    // `asn1_encoded_key` should be the certificate structure defined in
103    // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
104    // might be able to parse with crates like
105    // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
106    // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
107    // SubjectPublicKeyInformation encoded within.
108    //
109    // Instead of using a function like this, you could also implement `RpkValidator` on any
110    // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
111    // keys.
112    # true
113}
114
115// RPK is only supported if the key is provided as a byte array in memory, providing file paths
116// directly is unsupported.
117let server_private_key = Vec::from(include_str!("../../../resources/test-keys/server/server.key.pem"));
118let server_public_key = Vec::from(include_str!("../../../resources/test-keys/server/server.pub.pem"));
119
120// Create key definition.
121let server_key_def = RpkKeyDef::with_pem_memory(server_public_key, server_private_key);
122
123// Create the cryptography context. Note that you might have to explicitly specify that
124// PKI certificate validation should not be performed, even though enabling it while passing a
125// RPK key definition is impossible due to a lack of a constructor for
126// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
127// This is a type system limitation.
128let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(server_key_def);
129// Set the RPK validator and build the context.
130let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
131
132let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
133coap_ctx.set_pki_rpk_context(crypto_ctx);
134coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");
135
136// For error handling, you might want to register an event handler with the CoAP context.
137// Remaining code omitted for brevity, see the crate-level docs for a full example of server
138// operation.
139```
140"##
141)]
142#![cfg_attr(
143    feature = "dtls-pki",
144    doc = r##"
145
146Creating and connecting a client-side session with DTLS PKI configured:
147```no_run
148use std::ffi::{c_uint, CStr};
149use std::net::SocketAddr;
150use libcoap_rs::CoapContext;
151use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder};
152use libcoap_rs::session::{CoapClientSession, CoapSession};
153use std::ffi::CString;
154
155// Paths to private key and certificate.
156// The certificate may also contain intermediates. However, they might *not* be provided to the
157// peer (i.e., the peer might have to already know all intermediates beforehand in order for
158// validation to succeed).
159let client_private_key = "../../../resources/test-keys/client/client.key.pem";
160let client_public_cert = "../../../resources/test-keys/client/client.crt.pem";
161
162// Create key definition.
163// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
164// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
165// It is exclusively used to determine a list of CA names that a server will provide to a client
166// to indicate to it which certificates it should send.
167// For client-side operation, `ca_cert` is not used.
168let client_key_def = PkiKeyDef::with_pem_files(
169                        None::<String>,
170                        client_public_cert,
171                        client_private_key);
172
173// The name of the server we want to connect to.
174let server_name = "example.com";
175
176// Validator function for certificate common names.
177// Typically, this function should have the following behavior:
178// - If !validated, something went wrong during TLS-level certificate checks, so reject.
179// - If depth == 0, we are checking the client certificate, whose common name should equal the
180//   server name we want to connect to, return the equality check result.
181// - If depth > 0, we are checking an intermediate or root CA certificate. As we usually trust
182//   all CAs in the trust store and validation of those is already performed by the TLS library,
183//   always accept.
184let c_server_name = CString::new(server_name).unwrap();
185let cn_validator = |
186     cn: &CStr,
187     asn1_public_cert: &[u8],
188     session: &CoapSession,
189     depth: c_uint,
190     validated: bool| {
191    if !validated {
192        false
193    } else if depth == 0 {
194        cn == c_server_name.as_c_str()
195    } else {
196        true
197    }
198};
199
200// Create the cryptography context. Note that you must explicitly specify whether
201// PKI certificate validation should be performed using the context builder's generics.
202let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(client_key_def)
203                 // Provide the server with a Server Name Indication (might be required by
204                 // some servers to use the right certificate).
205                 .client_sni(server_name).unwrap()
206                 // Use the CN validator we defined earlier.
207                 .cn_validator(cn_validator)
208                 // Enable certificate chain validation (in case you have intermediate CAs) and set
209                 // verification depth (recommended value here is 3).
210                 .cert_chain_validation(3)
211                 .build();
212
213let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
214let session = CoapClientSession::connect_dtls(
215             &mut coap_ctx,
216             SocketAddr::new(server_name.parse().expect("error in name resolution"), 5684),
217             crypto_ctx);
218
219// The session might not be immediately established, but you can already create and send
220// requests as usual after this point.
221// To check for errors and/or disconnections, you might want to call and check the return value
222// of `session.state()` occasionally.
223// For error handling, you might also want to register an event handler with the CoAP context.
224// Remaining code omitted for brevity, see the crate-level docs for a full example of client
225// operation.
226```
227
228Creating a server that supports DTLS RPK configured:
229```no_run
230use std::ffi::{c_uint, CStr};
231use std::net::SocketAddr;
232use libcoap_rs::CoapContext;
233use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder, KeyDef, Pki};
234use libcoap_rs::session::{CoapClientSession, CoapSession};
235use std::ffi::CString;
236
237// Paths to private key and certificate.
238// The certificate may also contain intermediates. However, they might *not* be provided to the
239// peer (i.e., the peer might have to already know all intermediates beforehand in order for
240// validation to succeed).
241let server_private_key = "../../../resources/test-keys/server/server.key.pem";
242let server_public_cert = "../../../resources/test-keys/server/server.crt.pem";
243let ca_cert = "../../../resources/test-keys/ca/ca.crt.pem";
244
245// Create key definition.
246// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
247// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
248// It is exclusively used to determine a list of CA names that a server will provide to a client
249// to indicate to it which certificates it should send.
250let server_key_def = PkiKeyDef::with_pem_files(Some(ca_cert), server_public_cert, server_private_key);
251
252// The name of the server we use.
253let server_name = "example.com";
254
255// Key provider for Server Name Indications.
256// If the client provides a server name using the Server Name Indication extension, this
257// callback is called to determine the key the server should use instead of the one provided as
258// the default to `PkiRpkContextBuilder::new`.
259// Typically, you would want to maintain a map from potential server names to key definitions,
260// and return either `Some(Box::new(key))` for the appropriate map entry or `None` if the server
261// name is unknown.
262let c_server_name = CString::new(server_name).unwrap();
263let sni_cb = |sni: &CStr| -> Option<Box<dyn KeyDef<KeyType = Pki>>> {
264    (sni == c_server_name.as_c_str()).then_some(Box::new(server_key_def.clone()))
265};
266
267// Just like the client, the server may also have a CN validator defined to determine whether
268// the common name of the client is acceptable. Here, we omit this validator for brevity.
269
270// Create the cryptography context. Note that you must explicitly specify whether
271// PKI certificate validation should be performed using the context builder's generics.
272let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(server_key_def.clone())
273                 .sni_key_provider(sni_cb)
274                 // Enable certificate chain validation (in case you have intermediate CAs) and set
275                 // verification depth (recommended value here is 3).
276                 .cert_chain_validation(3)
277                 .build();
278
279let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
280coap_ctx.set_pki_rpk_context(crypto_ctx);
281coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");
282
283// For error handling, you might want to register an event handler with the CoAP context.
284// Remaining code omitted for brevity, see the crate-level docs for a full example of server
285// operation.
286```
287"##
288)]
289
290/// Data structures and builders for PKI/RPK keys.
291mod key;
292/// Code specific to PKI support.
293#[cfg(feature = "dtls-pki")]
294mod pki;
295/// Code specific to RPK support.
296#[cfg(feature = "dtls-rpk")]
297mod rpk;
298
299use std::{
300    cell::RefCell,
301    ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError},
302    fmt::{Debug, Formatter},
303    marker::PhantomData,
304    ptr::NonNull,
305    rc::{Rc, Weak},
306};
307
308pub use key::*;
309use libcoap_sys::{
310    coap_context_set_pki, coap_context_t, coap_dtls_key_t, coap_dtls_pki_t, coap_new_client_session_pki, coap_proto_t,
311    coap_session_t, COAP_DTLS_PKI_SETUP_VERSION,
312};
313#[cfg(feature = "dtls-pki")]
314pub use pki::*;
315#[cfg(feature = "dtls-rpk")]
316pub use rpk::*;
317
318use crate::{
319    error::{ContextConfigurationError, SessionCreationError},
320    session::CoapSession,
321    types::CoapAddress,
322    CoapContext,
323};
324
325/// A context configuration for server-side PKI or RPK based DTLS encryption.
326#[derive(Clone, Debug)]
327pub enum ServerPkiRpkCryptoContext<'a> {
328    /// PKI based configuration.
329    #[cfg(feature = "dtls-pki")]
330    Pki(PkiRpkContext<'a, Pki>),
331    // RPK based configuration.
332    #[cfg(feature = "dtls-rpk")]
333    Rpk(PkiRpkContext<'a, Rpk>),
334}
335
336impl ServerPkiRpkCryptoContext<'_> {
337    /// Apply this cryptographic context to the given raw `coap_context_t`.
338    ///
339    /// # Errors
340    ///
341    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
342    /// function fails.
343    ///
344    /// # Safety
345    /// The provided CoAP context must be valid and must not outlive this PkiRpkContext.
346    pub(crate) unsafe fn apply_to_context(
347        &self,
348        ctx: NonNull<coap_context_t>,
349    ) -> Result<(), ContextConfigurationError> {
350        match self {
351            #[cfg(feature = "dtls-pki")]
352            ServerPkiRpkCryptoContext::Pki(v) => v.apply_to_context(ctx),
353            #[cfg(feature = "dtls-rpk")]
354            ServerPkiRpkCryptoContext::Rpk(v) => v.apply_to_context(ctx),
355        }
356    }
357}
358
359/// Marker indicating that a cryptographic context does not do TLS library-side certificate
360/// verification.
361///
362/// # Implementation details (informative, not covered by semver guarantees)
363/// A [`PkiRpkContext`] that is [`NonCertVerifying`] will set the `verify_peer_cert` field of the
364/// underlying [`coap_dtls_pki_t`] to `0`.
365pub struct NonCertVerifying;
366
367/// Marker indicating that a cryptographic context does perform TLS library-side certificate
368/// verification.
369///
370/// # Implementation details (informative, not covered by semver guarantees)
371/// A [`PkiRpkContext`] that is [`CertVerifying`] will set the `verify_peer_cert` field of the
372/// underlying [`coap_dtls_pki_t`] to `1`.
373pub struct CertVerifying;
374
375trait CertVerificationModeSealed {}
376
377/// Trait for markers that indicate whether a PKI/RPK DTLS context performs certificate validation.
378#[allow(private_bounds)]
379pub trait CertVerificationMode: CertVerificationModeSealed {}
380
381impl CertVerificationModeSealed for NonCertVerifying {}
382
383impl CertVerificationModeSealed for CertVerifying {}
384
385impl CertVerificationMode for NonCertVerifying {}
386
387impl CertVerificationMode for CertVerifying {}
388
389/// Builder for a PKI or RPK configuration context.
390pub struct PkiRpkContextBuilder<'a, KTY: KeyType, V: CertVerificationMode> {
391    ctx: PkiRpkContextInner<'a, KTY>,
392    verifying: PhantomData<V>,
393}
394
395impl<'a, KTY: KeyType> PkiRpkContextBuilder<'a, KTY, NonCertVerifying> {
396    /// Creates a new context builder with the given `key` as the default key to use.
397    ///
398    /// # Implementation details (informative, not covered by semver guarantees)
399    ///
400    /// Providing a raw public key will set `is_rpk_not_cert` to `1` in the underlying
401    /// [`coap_dtls_pki_t`] structure. `pki_key` will be set to the provided key regardless of key
402    /// type.
403    pub fn new<K: KeyDef<KeyType = KTY> + 'a>(key: K) -> Self {
404        let mut result = PkiRpkContextBuilder::<KTY, NonCertVerifying> {
405            ctx: PkiRpkContextInner {
406                raw_cfg: Box::new(coap_dtls_pki_t {
407                    version: COAP_DTLS_PKI_SETUP_VERSION as u8,
408                    verify_peer_cert: 0,
409                    check_common_ca: 0,
410                    allow_self_signed: 0,
411                    allow_expired_certs: 0,
412                    cert_chain_validation: 0,
413                    cert_chain_verify_depth: 0,
414                    check_cert_revocation: 0,
415                    allow_no_crl: 0,
416                    allow_expired_crl: 0,
417                    allow_bad_md_hash: 0,
418                    allow_short_rsa_length: 0,
419                    is_rpk_not_cert: 0,
420                    use_cid: 0,
421                    reserved: Default::default(),
422                    validate_cn_call_back: None,
423                    cn_call_back_arg: std::ptr::null_mut(),
424                    validate_sni_call_back: None,
425                    sni_call_back_arg: std::ptr::null_mut(),
426                    additional_tls_setup_call_back: None,
427                    client_sni: std::ptr::null_mut(),
428                    pki_key: key.as_raw_dtls_key(),
429                }),
430                provided_keys: vec![Box::new(key)],
431                provided_key_descriptors: vec![],
432                cn_callback: None,
433                sni_key_provider: None,
434                client_sni: None,
435            },
436            verifying: Default::default(),
437        };
438        KTY::set_key_type_defaults(result.ctx.raw_cfg.as_mut());
439        result
440    }
441}
442
443impl<KTY: KeyType, V: CertVerificationMode> PkiRpkContextBuilder<'_, KTY, V> {
444    /// Enables/disables use of DTLS connection identifiers
445    /// ([RFC 9146](https://datatracker.ietf.org/doc/html/rfc9146)) for the built context *if used
446    /// in a client-side session*.
447    ///
448    /// For server-side sessions, this setting is ignored, and connection identifiers will always be
449    /// used if supported by the underlying DTLS library.
450    ///
451    /// # Implementation details (informative, not covered by semver guarantees)
452    ///
453    /// Equivalent to setting `use_cid` in the underlying [`coap_dtls_pki_t`] structure.
454    pub fn use_cid(mut self, use_cid: bool) -> Self {
455        self.ctx.raw_cfg.use_cid = use_cid.into();
456        self
457    }
458
459    /// Sets the server name indication that should be sent to servers if the built
460    /// [`PkiRpkContext`] is used in a client-side session.
461    ///
462    /// `client_sni` should be convertible into a byte string that does not contain null bytes.
463    /// Typically, you would provide a `&str` or `String`.
464    ///
465    /// # Errors
466    ///
467    /// Will return [`NulError`] if the provided byte string contains null bytes.
468    ///
469    /// # Implementation details (informative, not covered by semver guarantees)
470    ///
471    /// Equivalent to setting `client_sni` in the underlying [`coap_dtls_pki_t`] structure.
472    ///
473    /// The provided `client_sni` will be converted into a `Box<[u8]>`, which will be owned and
474    /// stored by the built context.
475    pub fn client_sni(mut self, client_sni: impl Into<Vec<u8>>) -> Result<Self, NulError> {
476        // For some reason, client_sni is not immutable here.
477        // While I don't see any reason why libcoap would modify the string, it is not strictly
478        // forbidden for it to do so, so simply using CString::into_raw() is not an option (as it
479        // does not allow modifications to client_sni that change the length).
480        let sni = CString::new(client_sni.into())?
481            .into_bytes_with_nul()
482            .into_boxed_slice();
483        self.ctx.client_sni = Some(sni);
484        self.ctx.raw_cfg.client_sni = self.ctx.client_sni.as_mut().unwrap().as_mut_ptr() as *mut c_char;
485        Ok(self)
486    }
487}
488
489impl<KTY: KeyType> PkiRpkContextBuilder<'_, KTY, CertVerifying> {
490    /// Enables or disables checking whether both peers' certificates are signed by the same CA.
491    ///
492    /// # Implementation details (informative, not covered by semver guarantees)
493    ///
494    /// Equivalent to setting `check_common_ca` in the underlying [`coap_dtls_pki_t`] structure.
495    pub fn check_common_ca(mut self, check_common_ca: bool) -> Self {
496        self.ctx.raw_cfg.check_common_ca = check_common_ca.into();
497        self
498    }
499
500    /// Allows or disallows use of self-signed certificates by the peer.
501    ///
502    /// If `check_common_ca` has been enabled, this setting will be **ignored**.
503    ///
504    /// # Implementation details (informative, not covered by semver guarantees)
505    ///
506    /// Equivalent to setting `allow_self_signed` in the underlying [`coap_dtls_pki_t`] structure.
507    pub fn allow_self_signed(mut self, allow_self_signed: bool) -> Self {
508        self.ctx.raw_cfg.allow_self_signed = allow_self_signed.into();
509        self
510    }
511
512    /// Allows or disallows usage of expired certificates by the peer.
513    ///
514    /// # Implementation details (informative, not covered by semver guarantees)
515    ///
516    /// Equivalent to setting `allow_expired_certs` in the underlying [`coap_dtls_pki_t`] structure.
517    pub fn allow_expired_certs(mut self, allow_expired_certs: bool) -> Self {
518        self.ctx.raw_cfg.allow_expired_certs = allow_expired_certs.into();
519        self
520    }
521
522    /// Enables or disables verification of the entire certificate chain (up to
523    /// `cert_chain_verify_depth`).
524    ///
525    /// If `cert_chain_verify_depth` is `0`, certificate chain validation is disabled.
526    ///
527    /// # Implementation details (informative, not covered by semver guarantees)
528    ///
529    /// Equivalent to setting `cert_chain_verify_depth` and `cert_chain_validation` in the
530    /// underlying [`coap_dtls_pki_t`] structure.
531    pub fn cert_chain_validation(mut self, cert_chain_verify_depth: u8) -> Self {
532        self.ctx.raw_cfg.cert_chain_validation = if cert_chain_verify_depth == 0 { 0 } else { 1 };
533        self.ctx.raw_cfg.cert_chain_verify_depth = cert_chain_verify_depth;
534        self
535    }
536
537    /// Enables or disables certificate revocation checking.
538    ///
539    /// # Implementation details (informative, not covered by semver guarantees)
540    ///
541    /// Equivalent to setting `check_cert_revocation` in the underlying [`coap_dtls_pki_t`] structure.
542    pub fn check_cert_revocation(mut self, check_cert_revocation: bool) -> Self {
543        self.ctx.raw_cfg.check_cert_revocation = check_cert_revocation.into();
544        self
545    }
546
547    /// Allows or disallows disabling certificate revocation checking if a certificate does not have
548    /// a CRL.
549    ///
550    /// # Implementation details (informative, not covered by semver guarantees)
551    ///
552    /// Equivalent to setting `allow_no_crl` in the underlying [`coap_dtls_pki_t`] structure.
553    pub fn allow_no_crl(mut self, allow_no_crl: bool) -> Self {
554        self.ctx.raw_cfg.allow_no_crl = allow_no_crl.into();
555        self
556    }
557
558    /// Allows or disallows disabling certificate revocation checking if a certificate has an
559    /// expired CRL.
560    ///
561    /// # Implementation details (informative, not covered by semver guarantees)
562    ///
563    /// Equivalent to setting `allow_expired_crl` in the underlying [`coap_dtls_pki_t`] structure.
564    pub fn allow_expired_crl(mut self, allow_expired_crl: bool) -> Self {
565        self.ctx.raw_cfg.allow_expired_crl = allow_expired_crl.into();
566        self
567    }
568
569    /// Allows or disallows use of unsupported MD hashes.
570    ///
571    /// # Implementation details (informative, not covered by semver guarantees)
572    ///
573    /// Equivalent to setting `allow_bad_md_hash` in the underlying [`coap_dtls_pki_t`] structure.
574    pub fn allow_bad_md_hash(mut self, allow_bad_md_hash: bool) -> Self {
575        self.ctx.raw_cfg.allow_bad_md_hash = allow_bad_md_hash.into();
576        self
577    }
578
579    /// Allows or disallows small RSA key sizes.
580    ///
581    /// # Implementation details (informative, not covered by semver guarantees)
582    ///
583    /// Equivalent to setting `allow_short_rsa_length` in the underlying [`coap_dtls_pki_t`] structure.
584    pub fn allow_short_rsa_length(mut self, allow_short_rsa_length: bool) -> Self {
585        self.ctx.raw_cfg.allow_short_rsa_length = allow_short_rsa_length.into();
586        self
587    }
588}
589
590impl<'a, KTY: KeyType, V: CertVerificationMode> PkiRpkContextBuilder<'a, KTY, V> {
591    /// Sets the key provider that provides keys for a SNI provided by a client (only used in
592    /// server-side operation).
593    ///
594    /// # Implementation details (informative, not covered by semver guarantees)
595    ///
596    /// Setting a `sni_key_provider` will set the `validate_sni_call_back` of the underlying
597    /// [`coap_dtls_pki_t`] to a wrapper function, which will then call the key provider.
598    ///
599    /// Keys returned by the key provider will be stored in the context for at least as long as they
600    /// are used by the respective session.
601    pub fn sni_key_provider(mut self, sni_key_provider: impl PkiRpkSniKeyProvider<KTY> + 'a) -> Self {
602        self.ctx.sni_key_provider = Some(Box::new(sni_key_provider));
603        self.ctx.raw_cfg.validate_sni_call_back = Some(dtls_pki_sni_callback::<KTY>);
604        self
605    }
606
607    /// Builds the configured `PkiRpkContext` by consuming this builder.
608    pub fn build(self) -> PkiRpkContext<'a, KTY> {
609        let ctx = Rc::new(RefCell::new(self.ctx));
610        {
611            let mut ctx_borrow = ctx.borrow_mut();
612            if ctx_borrow.raw_cfg.validate_cn_call_back.is_some() {
613                ctx_borrow.raw_cfg.cn_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void;
614            }
615            if ctx_borrow.raw_cfg.validate_sni_call_back.is_some() {
616                ctx_borrow.raw_cfg.sni_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void;
617            }
618        }
619        PkiRpkContext { inner: ctx }
620    }
621}
622
623/// Inner structure of a [`PkiRpkContext`].
624struct PkiRpkContextInner<'a, KTY: KeyType> {
625    /// Raw PKI/RPK configuration structure.
626    raw_cfg: Box<coap_dtls_pki_t>,
627    /// Store for key definitions that we provided in previous callback invocations.
628    provided_keys: Vec<Box<dyn KeyDef<KeyType = KTY> + 'a>>,
629    /// Store for raw key definitions provided to libcoap.
630    ///
631    /// The stored raw pointers must all have been created by a call to `Box::into_raw`, and must
632    /// remain valid as long as the respective session is still active.
633    ///
634    /// Using `Vec<coap_dtls_key_t>` instead is not an option, as a Vec resize may cause the
635    /// instances to be moved to a different place in memory, invalidating pointers provided to
636    /// libcoap.
637    provided_key_descriptors: Vec<*mut coap_dtls_key_t>,
638    /// User-provided CN callback that should be wrapped (either a PKI CN callback or a RPK public
639    /// key validator).
640    cn_callback: Option<CnCallback<'a>>,
641    /// User-provided SNI key provider.
642    sni_key_provider: Option<Box<dyn PkiRpkSniKeyProvider<KTY> + 'a>>,
643    /// Byte string that client-side sessions using this context should send as SNI.
644    ///
645    /// Is referenced in raw_cfg and must therefore not be mutated for the lifetime of this context.
646    client_sni: Option<Box<[u8]>>,
647}
648
649impl<KTY: KeyType> Debug for PkiRpkContextInner<'_, KTY> {
650    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
651        f.debug_struct("PkiContextInner")
652            .field(
653                "raw_cfg",
654                &format!("(does not implement Debug), address: {:p}", self.raw_cfg),
655            )
656            .field("provided_keys", &self.provided_keys)
657            .field(
658                "provided_key_descriptors",
659                &format!(
660                    "(values do not implement Debug), length: {}",
661                    self.provided_key_descriptors.len()
662                ),
663            )
664            .field("cn_callback", &"(value does not implement Debug)")
665            .field("sni_key_provider", &"(value does not implement Debug)")
666            .field("client_sni", &self.client_sni)
667            .finish()
668    }
669}
670
671impl<KTY: KeyType> Drop for PkiRpkContextInner<'_, KTY> {
672    fn drop(&mut self) {
673        for key_ref in std::mem::take(&mut self.provided_key_descriptors).into_iter() {
674            // SAFETY: If the inner context is dropped, this implies that the pointers returned in
675            // previous callbacks are no longer used (because of the contracts of apply_to_context()
676            // and create_raw_session()). We can therefore restore and drop these values without
677            // breaking the aliasing rules.
678            unsafe {
679                drop(Box::from_raw(key_ref));
680            }
681        }
682        if !self.raw_cfg.cn_call_back_arg.is_null() {
683            // SAFETY: If we set this, it must have been using a call to `Weak::into_raw` with the
684            //         correct type, otherwise, the value will always be null.
685            unsafe {
686                Weak::from_raw(self.raw_cfg.cn_call_back_arg as *mut RefCell<Self>);
687            }
688        }
689        if !self.raw_cfg.sni_call_back_arg.is_null() {
690            // SAFETY: If we set this, it must have been using a call to `Weak::into_raw` with the
691            //         correct type, otherwise, the value will always be null.
692            unsafe {
693                Weak::from_raw(self.raw_cfg.sni_call_back_arg as *mut RefCell<Self>);
694            }
695        }
696    }
697}
698
699/// A configuration context for PKI or RPK based DTLS operation.
700///
701/// Whether PKI or RPK is configured for this context is indicated by the `KTY` generic, which may
702/// either be [`Pki`] or [`Rpk`].
703#[derive(Clone, Debug)]
704pub struct PkiRpkContext<'a, KTY: KeyType> {
705    /// Inner structure that is referenced by this context.
706    inner: Rc<RefCell<PkiRpkContextInner<'a, KTY>>>,
707}
708
709impl<KTY: KeyType> PkiRpkContext<'_, KTY> {
710    /// Creates a raw [`coap_session_t`] that is bound and uses this encryption context.
711    ///
712    /// # Safety
713    ///
714    /// This PkiRpkContext must outlive the returned [`coap_session_t`].
715    pub(crate) unsafe fn create_raw_session(
716        &self,
717        ctx: &mut CoapContext<'_>,
718        addr: &CoapAddress,
719        proto: coap_proto_t,
720    ) -> Result<NonNull<coap_session_t>, SessionCreationError> {
721        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null,
722        // raw_cfg is of valid format (as constructed by the builder).
723        {
724            let mut inner = (*self.inner).borrow_mut();
725            NonNull::new(unsafe {
726                coap_new_client_session_pki(
727                    ctx.as_mut_raw_context(),
728                    std::ptr::null(),
729                    addr.as_raw_address(),
730                    proto,
731                    inner.raw_cfg.as_mut(),
732                )
733            })
734            .ok_or(SessionCreationError::Unknown)
735        }
736    }
737
738    /// Configures the provided raw [`coap_context_t`] to use this encryption context for RPK or PKI
739    /// based server-side operation.
740    ///
741    /// # Errors
742    ///
743    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
744    /// function fails.
745    ///
746    /// # Safety
747    ///
748    /// The provided CoAP context must be valid and must not outlive this [`PkiRpkContext`].
749    unsafe fn apply_to_context(&self, mut ctx: NonNull<coap_context_t>) -> Result<(), ContextConfigurationError> {
750        let mut inner = self.inner.borrow_mut();
751        // SAFETY: context is valid as per caller contract, raw_cfg is a valid configuration as
752        // ensured by the builder.
753        match unsafe { coap_context_set_pki(ctx.as_mut(), inner.raw_cfg.as_mut()) } {
754            1 => Ok(()),
755            _ => Err(ContextConfigurationError::Unknown),
756        }
757    }
758}
759
760impl<'a, KTY: KeyType> PkiRpkContext<'a, KTY> {
761    /// Wrapper function for the user-provided CN callback.
762    ///
763    /// Calls the user-provided CN callback and converts its return value into the integer values
764    /// libcoap expects.
765    // cn and depth are unused only if dtls-pki feature is not enabled
766    #[cfg_attr(not(feature = "dtls-pki"), allow(unused_variables))]
767    fn cn_callback(
768        &self,
769        cn: &CStr,
770        asn1_public_cert: &[u8],
771        session: &CoapSession,
772        depth: c_uint,
773        validated: bool,
774    ) -> c_int {
775        let inner = (*self.inner).borrow();
776        // This function is only ever called if a CN key provider is set, so it's fine to unwrap
777        // here.
778        if match inner.cn_callback.as_ref().unwrap() {
779            #[cfg(feature = "dtls-pki")]
780            CnCallback::Pki(pki) => pki.validate_cn(cn, asn1_public_cert, session, depth, validated),
781            #[cfg(feature = "dtls-rpk")]
782            CnCallback::Rpk(rpk) => rpk.validate_rpk(asn1_public_cert, session, validated),
783        } {
784            1
785        } else {
786            0
787        }
788    }
789
790    /// Wrapper function for the user-provided SNI callback.
791    ///
792    /// Stores the returned key in a way that ensures it is accessible for libcoap for the lifetime
793    /// of this encryption context.  
794    ///
795    /// **Important:** After the underlying [`PkiRpkContextInner`] is dropped, the returned
796    /// pointer will no longer be valid and should no longer be dereferenced.
797    fn sni_callback(&self, sni: &CStr) -> *mut coap_dtls_key_t {
798        let mut inner = self.inner.borrow_mut();
799        // This function is only ever called if an SNI key provider is set, so it's fine to unwrap
800        // here.
801        let key = inner.sni_key_provider.as_ref().unwrap().key_for_sni(sni);
802        if let Some(key) = key {
803            let key_ref = Box::into_raw(Box::new(key.as_raw_dtls_key()));
804            inner.provided_keys.push(key);
805            inner.provided_key_descriptors.push(key_ref);
806            key_ref
807        } else {
808            std::ptr::null_mut()
809        }
810    }
811
812    /// Restores a [`PkiRpkContext`] from a pointer to its inner structure (i.e. from the
813    /// user-provided pointer given to DTLS callbacks).
814    ///
815    /// # Panics
816    ///
817    /// Panics if the given pointer is a null pointer or the inner structure was already dropped.
818    ///
819    /// # Safety
820    /// The provided pointer must be a valid reference to a [`RefCell<PkiRpkContextInner<KTY>>`]
821    /// instance created from a call to [`Weak::into_raw()`].
822    unsafe fn from_raw(raw_ctx: *const RefCell<PkiRpkContextInner<'a, KTY>>) -> Self {
823        assert!(!raw_ctx.is_null(), "provided raw DTLS PKI context was null");
824        let inner_weak = Weak::from_raw(raw_ctx);
825        let inner = inner_weak
826            .upgrade()
827            .expect("provided DTLS PKI context was already dropped!");
828        let _ = Weak::into_raw(inner_weak);
829        PkiRpkContext { inner }
830    }
831}
832
833/// User-provided CN callback.
834///
835/// Depending on whether the encryption context is configured for RPK or PKI operation, the callback
836/// will be either a [`PkiCnValidator`] or a [`RpkValidator`].
837enum CnCallback<'a> {
838    /// CN callback for PKI based configuration.
839    #[cfg(feature = "dtls-pki")]
840    Pki(Box<dyn PkiCnValidator + 'a>),
841    /// CN callback for RPK based configuration.
842    #[cfg(feature = "dtls-rpk")]
843    Rpk(Box<dyn RpkValidator + 'a>),
844}
845
846/// Trait for things that can provide RPK/PKI DTLS keys for a given Server Name Indication.
847pub trait PkiRpkSniKeyProvider<KTY: KeyType> {
848    /// Provide a key for the server name indication given as `sni`, or `None` if the SNI is not
849    /// valid and no key is available.
850    ///
851    /// Note that libcoap will remember the returned key and re-use it for future handshakes with
852    /// the same SNI (even if the peer is not the same), the return value should therefore not
853    /// depend on the provided `session`.
854    fn key_for_sni(&self, sni: &CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>>;
855}
856
857impl<KTY: KeyType, T: Fn(&CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>>> PkiRpkSniKeyProvider<KTY> for T {
858    fn key_for_sni(&self, sni: &CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>> {
859        self(sni)
860    }
861}
862
863/// Raw CN callback that can be provided to libcoap.
864///
865/// # Safety
866///
867/// This function expects the arguments to be provided in a way that libcoap would when invoking
868/// this function as a CN callback.
869///
870/// Additionally, `session` must be a valid argument to [`CoapSession::from_raw`], and `arg` must be
871/// a valid argument to [`PkiRpkContext::from_raw`] (where the key type of `PkiRpkContext` matches
872/// the key type of this function).
873unsafe extern "C" fn dtls_pki_cn_callback<KTY: KeyType>(
874    cn: *const c_char,
875    asn1_public_cert: *const u8,
876    asn1_length: usize,
877    session: *mut coap_session_t,
878    depth: c_uint,
879    validated: c_int,
880    arg: *mut c_void,
881) -> c_int {
882    let session = CoapSession::from_raw(session);
883    let cn = CStr::from_ptr(cn);
884    let asn1_public_cert = std::slice::from_raw_parts(asn1_public_cert, asn1_length);
885    let validated = validated == 1;
886    let context = PkiRpkContext::from_raw(arg as *const RefCell<PkiRpkContextInner<KTY>>);
887    context.cn_callback(cn, asn1_public_cert, &session, depth, validated)
888}
889
890/// Raw PKI/RPK SNI callback that can be provided to libcoap.
891///
892/// # Safety
893///
894/// This function expects the arguments to be provided in a way that libcoap would when invoking
895/// this function as an PKI/RPK SNI callback.
896///
897/// Additionally, `arg` must be a valid argument to [`PkiRpkContext::from_raw`] (where the key type
898/// of `PkiRpkContext` matches the key type of this function).
899unsafe extern "C" fn dtls_pki_sni_callback<KTY: KeyType>(sni: *const c_char, arg: *mut c_void) -> *mut coap_dtls_key_t {
900    let sni = CStr::from_ptr(sni);
901    let context = PkiRpkContext::from_raw(arg as *const RefCell<PkiRpkContextInner<KTY>>);
902    context.sni_callback(sni)
903}