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
    os::raw::c_char,
19
    ptr::NonNull,
20
    rc::{Rc, Weak},
21
};
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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