1use core::borrow::Borrow as _;
4
5use spideroak_crypto::hash::{Digest, Hash};
6use zerocopy::{Immutable, IntoBytes, KnownLayout, Unaligned};
7
8use crate::{
9 aranya::{Signature, SigningKeyId},
10 ciphersuite::{CipherSuite, CipherSuiteExt as _},
11 id::{IdExt as _, custom_id},
12};
13
14custom_id! {
15 #[derive(Immutable, IntoBytes, KnownLayout, Unaligned)]
17 pub struct GroupId;
18}
19
20custom_id! {
21 #[derive(Immutable, IntoBytes, KnownLayout, Unaligned)]
23 pub struct PolicyId;
24}
25
26custom_id! {
27 #[derive(Immutable, IntoBytes, KnownLayout, Unaligned)]
29 pub struct CmdId;
30}
31
32pub(crate) fn cmd_id<CS: CipherSuite>(
34 cmd: &Digest<<CS::Hash as Hash>::DigestSize>,
35 sig: &Signature<CS>,
36) -> CmdId {
37 CmdId::new::<CS>(
43 b"PolicyCommandId-v1",
44 [cmd.as_bytes(), sig.raw_sig().borrow()],
45 )
46}
47
48pub fn merge_cmd_id<CS: CipherSuite>(left: CmdId, right: CmdId) -> CmdId {
50 CmdId::new::<CS>(b"MergeCommandId-v1", [left.as_bytes(), right.as_bytes()])
56}
57
58#[derive(Copy, Clone, Debug)]
60pub struct Cmd<'a> {
61 pub data: &'a [u8],
63 pub name: &'a str,
67 pub parent_id: &'a CmdId,
69}
70
71impl Cmd<'_> {
72 pub(crate) fn digest<CS: CipherSuite>(
75 &self,
76 author: SigningKeyId,
77 ) -> Digest<<CS::Hash as Hash>::DigestSize> {
78 CS::tuple_hash(
89 b"SignPolicyCommand-v1",
90 [
91 author.as_bytes(),
93 self.name.as_bytes(),
95 self.parent_id.as_bytes(),
97 self.data,
99 ],
100 )
101 }
102}
103
104custom_id! {
105 #[derive(Immutable, IntoBytes, KnownLayout, Unaligned)]
107 pub struct RoleId;
108}
109
110pub fn role_id<CS: CipherSuite>(cmd_id: CmdId, name: &str, policy_id: PolicyId) -> RoleId {
115 RoleId::new::<CS>(
122 b"RoleId-v1",
123 [cmd_id.as_bytes(), name.as_bytes(), policy_id.as_bytes()],
124 )
125}
126
127custom_id! {
128 #[derive(Immutable, IntoBytes, KnownLayout, Unaligned)]
130 pub struct LabelId;
131}
132
133pub fn label_id<CS: CipherSuite>(cmd_id: CmdId, name: &str, policy_id: PolicyId) -> LabelId {
138 LabelId::new::<CS>(
145 b"LabelId-v1",
146 [cmd_id.as_bytes(), name.as_bytes(), policy_id.as_bytes()],
147 )
148}
149
150#[cfg(test)]
151mod tests {
152 use spideroak_crypto::{ed25519::Ed25519, rust};
153
154 use super::*;
155 use crate::{default::DhKemP256HkdfSha256, test_util::TestCs};
156
157 type CS = TestCs<
158 rust::Aes256Gcm,
159 rust::Sha256,
160 rust::HkdfSha512,
161 DhKemP256HkdfSha256,
162 rust::HmacSha512,
163 Ed25519,
164 >;
165
166 #[test]
168 fn test_label_id() {
169 let tests = [
170 (
171 CmdId::default(),
172 "foo",
173 PolicyId::default(),
174 "C1PupQYTjr2ouZ3DohnRFEaHR4yoTnMkarbBK4TGhJoi",
175 ),
176 (
177 CmdId::default(),
178 "bar",
179 PolicyId::default(),
180 "Eq71P2UhRVMt7R1s1ZB6m1kSuuzwBZwAd21BEv3gmtBC",
181 ),
182 (
183 CmdId::from_bytes([b'A'; 32]),
184 "bar",
185 PolicyId::default(),
186 "B4XqE83yLS1i8AiyMxGKo2wtrwvqrhUers5ou3eRfH8z",
187 ),
188 (
189 CmdId::from_bytes([b'A'; 32]),
190 "baz",
191 PolicyId::from_bytes([b'B'; 32]),
192 "ACnKJXFwd9e2tSnakXgP8SMiYHBSQLUetWgRjHjyQo8y",
193 ),
194 ];
195 for (i, (cmd_id, name, policy_id, want)) in tests.iter().enumerate() {
196 let got = label_id::<CS>(*cmd_id, name, *policy_id);
197 let want = LabelId::decode(*want).unwrap();
198 assert_eq!(got, want, "#{i}");
199 }
200 }
201
202 #[test]
204 fn test_role_id() {
205 let tests = [
206 (
207 CmdId::default(),
208 "foo",
209 PolicyId::default(),
210 "BoukxZv6twB39TdXkzMafUxsT1uvpmMJbr6nsKLBg7VT",
211 ),
212 (
213 CmdId::default(),
214 "bar",
215 PolicyId::default(),
216 "CEEjmy5R6Q7RXBqFtt1nrh597Ytr7bCc2aEWJfixEp9K",
217 ),
218 (
219 CmdId::from_bytes([b'A'; 32]),
220 "bar",
221 PolicyId::default(),
222 "9NEW3iaJim8iipkeBCJPJ3v75pEH92iLtrqo8sddkqER",
223 ),
224 (
225 CmdId::from_bytes([b'A'; 32]),
226 "baz",
227 PolicyId::from_bytes([b'B'; 32]),
228 "4sVA51vurQexYL8NFxGYnhj7RTf51udZg7Qd1dhsgBnx",
229 ),
230 ];
231 for (i, (cmd_id, name, policy_id, want)) in tests.iter().enumerate() {
232 let got = role_id::<CS>(*cmd_id, name, *policy_id);
233 let want = RoleId::decode(*want).unwrap();
234 assert_eq!(got, want, "#{i}");
235 }
236 }
237}