Add bearer challenge-response proofs
This commit is contained in:
parent
43433dbf1d
commit
72cd018a3e
11 changed files with 444 additions and 10 deletions
|
|
@ -29,6 +29,23 @@ pub struct BearerAccess {
|
|||
pub may_delegate: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BearerChallenge {
|
||||
pub resource: ResourceId,
|
||||
pub capabilities: Vec<Capability>,
|
||||
pub nonce: String,
|
||||
pub issued_at: UnixMillis,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BearerProof {
|
||||
pub secret: SecretId,
|
||||
pub resource: ResourceId,
|
||||
pub capabilities: Vec<Capability>,
|
||||
pub nonce: String,
|
||||
pub response: String,
|
||||
}
|
||||
|
||||
impl BearerAccess {
|
||||
#[must_use]
|
||||
pub fn resource_scoped(
|
||||
|
|
@ -76,6 +93,43 @@ pub fn validate_bearer_capabilities(capabilities: &[Capability]) -> Result<(), S
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn bearer_response(
|
||||
secret: &SecretId,
|
||||
resource: &ResourceId,
|
||||
capabilities: &[Capability],
|
||||
nonce: &str,
|
||||
) -> String {
|
||||
let key = *blake3::hash(secret.as_str().as_bytes()).as_bytes();
|
||||
let mut capability_strings = capabilities
|
||||
.iter()
|
||||
.map(|capability| capability.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
capability_strings.sort_unstable();
|
||||
let mut message = String::new();
|
||||
message.push_str(resource.as_str());
|
||||
message.push('\0');
|
||||
message.push_str(nonce);
|
||||
for capability in capability_strings {
|
||||
message.push('\0');
|
||||
message.push_str(capability);
|
||||
}
|
||||
blake3::keyed_hash(&key, message.as_bytes())
|
||||
.to_hex()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn verify_bearer_response(
|
||||
secret: &SecretId,
|
||||
resource: &ResourceId,
|
||||
capabilities: &[Capability],
|
||||
nonce: &str,
|
||||
response: &str,
|
||||
) -> bool {
|
||||
bearer_response(secret, resource, capabilities, nonce) == response
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -96,4 +150,35 @@ mod tests {
|
|||
Err(SecretsError::ForbiddenBearerCapability(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bearer_response_is_resource_scoped_and_capability_scoped() {
|
||||
let secret = SecretId::new("bearer:test");
|
||||
let resource = ResourceId::new("resource:kv:prefs");
|
||||
let capabilities = vec![Capability::new("kv.read"), Capability::new("kv.write")];
|
||||
|
||||
let response = bearer_response(&secret, &resource, &capabilities, "nonce");
|
||||
|
||||
assert!(verify_bearer_response(
|
||||
&secret,
|
||||
&resource,
|
||||
&capabilities,
|
||||
"nonce",
|
||||
&response
|
||||
));
|
||||
assert!(!verify_bearer_response(
|
||||
&secret,
|
||||
&ResourceId::new("resource:kv:other"),
|
||||
&capabilities,
|
||||
"nonce",
|
||||
&response
|
||||
));
|
||||
assert!(!verify_bearer_response(
|
||||
&secret,
|
||||
&resource,
|
||||
&[Capability::new("kv.read")],
|
||||
"nonce",
|
||||
&response
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue