aboutsummaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2024-03-30 15:19:49 +0100
committerLibravatar Rutger Broekhoff2024-03-30 15:19:49 +0100
commit5e5cde1624b1b4ffd00efa73935c48e547a5a8d3 (patch)
tree67bceba2d25cf3298cd83b2f4e876752684dd704 /common
parentf033c6889e0071b29e75c551586e8e5da1b556a3 (diff)
downloadgitolfs3-5e5cde1624b1b4ffd00efa73935c48e547a5a8d3.tar.gz
gitolfs3-5e5cde1624b1b4ffd00efa73935c48e547a5a8d3.zip
Restructure authorization flow
Tried to write verify_claims such that the happy path is to the left as much as possible.
Diffstat (limited to 'common')
-rw-r--r--common/src/lib.rs102
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};
8use subtle::ConstantTimeEq; 8use subtle::ConstantTimeEq;
9 9
10#[repr(u8)]
11enum AuthType {
12 BatchApi = 1,
13 Download = 2,
14}
15
16#[derive(Debug, Copy, Clone)]
17pub 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)]
24pub enum SpecificClaims {
25 BatchApi(Operation),
26 Download(Oid),
27}
28
29pub 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)]
12pub enum Operation { 33pub 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.
41pub 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)]
20pub struct ParseOperationError; 64pub struct ParseOperationError;
21 65
@@ -37,12 +81,6 @@ impl FromStr for Operation {
37 } 81 }
38} 82}
39 83
40#[repr(u8)]
41enum AuthType {
42 BatchApi = 1,
43 Download = 2,
44}
45
46/// None means out of range. 84/// None means out of range.
47fn decode_nibble(c: u8) -> Option<u8> { 85fn 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
189pub type Key = SafeByteArray<64>;
190
191pub 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
151pub struct SafeByteArray<const N: usize> { 196pub 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
195pub type Oid = Digest<32>;
196
197#[derive(Debug, Copy, Clone)]
198pub enum SpecificClaims {
199 BatchApi(Operation),
200 Download(Oid),
201}
202
203#[derive(Debug, Copy, Clone)]
204pub 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.
211pub 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
233pub struct HexFmt<B: AsRef<[u8]>>(pub B); 240pub struct HexFmt<B: AsRef<[u8]>>(pub B);
234 241
235impl<B: AsRef<[u8]>> fmt::Display for HexFmt<B> { 242impl<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
343pub type Key = SafeByteArray<64>;
344
345pub 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}