Skip to main content

confium_pkcs11_server/
dispatch.rs

1//! Dispatch layer — routes PKCS#11 calls to threshold protocol.
2
3use crate::slot::{SlotId, SlotInfo};
4use crate::token::TokenInfo;
5use std::collections::HashMap;
6
7/// Errors during PKCS#11 dispatch.
8#[derive(Debug, thiserror::Error)]
9pub enum Pkcs11Error {
10    /// Slot not present.
11    #[error("slot {0:?} not present")]
12    SlotNotPresent(SlotId),
13    /// Threshold signing failed.
14    #[error("threshold signing failed: {0}")]
15    SignFailed(String),
16    /// Threshold decryption failed.
17    #[error("threshold decryption failed: {0}")]
18    DecryptFailed(String),
19    /// Function not supported.
20    #[error("function {0} not supported")]
21    UnsupportedFunction(String),
22    /// PIN incorrect.
23    #[error("PIN incorrect")]
24    BadPin,
25}
26
27/// Signer trait — caller provides the actual coordinator dispatch.
28pub trait QuorumDispatcher: Send + Sync {
29    /// Sign `data` using the threshold quorum at `slot`. Returns signature bytes.
30    fn sign(&self, slot: SlotId, data: &[u8]) -> Result<Vec<u8>, String>;
31
32    /// Decrypt `ciphertext` using the threshold quorum at `slot`. Returns plaintext.
33    fn decrypt(&self, slot: SlotId, ciphertext: &[u8]) -> Result<Vec<u8>, String>;
34
35    /// Trigger a DKG for a new threshold keypair.
36    fn generate_keypair(&self, slot: SlotId) -> Result<Vec<u8>, String>;
37}
38
39/// The PKCS#11 dispatch service.
40pub struct Pkcs11Server {
41    slots: HashMap<SlotId, SlotInfo>,
42    tokens: HashMap<SlotId, TokenInfo>,
43    dispatcher: Box<dyn QuorumDispatcher>,
44}
45
46impl Pkcs11Server {
47    /// Construct a new server backed by `dispatcher`.
48    pub fn new(dispatcher: Box<dyn QuorumDispatcher>) -> Self {
49        Self {
50            slots: HashMap::new(),
51            tokens: HashMap::new(),
52            dispatcher,
53        }
54    }
55
56    /// Register a slot for a Confium quorum.
57    pub fn register_quorum(&mut self, slot: SlotId, slot_info: SlotInfo, token_info: TokenInfo) {
58        self.slots.insert(slot.clone(), slot_info);
59        self.tokens.insert(slot, token_info);
60    }
61
62    /// `C_Sign` — sign data via threshold protocol.
63    pub fn c_sign(&self, slot: SlotId, data: &[u8]) -> Result<Vec<u8>, Pkcs11Error> {
64        if !self.slots.contains_key(&slot) {
65            return Err(Pkcs11Error::SlotNotPresent(slot));
66        }
67        self.dispatcher
68            .sign(slot.clone(), data)
69            .map_err(Pkcs11Error::SignFailed)
70    }
71
72    /// `C_Decrypt` — decrypt via threshold protocol.
73    pub fn c_decrypt(&self, slot: SlotId, ciphertext: &[u8]) -> Result<Vec<u8>, Pkcs11Error> {
74        if !self.slots.contains_key(&slot) {
75            return Err(Pkcs11Error::SlotNotPresent(slot));
76        }
77        self.dispatcher
78            .decrypt(slot.clone(), ciphertext)
79            .map_err(Pkcs11Error::DecryptFailed)
80    }
81
82    /// `C_GenerateKeyPair` — trigger DKG.
83    pub fn c_generate_keypair(&self, slot: SlotId) -> Result<Vec<u8>, Pkcs11Error> {
84        if !self.slots.contains_key(&slot) {
85            return Err(Pkcs11Error::SlotNotPresent(slot));
86        }
87        self.dispatcher
88            .generate_keypair(slot)
89            .map_err(Pkcs11Error::SignFailed)
90    }
91
92    /// Get slot info.
93    pub fn slot_info(&self, slot: &SlotId) -> Option<&SlotInfo> {
94        self.slots.get(slot)
95    }
96
97    /// Get token info.
98    pub fn token_info(&self, slot: &SlotId) -> Option<&TokenInfo> {
99        self.tokens.get(slot)
100    }
101
102    /// Number of registered slots.
103    pub fn slot_count(&self) -> usize {
104        self.slots.len()
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    struct MockDispatcher;
113    impl QuorumDispatcher for MockDispatcher {
114        fn sign(&self, _slot: SlotId, data: &[u8]) -> Result<Vec<u8>, String> {
115            Ok(data.iter().map(|b| !b).collect())
116        }
117        fn decrypt(&self, _slot: SlotId, ct: &[u8]) -> Result<Vec<u8>, String> {
118            Ok(ct.to_vec())
119        }
120        fn generate_keypair(&self, _slot: SlotId) -> Result<Vec<u8>, String> {
121            Ok(vec![0u8; 32])
122        }
123    }
124
125    #[test]
126    fn full_pkcs11_lifecycle() {
127        let mut server = Pkcs11Server::new(Box::new(MockDispatcher));
128        let slot = SlotId(1);
129        server.register_quorum(
130            slot,
131            SlotInfo::for_quorum("test-quorum"),
132            TokenInfo::for_quorum(
133                SlotId(1),
134                "test-quorum",
135                2,
136                3,
137                "FROST-P256",
138                "coordinator.example.com:443",
139            ),
140        );
141        assert_eq!(server.slot_count(), 1);
142
143        let sig = server.c_sign(SlotId(1), b"hello").unwrap();
144        // !b'h' = 0x97, !b'e' = 0x9a, !b'l' = 0x93, !b'l' = 0x93, !b'o' = 0x90
145        assert_eq!(sig, vec![0x97, 0x9a, 0x93, 0x93, 0x90]);
146
147        let pt = server.c_decrypt(SlotId(1), b"cipher").unwrap();
148        assert_eq!(pt, b"cipher");
149
150        let pk = server.c_generate_keypair(SlotId(1)).unwrap();
151        assert_eq!(pk.len(), 32);
152    }
153
154    #[test]
155    fn unknown_slot_fails() {
156        let server = Pkcs11Server::new(Box::new(MockDispatcher));
157        let result = server.c_sign(SlotId(99), b"data");
158        assert!(matches!(result, Err(Pkcs11Error::SlotNotPresent(_))));
159    }
160}