1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * crypto/pki_rpk/mod.rs - Interfaces and types for PKI/RPK support in libcoap-rs.
4
 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5
 * more information and terms of use.
6
 * Copyright © 2021-2024 The NAMIB Project Developers, all rights reserved.
7
 * See the README as well as the LICENSE file for more information.
8
 */
9
//! Types and traits related to (D)TLS with raw public keys and/or public key infrastructure support
10
//! for CoAP.
11
//!
12
//! In order to configure PKI and/or RPK support, the following general steps need to be followed:
13
//! 1. Create a key definition for the desired DTLS variant, see [`PkiKeyDef`](pki_rpk::PkiKeyDef)
14
//!    and [`RpkKeyDef`](pki_rpk::RpkKeyDef) for more detailed information.
15
//! 2. Create a [`PkiRpkContextBuilder`](pki_rpk::PkiRpkContextBuilder) using the provided key and
16
//!    (optionally) make some additional configuration changes (see the builder struct
17
//!    documentation).
18
//! 3. Call [`PkiRpkContextBuilder::build`](pki_rpk::PkiRpkContextBuilder::build) to create a
19
//!    [`PkiRpkContext`](pki_rpk::PkiRpkContext).
20
//! 4. Provide the created context to [`CoapClientSession::connect_dtls`](crate::session::CoapClientSession::connect_dtls)
21
//!    (for client-side sessions) or [`CoapContext::set_pki_rpk_context`](crate::CoapContext::set_pki_rpk_context)
22
//!    (for server-side sessions).
23
//! 5. On servers, run [`CoapContext::add_endpoint_dtls`](crate::CoapContext::add_endpoint_dtls) to
24
//!    add a DTLS endpoint.
25
//!
26
//! Note that [`PkiRpkContextBuilder`](pki_rpk::PkiRpkContextBuilder) uses generics with the marker
27
//! structs [`Pki`](pki_rpk::Pki) and [`Rpk`](pki_rpk::Rpk) to statically indicate the DTLS variant
28
//! and [`NonCertVerifying`](pki_rpk::NonCertVerifying) and [`CertVerifying`](pki_rpk::CertVerifying)
29
//! to indicate whether the peer certificate should be verified (PKI only, RPK will always use
30
//! [`NonCertVerifying`](pki_rpk::NonCertVerifying)).
31
//!
32
//! # Examples
33
#![cfg_attr(
34
    feature = "dtls-rpk",
35
    doc = r##"
36
Creating and connecting a client-side session with DTLS RPK configured:
37
```no_run
38
use libcoap_rs::CoapContext;
39
use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
40
use libcoap_rs::session::{CoapClientSession, CoapSession};
41

            
42
// RPK is only supported if the key is provided as a byte array in memory, providing file paths
43
// directly is unsupported.
44
let client_private_key = Vec::from(include_str!("../../../resources/test-keys/client/client.key.pem"));
45
let client_public_key = Vec::from(include_str!("../../../resources/test-keys/client/client.pub.pem"));
46

            
47
// Create key definition.
48
let client_key_def = RpkKeyDef::with_pem_memory(client_public_key, client_private_key);
49

            
50
// Create the cryptography context. Note that you might have to explicitly specify that
51
// PKI certificate validation should not be performed, even though enabling it while passing a
52
// RPK key definition is impossible due to a lack of a constructor for
53
// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
54
// This is a type system limitation.
55
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(client_key_def);
56

            
57
let key_validator = |asn1_public_key: &[u8], session: &CoapSession, validated: bool| {
58
    if !validated {
59
        false
60
    } else {
61
        // Here, you would add code to validate that the peer's public key is actually the one you
62
        // expect, and return either true (accept key) or false (reject key).
63
        // `asn1_encoded_key` should be the certificate structure defined in
64
        // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
65
        // might be able to parse with crates like
66
        // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
67
        // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
68
        // SubjectPublicKeyInformation encoded within.
69
        //
70
        // Instead of using a lambda like this, you could also implement `RpkValidator` on any
71
        // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
72
        // keys.
73
        # true
74
    }
75
};
76

            
77
// Set the RPK validator and build the context.
78
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
79

            
80
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
81
let session = CoapClientSession::connect_dtls(&mut coap_ctx, "example.com:5684".parse().unwrap(), crypto_ctx);
82

            
83
// The session might not be immediately established, but you can already create and send
84
// requests as usual after this point.
85
// To check for errors and/or disconnections, you might want to call and check the return value
86
// of `session.state()` occasionally.
87
// For error handling, you might also want to register an event handler with the CoAP context.
88
// Remaining code omitted for brevity, see the crate-level docs for a full example of client
89
// operation.
90
```
91

            
92
Creating a server that supports DTLS RPK configured:
93
```no_run
94
use libcoap_rs::CoapContext;
95
use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
96
use libcoap_rs::session::{CoapClientSession, CoapSession};
97

            
98
fn key_validator(asn1_public_key: &[u8], session: &CoapSession, validated: bool) -> bool {
99
    // Here, you would add code to validate that the peer's public key is actually the one you
100
    // expect, and return either true (accept key) or false (reject key).
101
    // `asn1_encoded_key` should be the certificate structure defined in
102
    // [RFC 7250, section 3](https://datatracker.ietf.org/doc/html/rfc7250#section-3), which you
103
    // might be able to parse with crates like
104
    // [x509-cert](https://docs.rs/x509-cert/latest/x509_cert/index.html) and
105
    // [spki](https://docs.rs/spki/0.7.3/spki/index.html) to obtain and match the
106
    // SubjectPublicKeyInformation encoded within.
107
    //
108
    // Instead of using a function like this, you could also implement `RpkValidator` on any
109
    // arbitrary type of your choice, e.g., a structure containing a storage of allowed public
110
    // keys.
111
    # true
112
}
113

            
114
// RPK is only supported if the key is provided as a byte array in memory, providing file paths
115
// directly is unsupported.
116
let server_private_key = Vec::from(include_str!("../../../resources/test-keys/server/server.key.pem"));
117
let server_public_key = Vec::from(include_str!("../../../resources/test-keys/server/server.pub.pem"));
118

            
119
// Create key definition.
120
let server_key_def = RpkKeyDef::with_pem_memory(server_public_key, server_private_key);
121

            
122
// Create the cryptography context. Note that you might have to explicitly specify that
123
// PKI certificate validation should not be performed, even though enabling it while passing a
124
// RPK key definition is impossible due to a lack of a constructor for
125
// `PkiRpkContextBuilder<Rpk, CertVerifying>`.
126
// This is a type system limitation.
127
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(server_key_def);
128
// Set the RPK validator and build the context.
129
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
130

            
131
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
132
coap_ctx.set_pki_rpk_context(crypto_ctx);
133
coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");
134

            
135
// For error handling, you might want to register an event handler with the CoAP context.
136
// Remaining code omitted for brevity, see the crate-level docs for a full example of server
137
// operation.
138
```
139
"##
140
)]
141
#![cfg_attr(
142
    feature = "dtls-pki",
