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/psk/server.rs - Interfaces and types for server-side PSK support in libcoap-rs.
9
 */
10

            
11
use std::{
12
    borrow::Borrow,
13
    cell::RefCell,
14
    collections::{BTreeMap, HashMap},
15
    ffi::{c_void, CStr},
16
    fmt::Debug,
17
    hash::Hash,
18
    mem::offset_of,
19
    os::raw::c_char,
20
    ptr::NonNull,
21
    rc::{Rc, Weak},
22
};
23

            
24
use libcoap_sys::{
25
    coap_bin_const_t, coap_context_set_psk2, coap_context_t, coap_dtls_spsk_info_t, coap_dtls_spsk_t, coap_session_t,
26
    COAP_DTLS_SPSK_SETUP_VERSION,
27
};
28

            
29
use crate::{crypto::psk::key::PskKey, error::ContextConfigurationError, session::CoapServerSession};
30

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

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

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

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

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

            
107
impl ServerPskContextBuilder<'_> {
108
    /// Enables or disables support for EC JPAKE ([RFC 8236](https://datatracker.ietf.org/doc/html/rfc8236))
109
    /// key exchanges in (D)TLS.
110
    ///
111
    /// Note: At the time of writing (based on libcoap 4.3.5), this is only supported on MbedTLS,
112
    /// enabling EC JPAKE on other DTLS backends has no effect.
113
    ///
114
    /// # Implementation details (informative, not covered by semver guarantees)
115
    ///
116
    /// Equivalent to setting `ec_jpake` in the underlying [`coap_dtls_spsk_t`] structure.
117
    pub fn ec_jpake(mut self, ec_jpake: bool) -> Self {
118
        self.ctx.raw_cfg.ec_jpake = ec_jpake.into();
119
        self
120
    }
121
}
122

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

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

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

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

            
198
        if let Some(key) = key {
199
            let boxed_key_info = Box::new(key.into_raw_spsk_info());
200
            let boxed_key_ptr = Box::into_raw(boxed_key_info);
201
            // TODO remove these entries prematurely if the underlying session is removed (would
202
            //      require modifications to the event handler).
203
            inner.provided_keys.push(boxed_key_ptr);
204
            // SAFETY: boxed_key_ptr is a valid pointer to a coap_dtls_spsk_info_t structure, which
205
            // has been created from a call to Box::into_raw.
206
            // We then apply the offset (determined using the offset_of! macro) of the key field inside of
207
            // the coap_dtls_spsk_info_t structure to this pointer to access the field inside of the data structure,
208
            // which in turn allows us to cast the pointer to the type of the field inside of the structure.
209
            unsafe {
210
                boxed_key_ptr.byte_offset(offset_of!(coap_dtls_spsk_info_t, key) as isize) as *const coap_bin_const_t
211
            }
212
        } else {
213
            std::ptr::null()
214
        }
215
    }
216

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

            
232
        if let Some(key) = key {
233
            let boxed_key_info = Box::new(key.into_raw_spsk_info());
234
            let boxed_key_ptr = Box::into_raw(boxed_key_info);
235
            inner.provided_keys.push(boxed_key_ptr);
236
            // SAFETY: Pointer is obviously valid.
237
            boxed_key_ptr
238
        } else {
239
            std::ptr::null()
240
        }
241
    }
242

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

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

            
289
/// Trait for types that can provide pre-shared keys for a key identity given by a client to a
290
/// server.
291
pub trait ServerPskIdentityKeyProvider<'a>: Debug {
292
    /// Provides the key for the key `identity` given by the client that is connected through
293
    /// `session`, or `None` if the identity unacceptable or no key is available.
294
    fn key_for_identity(&self, identity: &[u8], session: &CoapServerSession<'_>) -> Option<PskKey<'a>>;
295
}
296

            
297
impl<'a, T: Debug> ServerPskIdentityKeyProvider<'a> for T
298
where
299
    T: AsRef<[PskKey<'a>]>,
300
{
301
    /// Returns the first key whose identity is equal to the one requested.
302
    /// If not found, returns the first key that has no key ID set.
303
    fn key_for_identity(&self, identity: &[u8], _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
304
        let keys = self.as_ref();
305
        keys.iter()
306
            .find(|k| k.identity().is_some_and(|kid| kid == identity))
307
            .or_else(|| keys.iter().find(|k| k.identity().is_none()))
308
            .cloned()
309
    }
310
}
311

            
312
/// Trait for things that can provide PSK DTLS keys for a given Server Name Indication.
313
pub trait ServerPskSniKeyProvider<'a>: Debug {
314
    /// Provide a key for the server name indication given as `sni`, or `None` if the SNI is not
315
    /// valid and no key is available.
316
    ///
317
    /// Note that libcoap will remember the returned key and re-use it for future handshakes with
318
    /// the same SNI (even if the peer is not the same), the return value should therefore not
319
    /// depend on the provided `session`.
320
    fn key_for_sni(&self, sni: &CStr, session: &CoapServerSession<'_>) -> Option<PskKey<'a>>;
321
}
322

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

            
331
impl<'a, T: AsRef<[u8]> + Debug, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a> for [(T, U)] {
332
    /// Return the second tuple object if the first one matches the given SNI.
333
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
334
        self.iter()
335
            .find_map(|(key_sni, key)| (key_sni.as_ref() == sni.to_bytes()).then_some(key.as_ref().clone()))
336
    }
337
}
338

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

            
348
impl<'a, T: Borrow<[u8]> + Debug + Ord, U: AsRef<PskKey<'a>> + Debug> ServerPskSniKeyProvider<'a> for BTreeMap<T, U> {
349
    /// Return the map value if the key matches the given SNI.
350
    fn key_for_sni(&self, sni: &CStr, _session: &CoapServerSession<'_>) -> Option<PskKey<'a>> {
351
        self.get(sni.to_bytes()).map(|v| v.as_ref()).cloned()
352
    }
353
}
354

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

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