1use std::{
7 fmt,
8 time::{Duration, Instant},
9};
10
11use crate::errors::SdkError;
12
13const CAPABILITY_LEN: usize = 32;
14const SPKI_DIGEST_LEN: usize = 32;
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum BootstrapPurpose {
18 Attach,
19 Recover,
20}
21
22#[derive(Clone, Copy, Eq, Hash, PartialEq)]
24pub struct ProcessOwner([u8; 16]);
25
26impl fmt::Debug for ProcessOwner {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 f.write_str("ProcessOwner(<opaque>)")
29 }
30}
31
32impl ProcessOwner {
33 pub fn generate() -> Result<Self, SdkError> {
35 let mut bytes = [0_u8; 16];
36 getrandom::fill(&mut bytes).map_err(|_| SdkError::Authentication)?;
37 Ok(Self(bytes))
38 }
39
40 pub(crate) const fn from_test_bytes(bytes: [u8; 16]) -> Self {
41 Self(bytes)
42 }
43}
44
45#[derive(Clone, Copy, Eq, PartialEq)]
48pub struct VerifiedServer([u8; SPKI_DIGEST_LEN]);
49
50impl fmt::Debug for VerifiedServer {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.write_str("VerifiedServer(<spki-redacted>)")
53 }
54}
55
56impl VerifiedServer {
57 pub(crate) const fn from_test_digest(digest: [u8; SPKI_DIGEST_LEN]) -> Self {
58 Self(digest)
59 }
60}
61
62#[derive(Clone, Copy, Eq, PartialEq)]
63pub struct BootstrapCapability([u8; CAPABILITY_LEN]);
64
65impl fmt::Debug for BootstrapCapability {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.write_str("BootstrapCapability(<redacted>)")
68 }
69}
70
71impl BootstrapCapability {
72 pub(crate) const fn from_test_bytes(bytes: [u8; CAPABILITY_LEN]) -> Self {
73 Self(bytes)
74 }
75}
76
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct BootstrapGrant {
81 owner: ProcessOwner,
82 epoch: u64,
83 purpose: BootstrapPurpose,
84 server: VerifiedServer,
85 capability: BootstrapCapability,
86 expires_at: Instant,
87}
88
89impl BootstrapGrant {
90 pub(crate) const fn from_verified(
91 owner: ProcessOwner,
92 epoch: u64,
93 purpose: BootstrapPurpose,
94 server: VerifiedServer,
95 capability: BootstrapCapability,
96 expires_at: Instant,
97 ) -> Self {
98 Self {
99 owner,
100 epoch,
101 purpose,
102 server,
103 capability,
104 expires_at,
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub struct BootstrapGate {
112 owner: ProcessOwner,
113 epoch: u64,
114 purpose: BootstrapPurpose,
115 expected_server: VerifiedServer,
116 consumed: bool,
117 zero_rtt: bool,
118}
119
120impl BootstrapGate {
121 pub(crate) const fn new(
122 owner: ProcessOwner,
123 epoch: u64,
124 purpose: BootstrapPurpose,
125 expected_server: VerifiedServer,
126 ) -> Self {
127 Self {
128 owner,
129 epoch,
130 purpose,
131 expected_server,
132 consumed: false,
133 zero_rtt: false,
134 }
135 }
136
137 pub const fn zero_rtt_enabled(self) -> bool {
139 self.zero_rtt
140 }
141
142 pub fn consume(&mut self, grant: BootstrapGrant, now: Instant) -> Result<(), SdkError> {
143 if self.consumed {
144 return Err(SdkError::CapabilityReplayed);
145 }
146 if grant.expires_at <= now {
147 return Err(SdkError::CapabilityExpired);
148 }
149 if grant.owner != self.owner || grant.epoch != self.epoch || grant.purpose != self.purpose {
150 return Err(SdkError::CapabilityBindingMismatch);
151 }
152 if grant.server != self.expected_server {
153 return Err(SdkError::HostKeyRejected);
154 }
155 let _ = grant.capability;
158 self.consumed = true;
159 Ok(())
160 }
161}
162
163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
165pub struct BootstrapRequest {
166 pub purpose: BootstrapPurpose,
167 pub epoch: u64,
168 pub timeout: Duration,
169}
170
171impl BootstrapRequest {
172 pub const fn new(purpose: BootstrapPurpose, epoch: u64, timeout: Duration) -> Self {
173 Self {
174 purpose,
175 epoch,
176 timeout,
177 }
178 }
179}
180
181#[doc(hidden)]
185pub mod test_support {
186 use super::*;
187
188 pub fn owner(value: u8) -> ProcessOwner {
189 ProcessOwner::from_test_bytes([value; 16])
190 }
191
192 pub fn server(value: u8) -> VerifiedServer {
193 VerifiedServer::from_test_digest([value; SPKI_DIGEST_LEN])
194 }
195
196 pub fn grant(
197 owner: ProcessOwner,
198 epoch: u64,
199 purpose: BootstrapPurpose,
200 server: VerifiedServer,
201 value: u8,
202 expires_at: Instant,
203 ) -> BootstrapGrant {
204 BootstrapGrant::from_verified(
205 owner,
206 epoch,
207 purpose,
208 server,
209 BootstrapCapability::from_test_bytes([value; CAPABILITY_LEN]),
210 expires_at,
211 )
212 }
213
214 pub fn gate(
215 owner: ProcessOwner,
216 epoch: u64,
217 purpose: BootstrapPurpose,
218 server: VerifiedServer,
219 ) -> BootstrapGate {
220 BootstrapGate::new(owner, epoch, purpose, server)
221 }
222}