143
    doc = r##"
144

            
145
Creating and connecting a client-side session with DTLS PKI configured:
146
```no_run
147
use std::ffi::{c_uint, CStr};
148
use std::net::SocketAddr;
149
use libcoap_rs::CoapContext;
150
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder};
151
use libcoap_rs::session::{CoapClientSession, CoapSession};
152
use std::ffi::CString;
153

            
154
// Paths to private key and certificate.
155
// The certificate may also contain intermediates. However, they might *not* be provided to the
156
// peer (i.e., the peer might have to already know all intermediates beforehand in order for
157
// validation to succeed).
158
let client_private_key = "../../../resources/test-keys/client/client.key.pem";
159
let client_public_cert = "../../../resources/test-keys/client/client.crt.pem";
160

            
161
// Create key definition.
162
// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
163
// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
164
// It is exclusively used to determine a list of CA names that a server will provide to a client
165
// to indicate to it which certificates it should send.
166
// For client-side operation, `ca_cert` is not used.
167
let client_key_def = PkiKeyDef::with_pem_files(
168
                        None::<String>,
169
                        client_public_cert,
170
                        client_private_key);
171

            
172
// The name of the server we want to connect to.
173
let server_name = "example.com";
174

            
175
// Validator function for certificate common names.
176
// Typically, this function should have the following behavior:
177
// - If !validated, something went wrong during TLS-level certificate checks, so reject.
178
// - If depth == 0, we are checking the client certificate, whose common name should equal the
179
//   server name we want to connect to, return the equality check result.
180
// - If depth > 0, we are checking an intermediate or root CA certificate. As we usually trust
181
//   all CAs in the trust store and validation of those is already performed by the TLS library,
182
//   always accept.
183
let c_server_name = CString::new(server_name).unwrap();
184
let cn_validator = |
185
     cn: &CStr,
186
     asn1_public_cert: &[u8],
187
     session: &CoapSession,
188
     depth: c_uint,
189
     validated: bool| {
190
    if !validated {
191
        false
192
    } else if depth == 0 {
193
        cn == c_server_name.as_c_str()
194
    } else {
195
        true
196
    }
197
};
198

            
199
// Create the cryptography context. Note that you must explicitly specify whether
200
// PKI certificate validation should be performed using the context builder's generics.
201
let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(client_key_def)
202
                 // Provide the server with a Server Name Indication (might be required by
203
                 // some servers to use the right certificate).
204
                 .client_sni(server_name).unwrap()
205
                 // Use the CN validator we defined earlier.
206
                 .cn_validator(cn_validator)
207
                 // Enable certificate chain validation (in case you have intermediate CAs) and set
208
                 // verification depth (recommended value here is 3).
209
                 .cert_chain_validation(3)
210
                 .build();
211

            
212
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
213
let session = CoapClientSession::connect_dtls(
214
             &mut coap_ctx,
215
             SocketAddr::new(server_name.parse().expect("error in name resolution"), 5684),
216
             crypto_ctx);
217

            
218
// The session might not be immediately established, but you can already create and send
219
// requests as usual after this point.
220
// To check for errors and/or disconnections, you might want to call and check the return value
221
// of `session.state()` occasionally.
222
// For error handling, you might also want to register an event handler with the CoAP context.
223
// Remaining code omitted for brevity, see the crate-level docs for a full example of client
224
// operation.
225
```
226

            
227
Creating a server that supports DTLS RPK configured:
228
```no_run
229
use std::ffi::{c_uint, CStr};
230
use std::net::SocketAddr;
231
use libcoap_rs::CoapContext;
232
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder, KeyDef, Pki};
233
use libcoap_rs::session::{CoapClientSession, CoapSession};
234
use std::ffi::CString;
235

            
236
// Paths to private key and certificate.
237
// The certificate may also contain intermediates. However, they might *not* be provided to the
238
// peer (i.e., the peer might have to already know all intermediates beforehand in order for
239
// validation to succeed).
240
let server_private_key = "../../../resources/test-keys/server/server.key.pem";
241
let server_public_cert = "../../../resources/test-keys/server/server.crt.pem";
242
let ca_cert = "../../../resources/test-keys/ca/ca.crt.pem";
243

            
244
// Create key definition.
245
// Note: the first argument (`ca_cert`) is not used to send intermediates and root certificates
246
// to the peer (unlike what you might expect if you have experience setting up HTTP servers).
247
// It is exclusively used to determine a list of CA names that a server will provide to a client
248
// to indicate to it which certificates it should send.
249
let server_key_def = PkiKeyDef::with_pem_files(Some(ca_cert), server_public_cert, server_private_key);
250

            
251
// The name of the server we use.
252
let server_name = "example.com";
253

            
254
// Key provider for Server Name Indications.
255
// If the client provides a server name using the Server Name Indication extension, this
256
// callback is called to determine the key the server should use instead of the one provided as
257
// the default to `PkiRpkContextBuilder::new`.
258
// Typically, you would want to maintain a map from potential server names to key definitions,
259
// and return either `Some(Box::new(key))` for the appropriate map entry or `None` if the server
260
// name is unknown.
261
let c_server_name = CString::new(server_name).unwrap();
262
let sni_cb = |sni: &CStr| -> Option<Box<dyn KeyDef<KeyType = Pki>>> {
263
    (sni == c_server_name.as_c_str()).then_some(Box::new(server_key_def.clone()))
264
};
265

            
266
// Just like the client, the server may also have a CN validator defined to determine whether
267
// the common name of the client is acceptable. Here, we omit this validator for brevity.
268

            
269
// Create the cryptography context. Note that you must explicitly specify whether
270
// PKI certificate validation should be performed using the context builder's generics.
271
let crypto_ctx = PkiRpkContextBuilder::<_, CertVerifying>::new(server_key_def.clone())
272
                 .sni_key_provider(sni_cb)
273
                 // Enable certificate chain validation (in case you have intermediate CAs) and set
274
                 // verification depth (recommended value here is 3).
275
                 .cert_chain_validation(3)
276
                 .build();
277

            
278
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
279
coap_ctx.set_pki_rpk_context(crypto_ctx);
280
coap_ctx.add_endpoint_dtls("[::1]:5684".parse().unwrap()).expect("unable to create DTLS endpoint");
281

            
282
// For error handling, you might want to register an event handler with the CoAP context.
283
// Remaining code omitted for brevity, see the crate-level docs for a full example of server
284
// operation.
285
```
286
"##
287
)]
288

            
289
/// Data structures and builders for PKI/RPK keys.
290
mod key;
291
/// Code specific to PKI support.
292
#[cfg(feature = "dtls-pki")]
293
mod pki;
294
/// Code specific to RPK support.
295
#[cfg(feature = "dtls-rpk")]
296
mod rpk;
297

            
298
#[cfg(feature = "dtls-pki")]
299
pub use pki::*;
300
#[cfg(feature = "dtls-rpk")]
301
pub use rpk::*;
302

            
303
pub use key::*;
304

            
305
use crate::error::{ContextConfigurationError, SessionCreationError};
306
use crate::session::CoapSession;
307
use crate::types::CoapAddress;
308
use crate::CoapContext;
309
use 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
use std::cell::RefCell;
314
use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError};
315
use std::fmt::{Debug, Formatter};
316
use std::marker::PhantomData;
317
use std::ptr::NonNull;
318
use std::rc::{Rc, Weak};
319

            
320
/// A context configuration for server-side PKI or RPK based DTLS encryption.
321
#[derive(Clone, Debug)]
322
pub enum ServerPkiRpkCryptoContext<'a> {
323
    /// PKI based configuration.
