Skip to main content

spacedb_access/
identity.rs

1//! Identities and signing — ECDSA P-256 (ES256), the curve mID uses.
2//!
3//! A [`Did`] is an opaque identifier (e.g. `did:mata:…`, `did:agent:…`); its
4//! published verification key is resolved through the [`KeyDirectory`] seam, so
5//! MATA can map `did:mata` via IAMHUMAN while a self-hoster uses the in-memory
6//! directory. An [`Identity`] is a keypair: it signs (capabilities, sub-grants)
7//! and publishes its SEC1 public key.
8//!
9//! [`KeyDirectory`]: crate::KeyDirectory
10
11use p256::ecdsa::signature::{Signer, Verifier};
12use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
13use serde::{Deserialize, Serialize};
14
15use crate::error::{AccessError, AccessResult};
16
17/// An identity reference: who an issuer/bearer is. Resolved to a key via the
18/// directory.
19#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
20pub struct Did(pub String);
21
22impl From<&str> for Did {
23    fn from(s: &str) -> Self {
24        Did(s.to_string())
25    }
26}
27
28impl From<String> for Did {
29    fn from(s: String) -> Self {
30        Did(s)
31    }
32}
33
34impl std::fmt::Display for Did {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.write_str(&self.0)
37    }
38}
39
40/// A P-256 keypair bound to a [`Did`]. Signs capabilities and sub-grants; its
41/// public key is published to a directory for verifiers.
42pub struct Identity {
43    did: Did,
44    signing_key: SigningKey,
45    public_sec1: Vec<u8>,
46}
47
48impl Identity {
49    /// Generate a fresh keypair for `did` using OS randomness.
50    pub fn generate(did: impl Into<Did>) -> AccessResult<Self> {
51        let mut raw = [0u8; 32];
52        getrandom::fill(&mut raw).map_err(|e| AccessError::KeyGen(e.to_string()))?;
53        let signing_key = SigningKey::from_bytes((&raw).into())
54            .map_err(|e| AccessError::KeyGen(e.to_string()))?;
55        let public_sec1 = signing_key
56            .verifying_key()
57            .to_encoded_point(true)
58            .as_bytes()
59            .to_vec();
60        Ok(Self {
61            did: did.into(),
62            signing_key,
63            public_sec1,
64        })
65    }
66
67    /// This identity's DID.
68    pub fn did(&self) -> &Did {
69        &self.did
70    }
71
72    /// This identity's published SEC1 public key (compressed, 33 bytes).
73    pub fn public_key(&self) -> &[u8] {
74        &self.public_sec1
75    }
76
77    /// Sign `message`, returning a DER-encoded ECDSA signature.
78    pub fn sign(&self, message: &[u8]) -> Vec<u8> {
79        let signature: Signature = self.signing_key.sign(message);
80        signature.to_der().as_bytes().to_vec()
81    }
82}
83
84/// Verify a DER signature `sig_der` over `message` against a SEC1 public key.
85/// Returns `false` (not an error) on any parse or verification failure — a bad
86/// signature is a [`Deny`](crate::Decision), not a system error.
87pub(crate) fn verify_sec1(public_sec1: &[u8], message: &[u8], sig_der: &[u8]) -> bool {
88    let key = match VerifyingKey::from_sec1_bytes(public_sec1) {
89        Ok(k) => k,
90        Err(_) => return false,
91    };
92    let signature = match Signature::from_der(sig_der) {
93        Ok(s) => s,
94        Err(_) => return false,
95    };
96    key.verify(message, &signature).is_ok()
97}