diff options
Diffstat (limited to 'common/src')
-rw-r--r-- | common/src/lib.rs | 102 |
1 files changed, 51 insertions, 51 deletions
diff --git a/common/src/lib.rs b/common/src/lib.rs index c26150d..917f566 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs | |||
@@ -7,6 +7,27 @@ use std::{ | |||
7 | }; | 7 | }; |
8 | use subtle::ConstantTimeEq; | 8 | use subtle::ConstantTimeEq; |
9 | 9 | ||
10 | #[repr(u8)] | ||
11 | enum AuthType { | ||
12 | BatchApi = 1, | ||
13 | Download = 2, | ||
14 | } | ||
15 | |||
16 | #[derive(Debug, Copy, Clone)] | ||
17 | pub struct Claims<'a> { | ||
18 | pub specific_claims: SpecificClaims, | ||
19 | pub repo_path: &'a str, | ||
20 | pub expires_at: DateTime<Utc>, | ||
21 | } | ||
22 | |||
23 | #[derive(Debug, Copy, Clone)] | ||
24 | pub enum SpecificClaims { | ||
25 | BatchApi(Operation), | ||
26 | Download(Oid), | ||
27 | } | ||
28 | |||
29 | pub type Oid = Digest<32>; | ||
30 | |||
10 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] | 31 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] |
11 | #[repr(u8)] | 32 | #[repr(u8)] |
12 | pub enum Operation { | 33 | pub enum Operation { |
@@ -16,6 +37,29 @@ pub enum Operation { | |||
16 | Upload = 2, | 37 | Upload = 2, |
17 | } | 38 | } |
18 | 39 | ||
40 | /// Returns None if the claims are invalid. Repo path length may be no more than 100 bytes. | ||
41 | pub fn generate_tag(claims: Claims, key: impl AsRef<[u8]>) -> Option<Digest<32>> { | ||
42 | if claims.repo_path.len() > 100 { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let mut hmac = hmac_sha256::HMAC::new(key); | ||
47 | match claims.specific_claims { | ||
48 | SpecificClaims::BatchApi(operation) => { | ||
49 | hmac.update([AuthType::BatchApi as u8]); | ||
50 | hmac.update([operation as u8]); | ||
51 | } | ||
52 | SpecificClaims::Download(oid) => { | ||
53 | hmac.update([AuthType::Download as u8]); | ||
54 | hmac.update(oid.as_bytes()); | ||
55 | } | ||
56 | } | ||
57 | hmac.update([claims.repo_path.len() as u8]); | ||
58 | hmac.update(claims.repo_path.as_bytes()); | ||
59 | hmac.update(claims.expires_at.timestamp().to_be_bytes()); | ||
60 | Some(hmac.finalize().into()) | ||
61 | } | ||
62 | |||
19 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | 63 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
20 | pub struct ParseOperationError; | 64 | pub struct ParseOperationError; |
21 | 65 | ||
@@ -37,12 +81,6 @@ impl FromStr for Operation { | |||
37 | } | 81 | } |
38 | } | 82 | } |
39 | 83 | ||
40 | #[repr(u8)] | ||
41 | enum AuthType { | ||
42 | BatchApi = 1, | ||
43 | Download = 2, | ||
44 | } | ||
45 | |||
46 | /// None means out of range. | 84 | /// None means out of range. |
47 | fn decode_nibble(c: u8) -> Option<u8> { | 85 | fn decode_nibble(c: u8) -> Option<u8> { |
48 | if c.is_ascii_digit() { | 86 | if c.is_ascii_digit() { |
@@ -148,6 +186,13 @@ fn parse_hex_exact(value: &str, buf: &mut [u8]) -> Result<(), ParseHexError> { | |||
148 | Ok(()) | 186 | Ok(()) |
149 | } | 187 | } |
150 | 188 | ||
189 | pub type Key = SafeByteArray<64>; | ||
190 | |||
191 | pub fn load_key(path: &str) -> Result<Key, ReadHexError> { | ||
192 | let key_str = std::fs::read_to_string(path).map_err(ReadHexError::Io)?; | ||
193 | key_str.trim().parse().map_err(ReadHexError::Format) | ||
194 | } | ||
195 | |||
151 | pub struct SafeByteArray<const N: usize> { | 196 | pub struct SafeByteArray<const N: usize> { |
152 | inner: [u8; N], | 197 | inner: [u8; N], |
153 | } | 198 | } |
@@ -192,44 +237,6 @@ impl<const N: usize> FromStr for SafeByteArray<N> { | |||
192 | } | 237 | } |
193 | } | 238 | } |
194 | 239 | ||
195 | pub type Oid = Digest<32>; | ||
196 | |||
197 | #[derive(Debug, Copy, Clone)] | ||
198 | pub enum SpecificClaims { | ||
199 | BatchApi(Operation), | ||
200 | Download(Oid), | ||
201 | } | ||
202 | |||
203 | #[derive(Debug, Copy, Clone)] | ||
204 | pub struct Claims<'a> { | ||
205 | pub specific_claims: SpecificClaims, | ||
206 | pub repo_path: &'a str, | ||
207 | pub expires_at: DateTime<Utc>, | ||
208 | } | ||
209 | |||
210 | /// Returns None if the claims are invalid. Repo path length may be no more than 100 bytes. | ||
211 | pub fn generate_tag(claims: Claims, key: impl AsRef<[u8]>) -> Option<Digest<32>> { | ||
212 | if claims.repo_path.len() > 100 { | ||
213 | return None; | ||
214 | } | ||
215 | |||
216 | let mut hmac = hmac_sha256::HMAC::new(key); | ||
217 | match claims.specific_claims { | ||
218 | SpecificClaims::BatchApi(operation) => { | ||
219 | hmac.update([AuthType::BatchApi as u8]); | ||
220 | hmac.update([operation as u8]); | ||
221 | } | ||
222 | SpecificClaims::Download(oid) => { | ||
223 | hmac.update([AuthType::Download as u8]); | ||
224 | hmac.update(oid.as_bytes()); | ||
225 | } | ||
226 | } | ||
227 | hmac.update([claims.repo_path.len() as u8]); | ||
228 | hmac.update(claims.repo_path.as_bytes()); | ||
229 | hmac.update(claims.expires_at.timestamp().to_be_bytes()); | ||
230 | Some(hmac.finalize().into()) | ||
231 | } | ||
232 | |||
233 | pub struct HexFmt<B: AsRef<[u8]>>(pub B); | 240 | pub struct HexFmt<B: AsRef<[u8]>>(pub B); |
234 | 241 | ||
235 | impl<B: AsRef<[u8]>> fmt::Display for HexFmt<B> { | 242 | impl<B: AsRef<[u8]>> fmt::Display for HexFmt<B> { |
@@ -339,10 +346,3 @@ impl<const N: usize> Serialize for Digest<N> { | |||
339 | serializer.serialize_str(&format!("{self}")) | 346 | serializer.serialize_str(&format!("{self}")) |
340 | } | 347 | } |
341 | } | 348 | } |
342 | |||
343 | pub type Key = SafeByteArray<64>; | ||
344 | |||
345 | pub fn load_key(path: &str) -> Result<Key, ReadHexError> { | ||
346 | let key_str = std::fs::read_to_string(path).map_err(ReadHexError::Io)?; | ||
347 | key_str.trim().parse().map_err(ReadHexError::Format) | ||
348 | } | ||