1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * crypto/psk/server.rs - Interfaces and types for server-side PSK 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

            
10
use crate::crypto::psk::key::PskKey;
11
use crate::error::ContextConfigurationError;
12
use crate::session::CoapServerSession;
13
use libcoap_sys::{
14
    coap_bin_const_t, coap_context_set_psk2, coap_context_t, coap_dtls_spsk_info_t, coap_dtls_spsk_t, coap_session_t,
15
    COAP_DTLS_SPSK_SETUP_VERSION,
16
};
17
use std::borrow::Borrow;
18
use std::cell::RefCell;
19
use std::collections::{BTreeMap, HashMap};
20
use std::ffi::{c_void, CStr};
21
use std::fmt::Debug;
22
use std::hash::Hash;
23
use std::os::raw::c_char;
24
use std::ptr::NonNull;
25
use std::rc::{Rc, Weak};
26

            
27
/// Builder for a server-side DTLS encryption context for use with pre-shared keys (PSK).
28
#[derive(Debug)]
29
pub struct ServerPskContextBuilder<'a> {
30
    ctx: ServerPskContextInner<'a>,
31
}
32

            
33
impl<'a> ServerPskContextBuilder<'a> {
34
    /// Creates a new context builder with the given `key` as the default key to use.
35
    ///
36
    /// # Implementation details (informative, not covered by semver guarantees)
37
    ///
38
    /// Providing a raw public key will set `psk_info` to the provided key in the underlying
39
    /// [`coap_dtls_spsk_t`] structure.
40
35
    pub fn new(key: PskKey<'a>) -> Self {
41
35
        Self {
42
35
            ctx: ServerPskContextInner {
43
35
                id_key_provider: None,
44
35
                sni_key_provider: None,
45
35
                provided_keys: Vec::new(),
46
35
                raw_cfg: Box::new(coap_dtls_spsk_t {
47
35
                    version: COAP_DTLS_SPSK_SETUP_VERSION as u8,
48
35
                    reserved: Default::default(),
49
35
                    #[cfg(dtls_ec_jpake_support)]
50
35
                    ec_jpake: 0,
51
35
                    validate_id_call_back: None,
52
35
                    id_call_back_arg: std::ptr::null_mut(),
53
35
                    validate_sni_call_back: None,
54
35
                    sni_call_back_arg: std::ptr::null_mut(),
55
35
                    psk_info: key.into_raw_spsk_info(),
56
35
                }),
57
35
            },
58
35
        }
59
35
    }
60

            
61
    /// Sets the key provider that provides a PSK for a given identity.
62
    ///
63
    /// # Implementation details (informative, not covered by semver guarantees)
64
    ///
65
    /// Setting a `id_key_provider` will set the `validate_id_call_back` of the underlying
66
    /// [`coap_dtls_spsk_t`] to a wrapper function, which will then call the key provider.
67
    pub fn id_key_provider(mut self, id_key_provider: impl ServerPskIdentityKeyProvider<'a> + 'a) -> Self {
68
        self.ctx.id_key_provider = Some(Box::new(id_key_provider));
69
        self.ctx.raw_cfg.validate_id_call_back = Some(dtls_psk_server_id_callback);
70
        self
71
    }
72

            
73
    /// Sets the key provider that provides keys for a SNI provided by a client.
74
    ///
75
    /// # Implementation details (informative, not covered by semver guarantees)
76
    ///
77
    /// Setting a `sni_key_provider` will set the `validate_sni_call_back` of the underlying
78
    /// [`coap_dtls_spsk_t`] to a wrapper function, which will then call the key provider.
79
    ///
80
    /// Keys returned by the key provider will be stored in the context for at least as long as they
81
    /// are used by the respective session.
82
    pub fn sni_key_provider(mut self, sni_key_provider: impl ServerPskSniKeyProvider<'a> + 'a) -> Self {
83
        self.ctx.sni_key_provider = Some(Box::new(sni_key_provider));
84
        self.ctx.raw_cfg.validate_sni_call_back = Some(dtls_psk_server_sni_callback);
85
        self
86
    }
87

            
88
    /// Consumes this builder to construct the resulting PSK context.
89
35
    pub fn build(self) -> ServerPskContext<'a> {
90
35
        let ctx = Rc::new(RefCell::new(self.ctx));
91
35
        {
92
35
            let mut ctx_borrow = ctx.borrow_mut();
93
35
            if ctx_borrow.raw_cfg.validate_id_call_back.is_some() {
94
                ctx_borrow.raw_cfg.id_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void
95
35
            }
96
35
            if ctx_borrow.raw_cfg.validate_sni_call_back.is_some() {
97
                ctx_borrow.raw_cfg.sni_call_back_arg = Rc::downgrade(&ctx).into_raw() as *mut c_void
98
35
            }
99
        }
100
35
        ServerPskContext { inner: ctx }
101
35
    }
