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};
8pub use format::{Draft, Signed, Sealed, TypedArtifact};
9
10use serde::{Deserialize, Serialize};
13use std::fmt;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct ArtifactId(pub [u8; 16]);
18
19impl ArtifactId {
20 pub fn new() -> Self {
21 let mut id = [0u8; 16];
22 getrandom::fill(&mut id).expect("OS entropy unavailable");
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 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#[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 #[allow(clippy::should_implement_trait)]
112 pub fn from_str(s: &str) -> Result<Self> {
113 match s.to_uppercase().as_str() {
114 "LAB" => Ok(Intent::Lab),
115 "OWNED-INFRA" | "OWNED_INFRA" => Ok(Intent::OwnedInfra),
116 "AUTH-REDTEAM" | "AUTH_REDTEAM" | "AUTHREDTEAM" => Ok(Intent::AuthRedteam),
117 "BLUE-REMEDIATION" | "BLUE_REMEDIATION" => Ok(Intent::BlueRemediation),
118 "RESEARCH" => Ok(Intent::Research),
119 _ => Err(Error::InvalidIntentString(s.to_string())),
120 }
121 }
122}
123
124impl fmt::Display for Intent {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}", self.as_str())
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct KeyFingerprint(pub [u8; 32]);
133
134impl KeyFingerprint {
135 pub fn new(public_key_bytes: &[u8; 32]) -> Self {
136 let hash = blake3::hash(public_key_bytes);
137 Self(*hash.as_bytes())
138 }
139
140 pub fn to_hex(&self) -> String {
141 hex::encode(self.0)
142 }
143
144 pub fn short_display(&self) -> String {
145 hex::encode(&self.0[..8])
146 }
147}
148
149impl fmt::Display for KeyFingerprint {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(f, "{}", self.to_hex())
152 }
153}
154
155