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

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

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

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

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

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

            
319
use 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)]
328
pub 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

            
337
impl 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
37
    pub(crate) unsafe fn apply_to_context(
348
37
        &self,
349
37
        ctx: NonNull<coap_context_t>,
350
37
    ) -> Result<(), ContextConfigurationError> {
351
37
        match self {
352
            #[cfg(feature = "dtls-pki")]
353
31
            ServerPkiRpkCryptoContext::Pki(v) => v.apply_to_context(ctx),
354
            #[cfg(feature = "dtls-rpk")]
355
6
            ServerPkiRpkCryptoContext::Rpk(v) => v.apply_to_context(ctx),
356
        }
357
37
    }
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`.
366
pub 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`.
374
pub struct CertVerifying;
375

            
376
trait CertVerificationModeSealed {}
377

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

            
382
impl CertVerificationModeSealed for NonCertVerifying {}
383

            
384
impl CertVerificationModeSealed for CertVerifying {}
385

            
386
impl CertVerificationMode for NonCertVerifying {}
387

            
388
impl CertVerificationMode for CertVerifying {}
389

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

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

            
444
impl<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
13
    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
13
        let sni = CString::new(client_sni.into())?
482
13
            .into_bytes_with_nul()
483
13
            .into_boxed_slice();
484
13
        self.ctx.client_sni = Some(sni);
485
13
        self.ctx.raw_cfg.client_sni = self.ctx.client_sni.as_mut().unwrap().as_mut_ptr() as *mut c_char;
486
13
        Ok(self)
487
13
    }
488
}
489

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

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

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

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

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

            
710
impl<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
13
    pub(crate) unsafe fn create_raw_session(
717
13
        &self,
718
13
        ctx: &mut CoapContext<'_>,
719
13
        addr: &CoapAddress,
720
13
        proto: coap_proto_t,
721
13
    ) -> 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
13
            let mut inner = (*self.inner).borrow_mut();
726
13
            NonNull::new(unsafe {
727
13
                coap_new_client_session_pki(
728
13
                    ctx.as_mut_raw_context(),
729
13
                    std::ptr::null(),
730
13
                    addr.as_raw_address(),
731
13
                    proto,
732
13
                    inner.raw_cfg.as_mut(),
733
                )
734
            })
735
13
            .ok_or(SessionCreationError::Unknown)
736
        }
737
13
    }
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
128
    unsafe fn apply_to_context(&self, mut ctx: NonNull<coap_context_t>) -> Result<(), ContextConfigurationError> {
751
128
        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
128
        match unsafe { coap_context_set_pki(ctx.as_mut(), inner.raw_cfg.as_mut()) } {
755
128
            1 => Ok(()),
756
            _ => Err(ContextConfigurationError::Unknown),
757
        }
758
128
    }
759
}
760

            
761
impl<'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`].
838
enum 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.
848
pub 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

            
858
impl<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).
874
unsafe 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).
900
unsafe 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
}