1use core::borrow::Borrow as _;
4
5use spideroak_crypto::hash::{Digest, Hash};
6
7use crate::{
8 aranya::{Signature, SigningKeyId},
9 ciphersuite::{CipherSuite, CipherSuiteExt as _},
10 id::{IdExt as _, custom_id},
11};
12
13custom_id! {
14 pub struct GroupId;
16}
17
18custom_id! {
19 pub struct PolicyId;
21}
22
23custom_id! {
24 pub struct CmdId;
26}
27
28pub(crate) fn cmd_id<CS: CipherSuite>(
30 cmd: &Digest<<CS::Hash as Hash>::DigestSize>,
31 sig: &Signature<CS>,
32) -> CmdId {
33 CmdId::new::<CS>(
39 b"PolicyCommandId-v1",
40 [cmd.as_bytes(), sig.raw_sig().borrow()],
41 )
42}
43
44pub fn merge_cmd_id<CS: CipherSuite>(left: CmdId, right: CmdId) -> CmdId {
46 CmdId::new::<CS>(b"MergeCommandId-v1", [left.as_bytes(), right.as_bytes()])
52}
53
54#[derive(Copy, Clone, Debug)]
56pub struct Cmd<'a> {
57 pub data: &'a [u8],
59 pub name: &'a str,
63 pub parent_id: &'a CmdId,
65}
66
67impl Cmd<'_> {
68 pub(crate) fn digest<CS: CipherSuite>(
71 &self,
72 author: SigningKeyId,
73 ) -> Digest<<CS::Hash as Hash>::DigestSize> {
74 CS::tuple_hash(
85 b"SignPolicyCommand-v1",
86 [
87 author.as_bytes(),
89 self.name.as_bytes(),
91 self.parent_id.as_bytes(),
93 self.data,
95 ],
96 )
97 }
98}
99
100custom_id! {
101 pub struct RoleId;
103}
104
105pub fn role_id<CS: CipherSuite>(cmd_id: CmdId, name: &str, policy_id: PolicyId) -> RoleId {
110 RoleId::new::<CS>(
117 b"RoleId-v1",
118 [cmd_id.as_bytes(), name.as_bytes(), policy_id.as_bytes()],
119 )
120}
121
122custom_id! {
123 pub struct LabelId;
125}
126
127pub fn label_id<CS: CipherSuite>(cmd_id: CmdId, name: &str, policy_id: PolicyId) -> LabelId {
132 LabelId::new::<CS>(
139 b"LabelId-v1",
140 [cmd_id.as_bytes(), name.as_bytes(), policy_id.as_bytes()],
141 )
142}
143
144#[cfg(test)]
145mod tests {
146 use spideroak_crypto::{ed25519::Ed25519, rust};
147
148 use super::*;
149 use crate::{default::DhKemP256HkdfSha256, test_util::TestCs};
150
151 type CS = TestCs<
152 rust::Aes256Gcm,
153 rust::Sha256,
154 rust::HkdfSha512,
155 DhKemP256HkdfSha256,
156 rust::HmacSha512,
157 Ed25519,
158 >;
159
160 #[test]
162 fn test_label_id() {
163 let tests = [
164 (
165 CmdId::default(),
166 "foo",
167 PolicyId::default(),
168 "C1PupQYTjr2ouZ3DohnRFEaHR4yoTnMkarbBK4TGhJoi",
169 ),
170 (
171 CmdId::default(),
172 "bar",
173 PolicyId::default(),
174 "Eq71P2UhRVMt7R1s1ZB6m1kSuuzwBZwAd21BEv3gmtBC",
175 ),
176 (
177 CmdId::from_bytes([b'A'; 32]),
178 "bar",
179 PolicyId::default(),
180 "B4XqE83yLS1i8AiyMxGKo2wtrwvqrhUers5ou3eRfH8z",
181 ),
182 (
183 CmdId::from_bytes([b'A'; 32]),
184 "baz",
185 PolicyId::from_bytes([b'B'; 32]),
186 "ACnKJXFwd9e2tSnakXgP8SMiYHBSQLUetWgRjHjyQo8y",
187 ),
188 ];
189 for (i, (cmd_id, name, policy_id, want)) in tests.iter().enumerate() {
190 let got = label_id::<CS>(*cmd_id, name, *policy_id);
191 let want = LabelId::decode(*want).unwrap();
192 assert_eq!(got, want, "#{i}");
193 }
194 }
195
196 #[test]
198 fn test_role_id() {
199 let tests = [
200 (
201 CmdId::default(),
202 "foo",
203 PolicyId::default(),
204 "BoukxZv6twB39TdXkzMafUxsT1uvpmMJbr6nsKLBg7VT",
205 ),
206 (
207 CmdId::default(),
208 "bar",
209 PolicyId::default(),
210 "CEEjmy5R6Q7RXBqFtt1nrh597Ytr7bCc2aEWJfixEp9K",
211 ),
212 (
213 CmdId::from_bytes([b'A'; 32]),
214 "bar",
215 PolicyId::default(),
216 "9NEW3iaJim8iipkeBCJPJ3v75pEH92iLtrqo8sddkqER",
217 ),
218 (
219 CmdId::from_bytes([b'A'; 32]),
220 "baz",
221 PolicyId::from_bytes([b'B'; 32]),
222 "4sVA51vurQexYL8NFxGYnhj7RTf51udZg7Qd1dhsgBnx",
223 ),
224 ];
225 for (i, (cmd_id, name, policy_id, want)) in tests.iter().enumerate() {
226 let got = role_id::<CS>(*cmd_id, name, *policy_id);
227 let want = RoleId::decode(*want).unwrap();
228 assert_eq!(got, want, "#{i}");
229 }
230 }
231}