102
}
103

            
104
impl ServerPskContextBuilder<'_> {
105
    /// Enables or disables support for EC JPAKE ([RFC 8236](https://datatracker.ietf.org/doc/html/rfc8236))
106
    /// key exchanges in (D)TLS.
107
    ///
108
    /// # Implementation details (informative, not covered by semver guarantees)
109
    ///
110
    /// Equivalent to setting `ec_jpake` in the underlying [`coap_dtls_spsk_t`] structure.
111
    #[cfg(dtls_ec_jpake_support)]
112
    pub fn ec_jpake(mut self, ec_jpake: bool) -> Self {
113
        self.ctx.raw_cfg.ec_jpake = ec_jpake.into();
114
        self
115
    }
116
}
117

            
118
#[derive(Debug)]
119
struct ServerPskContextInner<'a> {
120
    /// Raw configuration object.
121
    raw_cfg: Box<coap_dtls_spsk_t>,
122
    /// Store for `coap_dtls_spsk_info_t` instances that we provided in previous SNI or ID
123
    /// callback invocations.
124
    ///
125
    /// The stored pointers *must* all be created from Box::into_raw().
126
    ///
127
    /// Using `Vec<coap_dtls_spsk_info_t>` instead is not an option, as a Vec resize may cause the
128
    /// instances to be moved to a different place in memory, invalidating pointers provided to
129
    /// libcoap.
130
    provided_keys: Vec<*mut coap_dtls_spsk_info_t>,
131
    /// User-supplied SNI key provider.
132
    sni_key_provider: Option<Box<dyn ServerPskSniKeyProvider<'a> + 'a>>,
133
    /// User-supplied identity key provider.
134
    id_key_provider: Option<Box<dyn ServerPskIdentityKeyProvider<'a> + 'a>>,
135
}
136

            
137
impl Drop for ServerPskContextInner<'_> {
138
35
    fn drop(&mut self) {
139
35
        for provided_key in std::mem::take(&mut self.provided_keys).into_iter() {
140
            // SAFETY: Vector has only ever been filled by instances created from to_raw_spsk_info.
141
            unsafe {
142
                PskKey::from_raw_spsk_info(*Box::from_raw(provided_key));
143
            }
144
        }
145
35
        if !self.raw_cfg.id_call_back_arg.is_null() {
146
            // SAFETY: If we set this, it must have been a call to Weak::into_raw with the correct
147
            //         type.
148
            unsafe {
149
                Weak::from_raw(self.raw_cfg.id_call_back_arg as *mut Self);
150
            }
151
35
        }
152
35
        if !self.raw_cfg.sni_call_back_arg.is_null() {
153
            // SAFETY: If we set this, it must have been a call to Weak::into_raw with the correct
154
            //         type.
155
            unsafe {
156
                Weak::from_raw(self.raw_cfg.sni_call_back_arg as *mut Self);
157
            }
158
35
        }
159
35
        unsafe {
160
35
            // SAFETY: Pointer should not have been changed by anything else and refers to a CPSK
161
35
            //         info instance created from DtlsPsk::into_raw_cpsk_info().
162
35
            PskKey::from_raw_spsk_info(self.raw_cfg.psk_info);
163
35
        }
164
35
    }
165
}
166

            
167
/// Server-side encryption context for PSK-based (D)TLS sessions.
168
#[derive(Clone, Debug)]
169
pub struct ServerPskContext<'a> {
170
    /// Inner structure of this context.
171
    inner: Rc<RefCell<ServerPskContextInner<'a>>>,
172
}
173

            
174
impl ServerPskContext<'_> {
175
    /// Returns a pointer to the PSK key data to use for a given `identity` and `session`, or
176
    /// [`std::ptr::null()`] if the provided identity hint and/or session are unacceptable.
177
    ///
178
    /// The returned pointer is guaranteed to remain valid as long as the underlying
179
    /// [`ServerPskContextInner`] is not dropped.
180
    /// As the [`ServerPskContext`] is also stored in the [`CoapServerSession`] instance, this
181
    /// implies that the pointer is valid for at least as long as the session is.
182
    ///
183
    /// **Important:** After the underlying [`ServerPskContextInner`] is dropped, the returned
184
    /// pointer will no longer be valid and should no longer be dereferenced.
185
    fn id_callback(&self, identity: &[u8], session: &CoapServerSession<'_>) -> *const coap_bin_const_t {
186
        let mut inner = (*self.inner).borrow_mut();
187
        let key = inner
188
            .id_key_provider
189
            .as_ref()
190
            .unwrap()
191
            .key_for_identity(identity, session);
192

            
193
        if let Some(key) = key {
194
            let boxed_key_info = Box::new(key.into_raw_spsk_info());
195
            let boxed_key_ptr = Box::into_raw(boxed_key_info);
196
            // TODO remove these entries prematurely if the underlying session is removed (would
197
            //      require modifications to the event handler).
198
            inner.provided_keys.push(boxed_key_ptr);
199
            // SAFETY: Pointer is obviously valid.
200
            &unsafe { *boxed_key_ptr }.key
201
        } else {
202
            std::ptr::null()
203
        }
204
    }
