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##"
37
Creating and connecting a client-side session with DTLS RPK configured:
38
```no_run
39
use libcoap_rs::CoapContext;
40
use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef};
41
use 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.
45
let client_private_key = Vec::from(include_str!("../../../resources/test-keys/client/client.key.pem"));
46
let client_public_key = Vec::from(include_str!("../../../resources/test-keys/client/client.pub.pem"));
47

            
48
// Create key definition.
49
let 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.
56
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(client_key_def);
57

            
58
let 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.
79
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
80

            
81
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
82
let 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

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

            
99
fn 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.
117
let server_private_key = Vec::from(include_str!("../../../resources/test-keys/server/server.key.pem"));
118
let server_public_key = Vec::from(include_str!("../../../resources/test-keys/server/server.pub.pem"));
119

            
120
// Create key definition.
121
let 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.
128
let crypto_ctx = PkiRpkContextBuilder::<_, NonCertVerifying>::new(server_key_def);
129
// Set the RPK validator and build the context.
130
let crypto_ctx = crypto_ctx.rpk_validator(key_validator).build();
131

            
132
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
133
coap_ctx.set_pki_rpk_context(crypto_ctx);
134
coap_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

            
146
Creating and connecting a client-side session with DTLS PKI configured:
147
```no_run
148
use std::ffi::{c_uint, CStr};
149
use std::net::SocketAddr;
150
use libcoap_rs::CoapContext;
151
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder};
152
use libcoap_rs::session::{CoapClientSession, CoapSession};
153
use 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).
159
let client_private_key = "../../../resources/test-keys/client/client.key.pem";
160
let 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.
168
let 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.
174
let 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.
184
let c_server_name = CString::new(server_name).unwrap();
185
let 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.
202
let 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

            
213
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
214
let 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

            
228
Creating a server that supports DTLS RPK configured:
229
```no_run
230
use std::ffi::{c_uint, CStr};
231
use std::net::SocketAddr;
232
use libcoap_rs::CoapContext;
233
use libcoap_rs::crypto::pki_rpk::{CertVerifying, PkiKeyDef, PkiRpkContextBuilder, KeyDef, Pki};
234
use libcoap_rs::session::{CoapClientSession, CoapSession};
235
use 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).
241
let server_private_key = "../../../resources/test-keys/server/server.key.pem";
242
let server_public_cert = "../../../resources/test-keys/server/server.crt.pem";
243
let 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.
250
let 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.
253
let 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.
262
let c_server_name = CString::new(server_name).unwrap();
263
let 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.
272
let 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

            
279
let mut coap_ctx = CoapContext::new().expect("unable to create CoAP context");
280
coap_ctx.set_pki_rpk_context(crypto_ctx);
281
coap_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.
291
mod key;
292
/// Code specific to PKI support.
293
#[cfg(feature = "dtls-pki")]
294
mod pki;
295
/// Code specific to RPK support.
296
#[cfg(feature = "dtls-rpk")]
297
mod rpk;
298

            
299
use 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

            
308
pub use key::*;
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
#[cfg(feature = "dtls-pki")]
314
pub use pki::*;
315
#[cfg(feature = "dtls-rpk")]
316
pub use rpk::*;
317

            
318
use 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)]
327
pub 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

            
336
impl 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
128
    pub(crate) unsafe fn apply_to_context(
347
128
        &self,
348
128
        ctx: NonNull<coap_context_t>,
349
128
    ) -> Result<(), ContextConfigurationError> {
350
128
        match self {
351
92
            #[cfg(feature = "dtls-pki")]
352
119
            ServerPkiRpkCryptoContext::Pki(v) => v.apply_to_context(ctx),
353
92
            #[cfg(feature = "dtls-rpk")]
354
101
            ServerPkiRpkCryptoContext::Rpk(v) => v.apply_to_context(ctx),
355
92
        }
356
128
    }
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`.
365
pub 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`.
373
pub struct CertVerifying;
374

            
375
trait CertVerificationModeSealed {}
376

            
377
/// Trait for markers that indicate whether a PKI/RPK DTLS context performs certificate validation.
378
#[allow(private_bounds)]
379
pub trait CertVerificationMode: CertVerificationModeSealed {}
380

            
381
impl CertVerificationModeSealed for NonCertVerifying {}
382

            
383
impl CertVerificationModeSealed for CertVerifying {}
384

            
385
impl CertVerificationMode for NonCertVerifying {}
386

            
387
impl CertVerificationMode for CertVerifying {}
388

            
389
/// Builder for a PKI or RPK configuration context.
390
pub struct PkiRpkContextBuilder<'a, KTY: KeyType, V: CertVerificationMode> {
391
    ctx: PkiRpkContextInner<'a, KTY>,
392
    verifying: PhantomData<V>,
393
}
394

            
395
impl<'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
26
    pub fn new<K: KeyDef<KeyType = KTY> + 'a>(key: K) -> Self {
404
26
        let mut result = PkiRpkContextBuilder::<KTY, NonCertVerifying> {
405
26
            ctx: PkiRpkContextInner {
406
26
                raw_cfg: Box::new(coap_dtls_pki_t {
407
26
                    version: COAP_DTLS_PKI_SETUP_VERSION as u8,
408
26
                    verify_peer_cert: 0,
409
26
                    check_common_ca: 0,
410
26
                    allow_self_signed: 0,
411
26
                    allow_expired_certs: 0,
412
26
                    cert_chain_validation: 0,
413
26
                    cert_chain_verify_depth: 0,
414
26
                    check_cert_revocation: 0,
415
26
                    allow_no_crl: 0,
416
26
                    allow_expired_crl: 0,
417
26
                    allow_bad_md_hash: 0,
418
26
                    allow_short_rsa_length: 0,
419
26
                    is_rpk_not_cert: 0,
420
26
                    use_cid: 0,
421
26
                    reserved: Default::default(),
422
26
                    validate_cn_call_back: None,
423
26
                    cn_call_back_arg: std::ptr::null_mut(),
424
26
                    validate_sni_call_back: None,
425
26
                    sni_call_back_arg: std::ptr::null_mut(),
426
26
                    additional_tls_setup_call_back: None,
427
26
                    client_sni: std::ptr::null_mut(),
428
26
                    pki_key: key.as_raw_dtls_key(),
429
26
                }),
430
26
                provided_keys: vec![Box::new(key)],
431
26
                provided_key_descriptors: vec![],
432
26
                cn_callback: None,
433
26
                sni_key_provider: None,
434
26
                client_sni: None,
435
26
            },
436
26
            verifying: Default::default(),
437
26
        };
438
26
        KTY::set_key_type_defaults(result.ctx.raw_cfg.as_mut());
439
26
        result
440
26
    }
441
}
442

            
443
impl<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

            
489
impl<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
22
    pub fn check_common_ca(mut self, check_common_ca: bool) -> Self {
496
22
        self.ctx.raw_cfg.check_common_ca = check_common_ca.into();
497
22
        self
498
22
    }
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

            
590
impl<'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
26
    pub fn build(self) -> PkiRpkContext<'a, KTY> {
609
26
        let ctx = Rc::new(RefCell::new(self.ctx));
610
26
        {
611
26
            let mut ctx_borrow = ctx.borrow_mut();
612
26
            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
26
            }
615
26
            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
26
            }
618
        }
619
26
        PkiRpkContext { inner: ctx }
620
26
    }
621
}
622

            
623
/// Inner structure of a [`PkiRpkContext`].
624
struct 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

            
649
impl<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

            
671
impl<KTY: KeyType> Drop for PkiRpkContextInner<'_, KTY> {
672
256
    fn drop(&mut self) {
673
256
        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
256
        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
256
        }
689
256
        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
256
        }
696
256
    }
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)]
704
pub struct PkiRpkContext<'a, KTY: KeyType> {
705
    /// Inner structure that is referenced by this context.
706
    inner: Rc<RefCell<PkiRpkContextInner<'a, KTY>>>,
707
}
708

            
709
impl<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
13
    pub(crate) unsafe fn create_raw_session(
716
13
        &self,
717
13
        ctx: &mut CoapContext<'_>,
718
13
        addr: &CoapAddress,
719
13
        proto: coap_proto_t,
720
13
    ) -> Result<NonNull<coap_session_t>, SessionCreationError> {
721
13
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null,
722
13
        // raw_cfg is of valid format (as constructed by the builder).
723
13
        {
724
13
            let mut inner = (*self.inner).borrow_mut();
725
13
            NonNull::new(unsafe {
726
13
                coap_new_client_session_pki(
727
13
                    ctx.as_mut_raw_context(),
728
13
                    std::ptr::null(),
729
13
                    addr.as_raw_address(),
730
13
                    proto,
731
13
                    inner.raw_cfg.as_mut(),
732
13
                )
733
13
            })
734
13
            .ok_or(SessionCreationError::Unknown)
735
13
        }
736
13
    }
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
128
    unsafe fn apply_to_context(&self, mut ctx: NonNull<coap_context_t>) -> Result<(), ContextConfigurationError> {
750
128
        let mut inner = self.inner.borrow_mut();
751
128
        // SAFETY: context is valid as per caller contract, raw_cfg is a valid configuration as
752
128
        // ensured by the builder.
753
128
        match unsafe { coap_context_set_pki(ctx.as_mut(), inner.raw_cfg.as_mut()) } {
754
128
            1 => Ok(()),
755
            _ => Err(ContextConfigurationError::Unknown),
756
        }
757
128
    }
758
}
759

            
760
impl<'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`].
837
enum 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.
847
pub 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

            
857
impl<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).
873
unsafe 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).
899
unsafe 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
}