diff options
Diffstat (limited to 'common')
| -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 | } | ||