324
    #[cfg(feature = "dtls-pki")]
325
    Pki(PkiRpkContext<'a, Pki>),
326
    // RPK based configuration.
327
    #[cfg(feature = "dtls-rpk")]
328
    Rpk(PkiRpkContext<'a, Rpk>),
329
}
330

            
331
impl ServerPkiRpkCryptoContext<'_> {
332
    /// Apply this cryptographic context to the given raw `coap_context_t`.
333
    ///
334
    /// # Errors
335
    ///
336
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
337
    /// function fails.
338
    ///
339
    /// # Safety
340
    /// The provided CoAP context must be valid and must not outlive this PkiRpkContext.
341
128
    pub(crate) unsafe fn apply_to_context(
342
128
        &self,
343
128
        ctx: NonNull<coap_context_t>,
344
128
    ) -> Result<(), ContextConfigurationError> {
345
128
        match self {
346
92
            #[cfg(feature = "dtls-pki")]
347
119
            ServerPkiRpkCryptoContext::Pki(v) => v.apply_to_context(ctx),
348
92
            #[cfg(feature = "dtls-rpk")]
349
101
            ServerPkiRpkCryptoContext::Rpk(v) => v.apply_to_context(ctx),
350
92
        }
351
128
    }
352
}
353

            
354
/// Marker indicating that a cryptographic context does not do TLS library-side certificate
355
/// verification.
356
///
357
/// # Implementation details (informative, not covered by semver guarantees)
358
/// A [`PkiRpkContext`] that is [`NonCertVerifying`] will set the `verify_peer_cert` field of the
359
/// underlying [`coap_dtls_pki_t`] to `0`.
360
pub struct NonCertVerifying;
361

            
362
/// Marker indicating that a cryptographic context does perform TLS library-side certificate
363
/// verification.
364
///
365
/// # Implementation details (informative, not covered by semver guarantees)
366
/// A [`PkiRpkContext`] that is [`CertVerifying`] will set the `verify_peer_cert` field of the
367
/// underlying [`coap_dtls_pki_t`] to `1`.
368
pub struct CertVerifying;
369

            
370
trait CertVerificationModeSealed {}
371

            
372
/// Trait for markers that indicate whether a PKI/RPK DTLS context performs certificate validation.
373
#[allow(private_bounds)]
374
pub trait CertVerificationMode: CertVerificationModeSealed {}
375

            
376
impl CertVerificationModeSealed for NonCertVerifying {}
377

            
378
impl CertVerificationModeSealed for CertVerifying {}
379

            
380
impl CertVerificationMode for NonCertVerifying {}
381

            
382
impl CertVerificationMode for CertVerifying {}
383

            
384
/// Builder for a PKI or RPK configuration context.
385
pub struct PkiRpkContextBuilder<'a, KTY: KeyType, V: CertVerificationMode> {
386
    ctx: PkiRpkContextInner<'a, KTY>,
