diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/Cargo.toml | 10 | ||||
| -rw-r--r-- | common/src/lib.rs | 348 |
2 files changed, 0 insertions, 358 deletions
diff --git a/common/Cargo.toml b/common/Cargo.toml deleted file mode 100644 index 20d9bdd..0000000 --- a/common/Cargo.toml +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | name = "common" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | chrono = "0.4" | ||
| 8 | hmac-sha256 = "1.1" | ||
| 9 | subtle = "2.5" | ||
| 10 | serde = { version = "1", features = ["derive"] } | ||
diff --git a/common/src/lib.rs b/common/src/lib.rs deleted file mode 100644 index 917f566..0000000 --- a/common/src/lib.rs +++ /dev/null | |||
| @@ -1,348 +0,0 @@ | |||
| 1 | use chrono::{DateTime, Utc}; | ||
| 2 | use serde::{de, Deserialize, Serialize}; | ||
| 3 | use std::{ | ||
| 4 | fmt::{self, Write}, | ||
| 5 | ops, | ||
| 6 | str::FromStr, | ||
| 7 | }; | ||
| 8 | use subtle::ConstantTimeEq; | ||
| 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 | |||
| 31 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] | ||
| 32 | #[repr(u8)] | ||
| 33 | pub enum Operation { | ||
| 34 | #[serde(rename = "download")] | ||
| 35 | Download = 1, | ||
| 36 | #[serde(rename = "upload")] | ||
| 37 | Upload = 2, | ||
| 38 | } | ||
| 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 | |||
| 63 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
| 64 | pub struct ParseOperationError; | ||
| 65 | |||
| 66 | impl fmt::Display for ParseOperationError { | ||
| 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 68 | write!(f, "operation should be 'download' or 'upload'") | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | impl FromStr for Operation { | ||
| 73 | type Err = ParseOperationError; | ||
| 74 | |||
| 75 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| 76 | match s { | ||
| 77 | "upload" => Ok(Self::Upload), | ||
| 78 | "download" => Ok(Self::Download), | ||
| 79 | _ => Err(ParseOperationError), | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// None means out of range. | ||
| 85 | fn decode_nibble(c: u8) -> Option<u8> { | ||
| 86 | if c.is_ascii_digit() { | ||
| 87 | Some(c - b'0') | ||
| 88 | } else if (b'a'..=b'f').contains(&c) { | ||
| 89 | Some(c - b'a' + 10) | ||
| 90 | } else if (b'A'..=b'F').contains(&c) { | ||
| 91 | Some(c - b'A' + 10) | ||
| 92 | } else { | ||
| 93 | None | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] | ||
| 98 | pub struct HexByte(pub u8); | ||
| 99 | |||
| 100 | impl<'de> Deserialize<'de> for HexByte { | ||
| 101 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| 102 | where | ||
| 103 | D: serde::Deserializer<'de>, | ||
| 104 | { | ||
| 105 | let str = <&str>::deserialize(deserializer)?; | ||
| 106 | let &[b1, b2] = str.as_bytes() else { | ||
| 107 | return Err(de::Error::invalid_length( | ||
| 108 | str.len(), | ||
| 109 | &"two hexadecimal characters", | ||
| 110 | )); | ||
| 111 | }; | ||
| 112 | let (Some(b1), Some(b2)) = (decode_nibble(b1), decode_nibble(b2)) else { | ||
| 113 | return Err(de::Error::invalid_value( | ||
| 114 | de::Unexpected::Str(str), | ||
| 115 | &"two hexadecimal characters", | ||
| 116 | )); | ||
| 117 | }; | ||
| 118 | Ok(HexByte((b1 << 4) | b2)) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | impl fmt::Display for HexByte { | ||
| 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 124 | let &HexByte(b) = self; | ||
| 125 | HexFmt(&[b]).fmt(f) | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
| 130 | pub enum ParseHexError { | ||
| 131 | UnevenNibbles, | ||
| 132 | InvalidCharacter, | ||
| 133 | TooShort, | ||
| 134 | TooLong, | ||
| 135 | } | ||
| 136 | |||
| 137 | impl fmt::Display for ParseHexError { | ||
| 138 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 139 | match self { | ||
| 140 | Self::UnevenNibbles => { | ||
| 141 | write!(f, "uneven amount of nibbles (chars in range [a-zA-Z0-9])") | ||
| 142 | } | ||
| 143 | Self::InvalidCharacter => write!(f, "non-hex character encountered"), | ||
| 144 | Self::TooShort => write!(f, "unexpected end of hex sequence"), | ||
| 145 | Self::TooLong => write!(f, "longer hex sequence than expected"), | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | #[derive(Debug)] | ||
| 151 | pub enum ReadHexError { | ||
| 152 | Io(std::io::Error), | ||
| 153 | Format(ParseHexError), | ||
| 154 | } | ||
| 155 | |||
| 156 | impl fmt::Display for ReadHexError { | ||
| 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 158 | match self { | ||
| 159 | Self::Io(e) => e.fmt(f), | ||
| 160 | Self::Format(e) => e.fmt(f), | ||
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | fn parse_hex_exact(value: &str, buf: &mut [u8]) -> Result<(), ParseHexError> { | ||
| 166 | if value.bytes().len() % 2 == 1 { | ||
| 167 | return Err(ParseHexError::UnevenNibbles); | ||
| 168 | } | ||
| 169 | if value.bytes().len() < 2 * buf.len() { | ||
| 170 | return Err(ParseHexError::TooShort); | ||
| 171 | } | ||
| 172 | if value.bytes().len() > 2 * buf.len() { | ||
| 173 | return Err(ParseHexError::TooLong); | ||
| 174 | } | ||
| 175 | for (i, c) in value.bytes().enumerate() { | ||
| 176 | if let Some(b) = decode_nibble(c) { | ||
| 177 | if i % 2 == 0 { | ||
| 178 | buf[i / 2] = b << 4; | ||
| 179 | } else { | ||
| 180 | buf[i / 2] |= b; | ||
| 181 | } | ||
| 182 | } else { | ||
| 183 | return Err(ParseHexError::InvalidCharacter); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | Ok(()) | ||
| 187 | } | ||
| 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 | |||
| 196 | pub struct SafeByteArray<const N: usize> { | ||
| 197 | inner: [u8; N], | ||
| 198 | } | ||
| 199 | |||
| 200 | impl<const N: usize> SafeByteArray<N> { | ||
| 201 | pub fn new() -> Self { | ||
| 202 | Self { inner: [0; N] } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | impl<const N: usize> Default for SafeByteArray<N> { | ||
| 207 | fn default() -> Self { | ||
| 208 | Self::new() | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<const N: usize> AsRef<[u8]> for SafeByteArray<N> { | ||
| 213 | fn as_ref(&self) -> &[u8] { | ||
| 214 | &self.inner | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | impl<const N: usize> AsMut<[u8]> for SafeByteArray<N> { | ||
| 219 | fn as_mut(&mut self) -> &mut [u8] { | ||
| 220 | &mut self.inner | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | impl<const N: usize> Drop for SafeByteArray<N> { | ||
| 225 | fn drop(&mut self) { | ||
| 226 | self.inner.fill(0) | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | impl<const N: usize> FromStr for SafeByteArray<N> { | ||
| 231 | type Err = ParseHexError; | ||
| 232 | |||
| 233 | fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
| 234 | let mut sba = Self { inner: [0u8; N] }; | ||
| 235 | parse_hex_exact(value, &mut sba.inner)?; | ||
| 236 | Ok(sba) | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | pub struct HexFmt<B: AsRef<[u8]>>(pub B); | ||
| 241 | |||
| 242 | impl<B: AsRef<[u8]>> fmt::Display for HexFmt<B> { | ||
| 243 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 244 | let HexFmt(buf) = self; | ||
| 245 | for b in buf.as_ref() { | ||
| 246 | let (high, low) = (b >> 4, b & 0xF); | ||
| 247 | let highc = if high < 10 { | ||
| 248 | high + b'0' | ||
| 249 | } else { | ||
| 250 | high - 10 + b'a' | ||
| 251 | }; | ||
| 252 | let lowc = if low < 10 { | ||
| 253 | low + b'0' | ||
| 254 | } else { | ||
| 255 | low - 10 + b'a' | ||
| 256 | }; | ||
| 257 | f.write_char(highc as char)?; | ||
| 258 | f.write_char(lowc as char)?; | ||
| 259 | } | ||
| 260 | Ok(()) | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | #[derive(Debug, Copy, Clone)] | ||
| 265 | pub struct Digest<const N: usize> { | ||
| 266 | inner: [u8; N], | ||
| 267 | } | ||
| 268 | |||
| 269 | impl<const N: usize> ops::Index<usize> for Digest<N> { | ||
| 270 | type Output = u8; | ||
| 271 | |||
| 272 | fn index(&self, index: usize) -> &Self::Output { | ||
| 273 | &self.inner[index] | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | impl<const N: usize> Digest<N> { | ||
| 278 | pub fn as_bytes(&self) -> &[u8; N] { | ||
| 279 | &self.inner | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | impl<const N: usize> fmt::Display for Digest<N> { | ||
| 284 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| 285 | HexFmt(&self.inner).fmt(f) | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | impl<const N: usize> Digest<N> { | ||
| 290 | pub fn new(data: [u8; N]) -> Self { | ||
| 291 | Self { inner: data } | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | impl<const N: usize> From<[u8; N]> for Digest<N> { | ||
| 296 | fn from(value: [u8; N]) -> Self { | ||
| 297 | Self::new(value) | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | impl<const N: usize> From<Digest<N>> for [u8; N] { | ||
| 302 | fn from(val: Digest<N>) -> Self { | ||
| 303 | val.inner | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | impl<const N: usize> FromStr for Digest<N> { | ||
| 308 | type Err = ParseHexError; | ||
| 309 | |||
| 310 | fn from_str(value: &str) -> Result<Self, Self::Err> { | ||
| 311 | let mut buf = [0u8; N]; | ||
| 312 | parse_hex_exact(value, &mut buf)?; | ||
| 313 | Ok(buf.into()) | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | impl<const N: usize> ConstantTimeEq for Digest<N> { | ||
| 318 | fn ct_eq(&self, other: &Self) -> subtle::Choice { | ||
| 319 | self.inner.ct_eq(&other.inner) | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | impl<const N: usize> PartialEq for Digest<N> { | ||
| 324 | fn eq(&self, other: &Self) -> bool { | ||
| 325 | self.ct_eq(other).into() | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | impl<const N: usize> Eq for Digest<N> {} | ||
| 330 | |||
| 331 | impl<'de, const N: usize> Deserialize<'de> for Digest<N> { | ||
| 332 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| 333 | where | ||
| 334 | D: serde::Deserializer<'de>, | ||
| 335 | { | ||
| 336 | let hex = <&str>::deserialize(deserializer)?; | ||
| 337 | Digest::from_str(hex).map_err(de::Error::custom) | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | impl<const N: usize> Serialize for Digest<N> { | ||
| 342 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
| 343 | where | ||
| 344 | S: serde::Serializer, | ||
| 345 | { | ||
| 346 | serializer.serialize_str(&format!("{self}")) | ||
| 347 | } | ||
| 348 | } | ||