Skip to main content

confium_wasm/
pki.rs

1//! `Certificate` + `CMS::SignedData` — PKI verifiers for the browser/Node.js
2//! surface.
3
4use confium_pki::{cert::Certificate as RustCert, cms::SignedData as RustSignedData};
5use wasm_bindgen::prelude::*;
6
7/// Parsed X.509 v3 certificate. Construct via [`Certificate::from_der`] or
8/// [`Certificate::from_pem`]; inspect validity window, fingerprint, serial.
9#[wasm_bindgen]
10pub struct Certificate {
11    inner: RustCert,
12}
13
14#[wasm_bindgen]
15impl Certificate {
16    /// Parse a certificate from DER bytes (`Uint8Array`).
17    #[wasm_bindgen(constructor)]
18    pub fn from_der(der: &[u8]) -> Result<Certificate, JsValue> {
19        let inner = RustCert::from_der(der)
20            .map_err(|e| JsValue::from_str(&format!("DER parse error: {e}")))?;
21        Ok(Self { inner })
22    }
23
24    /// Parse a certificate from PEM (RFC 7468) text.
25    pub fn from_pem(pem: &str) -> Result<Certificate, JsValue> {
26        let inner = RustCert::from_pem(pem)
27            .map_err(|e| JsValue::from_str(&format!("PEM parse error: {e}")))?;
28        Ok(Self { inner })
29    }
30
31    /// SHA-256 fingerprint as a lowercase hex string.
32    #[wasm_bindgen(getter)]
33    pub fn fingerprint_sha256(&self) -> String {
34        self.inner.fingerprint_sha256()
35    }
36
37    /// Serial number as a lowercase hex string.
38    #[wasm_bindgen(getter)]
39    pub fn serial_hex(&self) -> String {
40        let bytes = self.inner.serial_bytes();
41        let mut out = String::with_capacity(bytes.len() * 2);
42        for b in bytes {
43            out.push_str(&format!("{:02x}", b));
44        }
45        out
46    }
47
48    /// DER bytes (`Uint8Array`).
49    #[wasm_bindgen]
50    pub fn to_der(&self) -> Vec<u8> {
51        self.inner.to_der()
52    }
53
54    /// Whether the certificate is within its validity window at the given
55    /// epoch-millisecond timestamp.
56    #[wasm_bindgen]
57    pub fn is_within_validity(&self, epoch_ms: f64) -> bool {
58        let now = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(epoch_ms as i64)
59            .unwrap_or_else(chrono::Utc::now);
60        self.inner.is_within_validity(now)
61    }
62}
63
64/// CMS SignedData JSON model — wraps `confium_pki::cms::SignedData`.
65///
66/// Construct via [`SignedData::from_json`] (the canonical wire format) and
67/// inspect signer / certificate / content fields. Verification of the
68/// signatures themselves happens at a higher layer (the verifier is
69/// caller-supplied because each signer algorithm needs its own callback).
70#[wasm_bindgen]
71pub struct SignedData {
72    inner: RustSignedData,
73}
74
75#[wasm_bindgen]
76impl SignedData {
77    /// Parse SignedData from its canonical JSON form.
78    #[wasm_bindgen(constructor)]
79    pub fn from_json(json: &str) -> Result<SignedData, JsValue> {
80        let inner: RustSignedData = serde_json::from_str(json)
81            .map_err(|e| JsValue::from_str(&format!("SignedData JSON parse error: {e}")))?;
82        Ok(Self { inner })
83    }
84
85    /// Round-trip back to canonical JSON.
86    #[wasm_bindgen]
87    pub fn to_json(&self) -> Result<String, JsValue> {
88        serde_json::to_string(&self.inner)
89            .map_err(|e| JsValue::from_str(&format!("serialize: {e}")))
90    }
91
92    /// Number of signer infos.
93    #[wasm_bindgen(getter)]
94    pub fn signer_count(&self) -> usize {
95        self.inner.signer_infos.len()
96    }
97
98    /// Content type OID.
99    #[wasm_bindgen(getter)]
100    pub fn content_type(&self) -> String {
101        self.inner.encap_content_info.content_type.clone()
102    }
103
104    /// Number of attached X.509 certificates.
105    #[wasm_bindgen(getter)]
106    pub fn certificate_count(&self) -> usize {
107        self.inner.certificates.len()
108    }
109
110    /// Get the DER bytes of the certificate at `index`, or `undefined` if
111    /// out of range.
112    #[wasm_bindgen]
113    pub fn certificate_at(&self, index: usize) -> Option<Vec<u8>> {
114        self.inner.certificates.get(index).cloned()
115    }
116
117    /// Get the encapsulated content bytes, or `None` if detached.
118    #[wasm_bindgen]
119    pub fn content(&self) -> Option<Vec<u8>> {
120        self.inner.encap_content_info.content.clone()
121    }
122}