Skip to main content

vwh_core/
lib.rs

1pub mod crypto;
2pub mod error;
3pub mod format;
4pub mod verify;
5
6pub use error::{Error, Result};
7pub use format::{Artifact, ArtifactState, FLAG_SEALED, MAGIC};
8
9// ArtifactId, Intent, KeyFingerprint are defined in this module and exported below
10
11use serde::{Deserialize, Serialize};
12use std::fmt;
13
14/// Artifact ID: immutable 128-bit random identifier
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct ArtifactId(pub [u8; 16]);
17
18impl ArtifactId {
19    pub fn new() -> Self {
20        use rand::RngCore;
21        let mut id = [0u8; 16];
22        rand::thread_rng().fill_bytes(&mut id);
23        Self(id)
24    }
25
26    pub fn from_bytes(bytes: [u8; 16]) -> Self {
27        Self(bytes)
28    }
29
30    pub fn to_hex(&self) -> String {
31        hex::encode(self.0)
32    }
33
34    pub fn from_hex(s: &str) -> Result<Self> {
35        let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
36        if bytes.len() != 16 {
37            return Err(Error::InvalidArtifactId);
38        }
39        let mut arr = [0u8; 16];
40        arr.copy_from_slice(&bytes);
41        Ok(Self(arr))
42    }
43
44    /// Short display (first 8 bytes)
45    pub fn short_display(&self) -> String {
46        hex::encode(&self.0[..8])
47    }
48}
49
50impl fmt::Display for ArtifactId {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}", self.to_hex())
53    }
54}
55
56impl Default for ArtifactId {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62/// Intent enum (mandatory, no default)
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[repr(u8)]
65pub enum Intent {
66    Lab = 0,
67    OwnedInfra = 1,
68    AuthRedteam = 2,
69    BlueRemediation = 3,
70    Research = 4,
71}
72
73impl Intent {
74    pub fn from_u8(value: u8) -> Result<Self> {
75        match value {
76            0 => Ok(Intent::Lab),
77            1 => Ok(Intent::OwnedInfra),
78            2 => Ok(Intent::AuthRedteam),
79            3 => Ok(Intent::BlueRemediation),
80            4 => Ok(Intent::Research),
81            _ => Err(Error::InvalidIntent(value)),
82        }
83    }
84
85    pub fn to_u8(self) -> u8 {
86        self as u8
87    }
88
89    pub fn as_str(&self) -> &'static str {
90        match self {
91            Intent::Lab => "LAB",
92            Intent::OwnedInfra => "OWNED-INFRA",
93            Intent::AuthRedteam => "AUTH-REDTEAM",
94            Intent::BlueRemediation => "BLUE-REMEDIATION",
95            Intent::Research => "RESEARCH",
96        }
97    }
98
99    pub fn all() -> &'static [Intent] {
100        &[
101            Intent::Lab,
102            Intent::OwnedInfra,
103            Intent::AuthRedteam,
104            Intent::BlueRemediation,
105            Intent::Research,
106        ]
107    }
108
109    pub fn from_str(s: &str) -> Result<Self> {
110        match s.to_uppercase().as_str() {
111            "LAB" => Ok(Intent::Lab),
112            "OWNED-INFRA" | "OWNED_INFRA" => Ok(Intent::OwnedInfra),
113            "AUTH-REDTEAM" | "AUTH_REDTEAM" | "AUTHREDTEAM" => Ok(Intent::AuthRedteam),
114            "BLUE-REMEDIATION" | "BLUE_REMEDIATION" => Ok(Intent::BlueRemediation),
115            "RESEARCH" => Ok(Intent::Research),
116            _ => Err(Error::InvalidIntentString(s.to_string())),
117        }
118    }
119}
120
121impl fmt::Display for Intent {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "{}", self.as_str())
124    }
125}
126
127/// Public key fingerprint (BLAKE3 hash of public key bytes)
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct KeyFingerprint(pub [u8; 32]);
130
131impl KeyFingerprint {
132    pub fn new(public_key_bytes: &[u8; 32]) -> Self {
133        let hash = blake3::hash(public_key_bytes);
134        Self(*hash.as_bytes())
135    }
136
137    pub fn to_hex(&self) -> String {
138        hex::encode(self.0)
139    }
140
141    pub fn short_display(&self) -> String {
142        hex::encode(&self.0[..8])
143    }
144}
145
146impl fmt::Display for KeyFingerprint {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(f, "{}", self.short_display())
149    }
150}
151
152/// Revocation reason
153#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
154#[serde(rename_all = "PascalCase")]
155pub enum RevocationReason {
156    Error,
157    Compromised,
158    Superseded,
159    AccessRevoked,
160    Other,
161}
162