Skip to main content

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