205

            
206
    /// Returns a pointer to the PSK (potentially with identity hint) to use for a given `sni` (and
207
    /// `session`), or [`std::ptr::null()`] if the provided identity hint and/or session are
208
    /// unacceptable.
209
    ///
210
    /// The returned pointer is guaranteed to remain valid as long as the underlying
211
    /// [`ServerPskContextInner`] is not dropped.
212
    /// As the [`ServerPskContext`] is also stored in the [`CoapServerSession`] instance, this
213
    /// implies that the pointer is valid for at least as long as the session is.
214
    ///
215
    /// **Important:** After the underlying [`ServerPskContextInner`] is dropped, the returned
216
    /// pointer will no longer be valid and should no longer be dereferenced.
217
    fn sni_callback(&self, sni: &CStr, session: &CoapServerSession<'_>) -> *const coap_dtls_spsk_info_t {
218
        let mut inner = (*self.inner).borrow_mut();
219
        let key = inner.sni_key_provider.as_ref().unwrap().key_for_sni(sni, session);
220

            
221
        if let Some(key) = key {
222
            let boxed_key_info = Box::new(key.into_raw_spsk_info());
223
            let boxed_key_ptr = Box::into_raw(boxed_key_info);
224
            inner.provided_keys.push(boxed_key_ptr);
225
            // SAFETY: Pointer is obviously valid.
226
            boxed_key_ptr
227
        } else {
228
            std::ptr::null()
229
        }
230
    }
231

            
232
    /// Applies this encryption configuration to the given raw `coap_context_t`.
233
    ///
234
    /// # Errors
235
    ///
236
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
237
    /// function fails.
238
    ///
239
    /// # Safety
240
    /// This [ServerPskContext] must outlive the provided CoAP context, the provided pointer must be
241
    /// valid.
242
35
    pub(crate) unsafe fn apply_to_context(
243
35
        &self,
244
35
        mut ctx: NonNull<coap_context_t>,
245
35
    ) -> Result<(), ContextConfigurationError> {
246
35
        let mut inner = self.inner.borrow_mut();
247
35
        // SAFETY: context is valid as per caller contract, raw_cfg is a valid configuration as
248
35
        // ensured by the builder.
249
35
        match unsafe { coap_context_set_psk2(ctx.as_mut(), inner.raw_cfg.as_mut()) } {
250
35
            1 => Ok(()),
251
            _ => Err(ContextConfigurationError::Unknown),
252
        }
253
35
    }
254
}
255

            
256
impl<'a> ServerPskContext<'a> {
257
    /// Restores a [`ServerPskContext`] from a pointer to its inner structure (i.e. from the
258
    /// user-provided pointer given to DTLS callbacks).
259
    ///
260
    /// # Panics
261
    ///
262
    /// Panics if the given pointer is a null pointer or the inner structure was already dropped.
263
    ///
264
    /// # Safety
265
    /// The provided pointer must be a valid reference to a [`RefCell<ServerPskContextInner>`]
266
    /// instance created from a call to [`Weak::into_raw()`].
267
    unsafe fn from_raw(raw_ctx: *const RefCell<ServerPskContextInner<'a>>) -> Self {
268
        assert!(!raw_ctx.is_null(), "provided raw DTLS PSK server context was null");
269
        let inner_weak = Weak::from_raw(raw_ctx);
270
        let inner = inner_weak
271
            .upgrade()
272
            .expect("provided DTLS PSK server context was already dropped!");
273
        let _ = Weak::into_raw(inner_weak);
274
        ServerPskContext { inner }
275
    }
276
}
277

            
278
/// Trait for types that can provide pre-shared keys for a key identity given by a client to a
279
/// server.
280
pub trait ServerPskIdentityKeyProvider<'a>: Debug {
281
    /// Provides the key for the key `identity` given by the client that is connected through
282
    /// `session`, or `None` if the identity unacceptable or no key is available.