387
    verifying: PhantomData<V>,
388
}
389

            
390
impl<'a, KTY: KeyType> PkiRpkContextBuilder<'a, KTY, NonCertVerifying> {
391
    /// Creates a new context builder with the given `key` as the default key to use.
392
    ///
393
    /// # Implementation details (informative, not covered by semver guarantees)
394
    ///
395
    /// Providing a raw public key will set `is_rpk_not_cert` to `1` in the underlying
396
    /// [`coap_dtls_pki_t`] structure. `pki_key` will be set to the provided key regardless of key
397
    /// type.
398
26
    pub fn new<K: KeyDef<KeyType = KTY> + 'a>(key: K) -> Self {
399
26
        let mut result = PkiRpkContextBuilder::<KTY, NonCertVerifying> {
400
26
            ctx: PkiRpkContextInner {
401
26
                raw_cfg: Box::new(coap_dtls_pki_t {
402
26
                    version: COAP_DTLS_PKI_SETUP_VERSION as u8,
403
26
                    verify_peer_cert: 0,
404
26
                    check_common_ca: 0,
405
26
                    allow_self_signed: 0,
406
26
                    allow_expired_certs: 0,
407
26
                    cert_chain_validation: 0,
408
26
                    cert_chain_verify_depth: 0,
409
26
                    check_cert_revocation: 0,
410
26
                    allow_no_crl: 0,
411
26
                    allow_expired_crl: 0,
412
26
                    allow_bad_md_hash: 0,
413
26
                    allow_short_rsa_length: 0,
414
26
                    is_rpk_not_cert: 0,
415
26
                    use_cid: 0,
416
26
                    reserved: Default::default(),
417
26
                    validate_cn_call_back: None,
418
26
                    cn_call_back_arg: std::ptr::null_mut(),
419
26
                    validate_sni_call_back: None,
420
26
                    sni_call_back_arg: std::ptr::null_mut(),
421
26
                    additional_tls_setup_call_back: None,
422
26
                    client_sni: std::ptr::null_mut(),
423
26
                    pki_key: key.as_raw_dtls_key(),
424
26
                }),
425
26
                provided_keys: vec![Box::new(key)],
426
26
                provided_key_descriptors: vec![],
427
26
                cn_callback: None,
428
26
                sni_key_provider: None,
429
26
                client_sni: None,
430
26
            },
431
26
            verifying: Default::default(),
432
26
        };
433
26
        KTY::set_key_type_defaults(result.ctx.raw_cfg.as_mut());
434
26
        result
435
26
    }
436
}
437

            
438
impl<KTY: KeyType, V: CertVerificationMode> PkiRpkContextBuilder<'_, KTY, V> {
439
    /// Enables/disables use of DTLS connection identifiers
440
    /// ([RFC 9146](https://datatracker.ietf.org/doc/html/rfc9146)) for the built context *if used
441
    /// in a client-side session*.
442
    ///
443
    /// For server-side sessions, this setting is ignored, and connection identifiers will always be
444
    /// used if supported by the underlying DTLS library.
445
    ///
446
    /// # Implementation details (informative, not covered by semver guarantees)
447
    ///
448
    /// Equivalent to setting `use_cid` in the underlying [`coap_dtls_pki_t`] structure.
449
    pub fn use_cid(mut self, use_cid: bool) -> Self {
450
        self.ctx.raw_cfg.use_cid = use_cid.into();
451
        self
452
    }
453

            
454
    /// Sets the server name indication that should be sent to servers if the built
455
    /// [`PkiRpkContext`] is used in a client-side session.
456
    ///
457
    /// `client_sni` should be convertible into a byte string that does not contain null bytes.
458
    /// Typically, you would provide a `&str` or `String`.
459
    ///
460
    /// # Errors
461
    ///
462
    /// Will return [`NulError`] if the provided byte string contains null bytes.
463
    ///
464
    /// # Implementation details (informative, not covered by semver guarantees)
465
    ///
466
    /// Equivalent to setting `client_sni` in the underlying [`coap_dtls_pki_t`] structure.
