Skip to main content

confium_tls_signer/
lib.rs

1//! TLS 1.3 signature callback satisfying via threshold.
2//!
3//! For high-value TLS endpoints (root CAs, payment gateways), the
4//! server signing key is threshold-held across multiple data centers.
5//! This crate provides the TLS callback that routes the signature
6//! request through a Confium coordinator.
7//!
8//! See `TODO.roadmap/28-mode2-pki-replacement.md` for full spec.
9
10#![forbid(unsafe_code)]
11#![allow(missing_docs)] // TODO: document before 1.0
12
13use serde::{Deserialize, Serialize};
14
15/// TLS signature scheme identifier (RFC 8446 §4.2.3 SignatureScheme).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18#[repr(u16)]
19pub enum SignatureScheme {
20    /// ECDSA over NIST P-256 with SHA-256 (0x0403).
21    EcdsaSecp256r1Sha256 = 0x0403,
22    /// Ed25519 (0x0807).
23    Ed25519 = 0x0807,
24    /// RSA PKCS#1 v1.5 with SHA-256 (0x0401).
25    RsaPkcs1Sha256 = 0x0401,
26    /// RSA-PSS with SHA-256 (0x0804).
27    RsaPssSha256 = 0x0804,
28}
29
30/// TLS signature request from the TLS handshake layer.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct TlsSignatureRequest {
33    /// Signature scheme to use.
34    pub scheme: SignatureScheme,
35    /// Data to be signed (typically the handshake transcript hash).
36    pub data: Vec<u8>,
37    /// Quorum that holds the threshold signing key.
38    pub quorum_id: String,
39}
40
41/// TLS signature response — the produced signature.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct TlsSignatureResponse {
44    /// Signature scheme used.
45    pub scheme: SignatureScheme,
46    /// The signature bytes.
47    pub signature: Vec<u8>,
48}
49
50/// Errors during TLS signing.
51#[derive(Debug, thiserror::Error)]
52pub enum TlsSignerError {
53    /// Unsupported signature scheme.
54    #[error("unsupported signature scheme: {0:?}")]
55    UnsupportedScheme(SignatureScheme),
56    /// Threshold signing failed.
57    #[error("threshold signing failed: {0}")]
58    ThresholdFailed(String),
59    /// Coordinator unreachable.
60    #[error("coordinator unreachable: {0}")]
61    CoordinatorUnreachable(String),
62    /// Timeout waiting for quorum.
63    #[error("timeout waiting for T-of-N quorum")]
64    QuorumTimeout,
65}
66
67/// Signer hook — caller provides concrete threshold signing backend.
68pub trait ThresholdSigner {
69    /// Sign `data` using the quorum's threshold key.
70    fn sign(
71        &self,
72        quorum_id: &str,
73        scheme: SignatureScheme,
74        data: &[u8],
75    ) -> Result<Vec<u8>, String>;
76}
77
78/// The TLS signer.
79pub struct TlsSigner<'a> {
80    signer: &'a dyn ThresholdSigner,
81}
82
83impl<'a> TlsSigner<'a> {
84    /// Construct a new TLS signer backed by `signer`.
85    pub fn new(signer: &'a dyn ThresholdSigner) -> Self {
86        Self { signer }
87    }
88
89    /// Handle a TLS signature request.
90    pub fn sign(
91        &self,
92        request: &TlsSignatureRequest,
93    ) -> Result<TlsSignatureResponse, TlsSignerError> {
94        let signature = self
95            .signer
96            .sign(&request.quorum_id, request.scheme, &request.data)
97            .map_err(TlsSignerError::ThresholdFailed)?;
98        Ok(TlsSignatureResponse {
99            scheme: request.scheme,
100            signature,
101        })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    struct MockSigner;
110    impl ThresholdSigner for MockSigner {
111        fn sign(
112            &self,
113            _quorum_id: &str,
114            _scheme: SignatureScheme,
115            data: &[u8],
116        ) -> Result<Vec<u8>, String> {
117            Ok(data.to_vec())
118        }
119    }
120
121    #[test]
122    fn tls_sign_mock_round_trip() {
123        let signer = MockSigner;
124        let tls = TlsSigner::new(&signer);
125        let req = TlsSignatureRequest {
126            scheme: SignatureScheme::Ed25519,
127            data: b"handshake transcript".to_vec(),
128            quorum_id: "test-quorum".into(),
129        };
130        let resp = tls.sign(&req).unwrap();
131        assert_eq!(resp.scheme, SignatureScheme::Ed25519);
132        assert_eq!(resp.signature, req.data);
133    }
134}