283
    fn key_for_identity(&self, identity: &[u8], session: &CoapServerSession<'_>) -> Option<PskKey<'a>>;
284
}
285

            
286
impl<'a, T: Debug> ServerPskIdentityKeyProvider<'a> for T
287
where
288
    T: AsRef<[PskKey<'a>]>,
289
{
290
    /// Returns the first key whose identity is equal to the one requested.
291
    /// If not found, returns the first key that has no key ID set.
292
    fn key_for_identity(&self, identity: &[u8], _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
293
        let keys = self.as_ref();
294
        keys.iter()
295
            .find(|k| k.identity().is_some_and(|kid| kid == identity))
296
            .or_else(|| keys.iter().find(|k| k.identity().is_none()))
297
            .cloned()
298
    }
299
}
300

            
301
/// Trait for things that can provide PSK DTLS keys for a given Server Name Indication.
302
pub trait ServerPskSniKeyProvider<'a>: Debug {
303
    /// Provide a key for the server name indication given as `sni`, or `None` if the SNI is not
304
    /// valid and no key is available.
305
    ///
306
    /// Note that libcoap will remember the returned key and re-use it for future handshakes with
307
    /// the same SNI (even if the peer is not the same), the return value should therefore not
308
    /// depend on the provided `session`.
309
    fn key_for_sni(&self, sni: &CStr, session: &CoapServerSession<'_>) -> Option<PskKey<'a>>;
310
}
311

            
312
impl<'a, T: AsRef<[u8]> + Debug, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a> for Vec<(T, U)> {
313
    /// Return the second tuple object if the first one matches the given SNI.
314
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
315
        self.iter()
316
            .find_map(|(key_sni, key)| (key_sni.as_ref() == sni.to_bytes()).then_some(key.as_ref().clone()))
317
    }
318
}
319

            
320
impl<'a, T: AsRef<[u8]> + Debug, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a> for [(T, U)] {
321
    /// Return the second tuple object if the first one matches the given SNI.
322
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
323
        self.iter()
324
            .find_map(|(key_sni, key)| (key_sni.as_ref() == sni.to_bytes()).then_some(key.as_ref().clone()))
325
    }
326
}
327

            
328
impl<'a, T: Borrow<[u8]> + Debug + Eq + Hash, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a>
329
    for HashMap<T, U>
330
{
331
    /// Return the map value if the key matches the given SNI.
332
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
333
        self.get(sni.to_bytes()).map(|v| v.as_ref()).cloned()
334
    }
335
}
336

            
337
impl<'a, T: Borrow<[u8]> + Debug + Ord, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a> for BTreeMap<T, U> {
338
    /// Return the map value if the key matches the given SNI.
339
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
340
        self.get(sni.to_bytes()).map(|v| v.as_ref()).cloned()
341
    }
342
}
343

            
344
/// Raw PSK identity callback that can be provided to libcoap.
345
///
346
/// # Safety
347
///
348
/// This function expects the arguments to be provided in a way that libcoap would when invoking
349
/// this function as an identity callback.
350
///
351
/// Additionally, `arg` must be a valid argument to [`ServerPskContext::from_raw`].
352
unsafe extern "C" fn dtls_psk_server_id_callback(
353
    identity: *mut coap_bin_const_t,
354
    session: *mut coap_session_t,
355
    userdata: *mut c_void,
356
) -> *const coap_bin_const_t {
357
    let identity = std::slice::from_raw_parts((*identity).s, (*identity).length);
358
    // We must not increase the refcount here, as doing so would require locking the global context,
359
    // which is not possible during a DTLS callback.
360
    // SAFETY: While we are in this callback, libcoap's context is locked by our current thread.
361
    //         therefore, it is impossible that the reference counter would be decreased by any
362
    //         other means, and constructing the server side session without increasing the refcount
363
    //         is fine.
364
    let session = CoapServerSession::from_raw_without_refcount(session);
365
    let server_context = ServerPskContext::from_raw(userdata as *const RefCell<ServerPskContextInner>);
366
    server_context.id_callback(identity, &session)
367
}
368

            
369
/// Raw PSK SNI callback that can be provided to libcoap.
370
///
371
/// # Safety
372
///
373
/// This function expects the arguments to be provided in a way that libcoap would when invoking
374
/// this function as an PSK SNI callback.
375
///
376
/// Additionally, `arg` must be a valid argument to [`ServerPskContext::from_raw`].
377
unsafe extern "C" fn dtls_psk_server_sni_callback(
378
    sni: *const c_char,
379
    session: *mut coap_session_t,
380
    userdata: *mut c_void,
381
) -> *const coap_dtls_spsk_info_t {
382
    let sni = CStr::from_ptr(sni);
383
    // We must not increase the refcount here, as doing so would require locking the global context,
384
    // which is not possible during a DTLS callback.
385
    // SAFETY: While we are in this callback, libcoap's context is locked by our current thread.
386
    //         therefore, it is impossible that the reference counter would be decreased by any
387
    //         other means, and constructing the server side session without increasing the refcount
388
    //         is fine.
389
    let session = CoapServerSession::from_raw_without_refcount(session);
390
    let server_context = ServerPskContext::from_raw(userdata as *const RefCell<ServerPskContextInner>);
391
    server_context.sni_callback(sni, &session)
392
}