467
    ///
468
    /// The provided `client_sni` will be converted into a `Box<[u8]>`, which will be owned and
469
    /// stored by the built context.
470
    pub fn client_sni(mut self, client_sni: impl Into<Vec<u8>>) -> Result<Self, NulError> {
471
        // For some reason, client_sni is not immutable here.
472
        // While I don't see any reason why libcoap would modify the string, it is not strictly
473
        // forbidden for it to do so, so simply using CString::into_raw() is not an option (as it
474
        // does not allow modifications to client_sni that change the length).
475
        let sni = CString::new(client_sni.into())?
476
            .into_bytes_with_nul()
477
            .into_boxed_slice();
478
        self.ctx.client_sni = Some(sni);
479
        self.ctx.raw_cfg.client_sni = self.ctx.client_sni.as_mut().unwrap().as_mut_ptr() as *mut c_char;
480
        Ok(self)
481
    }
482
}
483

            
484
impl<KTY: KeyType> PkiRpkContextBuilder<'_, KTY, CertVerifying> {
485
    /// Enables or disables checking whether both peers' certificates are signed by the same CA.
486
    ///
487
    /// # Implementation details (informative, not covered by semver guarantees)
488
    ///
489
    /// Equivalent to setting `check_common_ca` in the underlying [`coap_dtls_pki_t`] structure.
490
22
    pub fn check_common_ca(mut self, check_common_ca: bool) -> Self {
491
22
        self.ctx.raw_cfg.check_common_ca = check_common_ca.into();
492
22
        self
493
22
    }
494

            
495
    /// Allows or disallows use of self-signed certificates by the peer.
496
    ///
497
    /// If `check_common_ca` has been enabled, this setting will be **ignored**.
498
    ///
499
    /// # Implementation details (informative, not covered by semver guarantees)
500
    ///
501
    /// Equivalent to setting `allow_self_signed` in the underlying [`coap_dtls_pki_t`] structure.
502
    pub fn allow_self_signed(mut self, allow_self_signed: bool) -> Self {
503
        self.ctx.raw_cfg.allow_self_signed = allow_self_signed.into();
504
        self
505
    }
506

            
507
    /// Allows or disallows usage of expired certificates by the peer.
508
    ///
509
    /// # Implementation details (informative, not covered by semver guarantees)
510
    ///
511
    /// Equivalent to setting `allow_expired_certs` in the underlying [`coap_dtls_pki_t`] structure.
512
    pub fn allow_expired_certs(mut self, allow_expired_certs: bool) -> Self {
513
        self.ctx.raw_cfg.allow_expired_certs = allow_expired_certs.into();
514
        self
515
    }
516

            
517
    /// Enables or disables verification of the entire certificate chain (up to
518
    /// `cert_chain_verify_depth`).
519
    ///
520
    /// If `cert_chain_verify_depth` is `0`, certificate chain validation is disabled.
521
    ///
522
    /// # Implementation details (informative, not covered by semver guarantees)
523
    ///
524
    /// Equivalent to setting `cert_chain_verify_depth` and `cert_chain_validation` in the
525
    /// underlying [`coap_dtls_pki_t`] structure.
526
    pub fn cert_chain_validation(mut self, cert_chain_verify_depth: u8) -> Self {
527
        self.ctx.raw_cfg.cert_chain_validation = if cert_chain_verify_depth == 0 { 0 } else { 1 };
528
        self.ctx.raw_cfg.cert_chain_verify_depth = cert_chain_verify_depth;
529
        self
530
    }
531

            
532
    /// Enables or disables certificate revocation checking.
533
    ///
534
    /// # Implementation details (informative, not covered by semver guarantees)
535
    ///
536
    /// Equivalent to setting `check_cert_revocation` in the underlying [`coap_dtls_pki_t`] structure.
537
    pub fn check_cert_revocation(mut self, check_cert_revocation: bool) -> Self {
538
        self.ctx.raw_cfg.check_cert_revocation = check_cert_revocation.into();
539
        self
540
    }
541

            
542
    /// Allows or disallows disabling certificate revocation checking if a certificate does not have
543
    /// a CRL.
544
    ///
545
    /// # Implementation details (informative, not covered by semver guarantees)
546
    ///
547
    /// Equivalent to setting `allow_no_crl` in the underlying [`coap_dtls_pki_t`] structure.
548
    pub fn allow_no_crl(mut self, allow_no_crl: bool) -> Self {
549
        self.ctx.raw_cfg.allow_no_crl = allow_no_crl.into();
550
        self
551
    }
552

            
553
    /// Allows or disallows disabling certificate revocation checking if a certificate has an
554
    /// expired CRL.
555
    ///
556
    /// # Implementation details (informative, not covered by semver guarantees)
557
    ///
558
    /// Equivalent to setting `allow_expired_crl` in the underlying [`coap_dtls_pki_t`] structure.
559
    pub fn allow_expired_crl(mut self, allow_expired_crl: bool) -> Self {
560
        self.ctx.raw_cfg.allow_expired_crl = allow_expired_crl.into();
561
        self
562
    }
563

            
564
    /// Allows or disallows use of unsupported MD hashes.
565
    ///
566
    /// # Implementation details (informative, not covered by semver guarantees)
567
    ///
568
    /// Equivalent to setting `allow_bad_md_hash` in the underlying [`coap_dtls_pki_t`] structure.
569
    pub fn allow_bad_md_hash(mut self, allow_bad_md_hash: bool) -> Self {
570
        self.ctx.raw_cfg.allow_bad_md_hash = allow_bad_md_hash.into();
571
        self
572
    }
573

            
574
    /// Allows or disallows small RSA key sizes.
575
    ///
576
    /// # Implementation details (informative, not covered by semver guarantees)
577
    ///
578
    /// Equivalent to setting `allow_short_rsa_length` in the underlying [`coap_dtls_pki_t`] structure.
579
    pub fn allow_short_rsa_length(mut self, allow_short_rsa_length: bool) -> Self {
580
        self.ctx.raw_cfg.allow_short_rsa_length = allow_short_rsa_length.into();
581
        self
582
    }
583
}
584

            
585
impl<'a, KTY: KeyType, V: CertVerificationMode> PkiRpkContextBuilder<'a, KTY, V> {
586
    /// Sets the key provider that provides keys for a SNI provided by a client (only used in
587
    /// server-side operation).
588
    ///
589
    /// # Implementation details (informative, not covered by semver guarantees)
590
    ///
591
    /// Setting a `sni_key_provider` will set the `validate_sni_call_back` of the underlying
592
    /// [`coap_dtls_pki_t`] to a wrapper function, which will then call the key provider.
593
    ///
594
    /// Keys returned by the key provider will be stored in the context for at least as long as they
595
    /// are used by the respective session.
596
    pub fn sni_key_provider(mut self, sni_key_provider: impl PkiRpkSniKeyProvider<KTY> + 'a) -> Self {
597
        self.ctx.sni_key_provider = Some(Box::new(sni_key_provider));
598
        self.ctx.raw_cfg.validate_sni_call_back = Some(dtls_pki_sni_callback::<KTY>);
599
        self
600
    }
601

            
602
    /// Builds the configured `PkiRpkContext` by consuming this builder.
603
26
    pub fn build(self) -> PkiRpkContext<'a, KTY> {
604
26
        let ctx = Rc::new(RefCell::new(self.ctx));
605
26
        {
606
26
            let mut ctx_borrow = ctx.borrow_mut();
607
26
            if ctx_borrow.raw_cfg.validate_cn_call_back.is_some() {
608
                ctx_borrow.raw_cfg.cn_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void;
609
26
            }
610
26
            if ctx_borrow.raw_cfg.validate_sni_call_back.is_some() {
611
                ctx_borrow.raw_cfg.sni_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void;
612
26
            }
613
        }
614
26
        PkiRpkContext { inner: ctx }
615
26
    }
616
}
617

            
618
/// Inner structure of a [`PkiRpkContext`].
619
struct PkiRpkContextInner<'a, KTY: KeyType> {
620
    /// Raw PKI/RPK configuration structure.
621
    raw_cfg: Box<coap_dtls_pki_t>,
622
    /// Store for key definitions that we provided in previous callback invocations.
623
    provided_keys: Vec<Box<dyn KeyDef<KeyType = KTY> + 'a>>,
624
    /// Store for raw key definitions provided to libcoap.
625
    ///
626
    /// The stored raw pointers must all have been created by a call to `Box::into_raw`, and must
627
    /// remain valid as long as the respective session is still active.
628
    ///
629
    /// Using `Vec<coap_dtls_key_t>` instead is not an option, as a Vec resize may cause the
630
    /// instances to be moved to a different place in memory, invalidating pointers provided to
631
    /// libcoap.
632
    provided_key_descriptors: Vec<*mut coap_dtls_key_t>,
633
    /// User-provided CN callback that should be wrapped (either a PKI CN callback or a RPK public
634
    /// key validator).
635
    cn_callback: Option<CnCallback<'a>>,
636
    /// User-provided SNI key provider.
637
    sni_key_provider: Option<Box<dyn PkiRpkSniKeyProvider<KTY> + 'a>>,
638
    /// Byte string that client-side sessions using this context should send as SNI.
639
    ///
640
    /// Is referenced in raw_cfg and must therefore not be mutated for the lifetime of this context.
641
    client_sni: Option<Box<[u8]>>,
642
}
643

            
644
impl<KTY: KeyType> Debug for PkiRpkContextInner<'_, KTY> {
645
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
646
        f.debug_struct("PkiContextInner")
647
            .field(
648
                "raw_cfg",
649
                &format!("(does not implement Debug), address: {:p}", self.raw_cfg),
650
            )
651
            .field("provided_keys", &self.provided_keys)
652
            .field(
653
                "provided_key_descriptors",
654
                &format!(
655
                    "(values do not implement Debug), length: {}",
656
                    self.provided_key_descriptors.len()
657
                ),
658
            )
659
            .field("cn_callback", &"(value does not implement Debug)")
660
            .field("sni_key_provider", &"(value does not implement Debug)")
661
            .field("client_sni", &self.client_sni)
662
            .finish()
663
    }
664
}
665

            
666
impl<KTY: KeyType> Drop for PkiRpkContextInner<'_, KTY> {
667
256
    fn drop(&mut self) {
668
256
        for key_ref in std::mem::take(&mut self.provided_key_descriptors).into_iter() {
669
            // SAFETY: If the inner context is dropped, this implies that the pointers returned in
670
            // previous callbacks are no longer used (because of the contracts of apply_to_context()
671
            // and create_raw_session()). We can therefore restore and drop these values without
672
            // breaking the aliasing rules.
673
            unsafe {
674
                drop(Box::from_raw(key_ref));
675
            }
676
        }
677
256
        if !self.raw_cfg.cn_call_back_arg.is_null() {
678
            // SAFETY: If we set this, it must have been using a call to `Weak::into_raw` with the
679
            //         correct type, otherwise, the value will always be null.
680
            unsafe {
681
                Weak::from_raw(self.raw_cfg.cn_call_back_arg as *mut RefCell<Self>);
682
            }
683
256
        }
684
256
        if !self.raw_cfg.sni_call_back_arg.is_null() {
685
            // SAFETY: If we set this, it must have been using a call to `Weak::into_raw` with the
686
            //         correct type, otherwise, the value will always be null.
687
            unsafe {
688
                Weak::from_raw(self.raw_cfg.sni_call_back_arg as *mut RefCell<Self>);
689
            }
690
256
        }
691
256
    }
692
}
693

            
694
/// A configuration context for PKI or RPK based DTLS operation.
695
///
696
/// Whether PKI or RPK is configured for this context is indicated by the `KTY` generic, which may
697
/// either be [`Pki`] or [`Rpk`].
698
#[derive(Clone, Debug)]
699
pub struct PkiRpkContext<'a, KTY: KeyType> {
700
    /// Inner structure that is referenced by this context.
701
    inner: Rc<RefCell<PkiRpkContextInner<'a, KTY>>>,
702
}
703

            
704
impl<KTY: KeyType> PkiRpkContext<'_, KTY> {
705
    /// Creates a raw [`coap_session_t`] that is bound and uses this encryption context.
706
    ///
707
    /// # Safety
708
    ///
709
    /// This PkiRpkContext must outlive the returned [`coap_session_t`].
710
13
    pub(crate) unsafe fn create_raw_session(
711
13
        &self,
712
13
        ctx: &mut CoapContext<'_>,
713
13
        addr: &CoapAddress,
714
13
        proto: coap_proto_t,
715
13
    ) -> Result<NonNull<coap_session_t>, SessionCreationError> {
716
13
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null,
717
13
        // raw_cfg is of valid format (as constructed by the builder).
718
13
        {
719
13
            let mut inner = (*self.inner).borrow_mut();
720
13
            NonNull::new(unsafe {
721
13
                coap_new_client_session_pki(
722
13
                    ctx.as_mut_raw_context(),
723
13
                    std::ptr::null(),
724
13
                    addr.as_raw_address(),
725
13
                    proto,
726
13
                    inner.raw_cfg.as_mut(),
727
13
                )
728
13
            })
729
13
            .ok_or(SessionCreationError::Unknown)
730
13
        }
731
13
    }
732

            
733
    /// Configures the provided raw [`coap_context_t`] to use this encryption context for RPK or PKI
734
    /// based server-side operation.
735
    ///
736
    /// # Errors
737
    ///
738
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
739
    /// function fails.
740
    ///
741
    /// # Safety
742
    ///
743
    /// The provided CoAP context must be valid and must not outlive this [`PkiRpkContext`].
744
128
    unsafe fn apply_to_context(&self, mut ctx: NonNull<coap_context_t>) -> Result<(), ContextConfigurationError> {
745
128
        let mut inner = self.inner.borrow_mut();
746
128
        // SAFETY: context is valid as per caller contract, raw_cfg is a valid configuration as
747
128
        // ensured by the builder.
748
128
        match unsafe { coap_context_set_pki(ctx.as_mut(), inner.raw_cfg.as_mut()) } {
749
128
            1 => Ok(()),
750
            _ => Err(ContextConfigurationError::Unknown),
751
        }
752
128
    }
753
}
754

            
755
impl<'a, KTY: KeyType> PkiRpkContext<'a, KTY> {
756
    /// Wrapper function for the user-provided CN callback.
757
    ///
758
    /// Calls the user-provided CN callback and converts its return value into the integer values
759
    /// libcoap expects.
760
    // cn and depth are unused only if dtls-pki feature is not enabled
761
    #[cfg_attr(not(feature = "dtls-pki"), allow(unused_variables))]
762
    fn cn_callback(
763
        &self,
764
        cn: &CStr,
765
        asn1_public_cert: &[u8],
766
        session: &CoapSession,
767
        depth: c_uint,
768
        validated: bool,
769
    ) -> c_int {
770
        let inner = (*self.inner).borrow();
771
        // This function is only ever called if a CN key provider is set, so it's fine to unwrap
772
        // here.
773
        if match inner.cn_callback.as_ref().unwrap() {
774
            #[cfg(feature = "dtls-pki")]
775
            CnCallback::Pki(pki) => pki.validate_cn(cn, asn1_public_cert, session, depth, validated),
776
            #[cfg(feature = "dtls-rpk")]
777
            CnCallback::Rpk(rpk) => rpk.validate_rpk(asn1_public_cert, session, validated),
778
        } {
779
            1
780
        } else {
781
            0
782
        }
783
    }
784

            
785
    /// Wrapper function for the user-provided SNI callback.
786
    ///
787
    /// Stores the returned key in a way that ensures it is accessible for libcoap for the lifetime
788
    /// of this encryption context.  
789
    ///
790
    /// **Important:** After the underlying [`PkiRpkContextInner`] is dropped, the returned
791
    /// pointer will no longer be valid and should no longer be dereferenced.
792
    fn sni_callback(&self, sni: &CStr) -> *mut coap_dtls_key_t {
793
        let mut inner = self.inner.borrow_mut();
794
        // This function is only ever called if an SNI key provider is set, so it's fine to unwrap
795
        // here.
796
        let key = inner.sni_key_provider.as_ref().unwrap().key_for_sni(sni);
797
        if let Some(key) = key {
798
            let key_ref = Box::into_raw(Box::new(key.as_raw_dtls_key()));
799
            inner.provided_keys.push(key);
800
            inner.provided_key_descriptors.push(key_ref);
801
            key_ref
802
        } else {
803
            std::ptr::null_mut()
804
        }
805
    }
806

            
807
    /// Restores a [`PkiRpkContext`] from a pointer to its inner structure (i.e. from the
808
    /// user-provided pointer given to DTLS callbacks).
809
    ///
810
    /// # Panics
811
    ///
812
    /// Panics if the given pointer is a null pointer or the inner structure was already dropped.
813
    ///
814
    /// # Safety
815
    /// The provided pointer must be a valid reference to a [`RefCell<PkiRpkContextInner<KTY>>`]
816
    /// instance created from a call to [`Weak::into_raw()`].
817
    unsafe fn from_raw(raw_ctx: *const RefCell<PkiRpkContextInner<'a, KTY>>) -> Self {
818
        assert!(!raw_ctx.is_null(), "provided raw DTLS PKI context was null");
819
        let inner_weak = Weak::from_raw(raw_ctx);
820
        let inner = inner_weak
821
            .upgrade()
822
            .expect("provided DTLS PKI context was already dropped!");
823
        let _ = Weak::into_raw(inner_weak);
824
        PkiRpkContext { inner }
825
    }
826
}
827

            
828
/// User-provided CN callback.
829
///
830
/// Depending on whether the encryption context is configured for RPK or PKI operation, the callback
831
/// will be either a [`PkiCnValidator`] or a [`RpkValidator`].
832
enum CnCallback<'a> {
833
    /// CN callback for PKI based configuration.
834
    #[cfg(feature = "dtls-pki")]
835
    Pki(Box<dyn PkiCnValidator + 'a>),
836
    /// CN callback for RPK based configuration.
837
    #[cfg(feature = "dtls-rpk")]
838
    Rpk(Box<dyn RpkValidator + 'a>),
839
}
840

            
841
/// Trait for things that can provide RPK/PKI DTLS keys for a given Server Name Indication.
842
pub trait PkiRpkSniKeyProvider<KTY: KeyType> {
843
    /// Provide a key for the server name indication given as `sni`, or `None` if the SNI is not
844
    /// valid and no key is available.
845
    ///
846
    /// Note that libcoap will remember the returned key and re-use it for future handshakes with
847
    /// the same SNI (even if the peer is not the same), the return value should therefore not
848
    /// depend on the provided `session`.
849
    fn key_for_sni(&self, sni: &CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>>;
850
}
851

            
852
impl<KTY: KeyType, T: Fn(&CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>>> PkiRpkSniKeyProvider<KTY> for T {
853
    fn key_for_sni(&self, sni: &CStr) -> Option<Box<dyn KeyDef<KeyType = KTY>>> {
854
        self(sni)
855
    }
856
}
857

            
858
/// Raw CN callback that can be provided to libcoap.
859
///
860
/// # Safety
861
///
862
/// This function expects the arguments to be provided in a way that libcoap would when invoking
863
/// this function as a CN callback.
864
///
865
/// Additionally, `session` must be a valid argument to [`CoapSession::from_raw`], and `arg` must be
866
/// a valid argument to [`PkiRpkContext::from_raw`] (where the key type of `PkiRpkContext` matches
867
/// the key type of this function).
868
unsafe extern "C" fn dtls_pki_cn_callback<KTY: KeyType>(
869
    cn: *const c_char,
870
    asn1_public_cert: *const u8,
871
    asn1_length: usize,
872
    session: *mut coap_session_t,
873
    depth: c_uint,
874
    validated: c_int,
875
    arg: *mut c_void,
876
) -> c_int {
877
    let session = CoapSession::from_raw(session);
878
    let cn = CStr::from_ptr(cn);
879
    let asn1_public_cert = std::slice::from_raw_parts(asn1_public_cert, asn1_length);
880
    let validated = validated == 1;
881
    let context = PkiRpkContext::from_raw(arg as *const RefCell<PkiRpkContextInner<KTY>>);
882
    context.cn_callback(cn, asn1_public_cert, &session, depth, validated)
883
}
884

            
885
/// Raw PKI/RPK SNI callback that can be provided to libcoap.
886
///
887
/// # Safety
888
///
889
/// This function expects the arguments to be provided in a way that libcoap would when invoking
890
/// this function as an PKI/RPK SNI callback.
891
///
892
/// Additionally, `arg` must be a valid argument to [`PkiRpkContext::from_raw`] (where the key type
893
/// of `PkiRpkContext` matches the key type of this function).
894
unsafe extern "C" fn dtls_pki_sni_callback<KTY: KeyType>(sni: *const c_char, arg: *mut c_void) -> *mut coap_dtls_key_t {
895
    let sni = CStr::from_ptr(sni);
896
    let context = PkiRpkContext::from_raw(arg as *const RefCell<PkiRpkContextInner<KTY>>);
897
    context.sni_callback(sni)
898
}