pub use hessra_token_authz::{
add_multi_party_attestation,
add_multi_party_attestation_to_token,
add_service_node_attestation,
add_prefix_restriction,
add_prefix_restriction_to_token,
biscuit_key_from_string,
create_biscuit,
create_multi_party_biscuit,
create_multi_party_biscuit_with_time,
create_multi_party_token,
create_multi_party_token_with_time,
create_raw_multi_party_biscuit,
create_service_chain_biscuit,
create_service_chain_token,
create_service_chain_token_with_time,
create_token,
create_token_with_time,
verify_biscuit_local,
verify_service_chain_biscuit_local,
verify_service_chain_token_local,
verify_token_local,
AuthorizationVerifier,
ServiceNode,
};
pub use hessra_token_core::{
decode_token, encode_token, parse_token, public_key_from_pem_file, Biscuit, KeyPair, PublicKey,
TokenError, TokenTimeConfig,
};
#[cfg(test)]
mod tests {
use super::*;
use biscuit_auth::macros::biscuit;
use serde_json::Value;
use std::fs;
#[test]
fn test_verify_biscuit_local() {
let keypair = KeyPair::new();
let public_key = keypair.public();
let biscuit_builder = biscuit!(
r#"
right("alice", "resource1", "read");
"#
);
let biscuit = biscuit_builder.build(&keypair).unwrap();
let token_bytes = biscuit.to_vec().unwrap();
let result = verify_biscuit_local(
token_bytes,
public_key,
"alice".to_string(),
"resource1".to_string(),
"read".to_string(),
);
assert!(result.is_ok());
}
#[test]
fn test_verify_service_chain_biscuit() {
let root_keypair = KeyPair::new();
let service_keypair = KeyPair::new();
let service_public_key_hex = hex::encode(service_keypair.public().to_bytes());
let service_public_key_str = format!("ed25519/{}", service_public_key_hex);
let biscuit_builder = biscuit!(
r#"
right("alice", "resource1", "write");
node("resource1", "service1");
"#
);
let biscuit = biscuit_builder.build(&root_keypair).unwrap();
let token_bytes = biscuit.to_vec().unwrap();
let service_nodes = vec![ServiceNode {
component: "service1".to_string(),
public_key: service_public_key_str,
}];
let result = verify_service_chain_biscuit_local(
token_bytes,
root_keypair.public(),
"alice".to_string(),
"resource1".to_string(),
"write".to_string(),
service_nodes,
None,
);
assert!(result.is_ok());
}
#[test]
fn test_add_service_node_attestation() {
let root_keypair = KeyPair::new();
let service_keypair = KeyPair::new();
let biscuit_builder = biscuit!(
r#"
right("alice", "resource1", "read");
right("alice", "resource1", "write");
"#
);
let biscuit = biscuit_builder.build(&root_keypair).unwrap();
let token_bytes = biscuit.to_vec().unwrap();
let attested_token = add_service_node_attestation(
token_bytes,
root_keypair.public(),
"resource1",
&service_keypair,
);
assert!(attested_token.is_ok());
let result = verify_biscuit_local(
attested_token.unwrap(),
root_keypair.public(),
"alice".to_string(),
"resource1".to_string(),
"read".to_string(),
);
assert!(result.is_ok());
}
#[test]
fn test_base64_utils() {
let keypair = KeyPair::new();
let biscuit_builder = biscuit!(
r#"
right("alice", "resource1", "read");
"#
);
let biscuit = biscuit_builder.build(&keypair).unwrap();
let original_bytes = biscuit.to_vec().unwrap();
let encoded = encode_token(&original_bytes);
assert!(!encoded.is_empty());
let decoded = decode_token(&encoded).unwrap();
assert_eq!(original_bytes, decoded);
let result = decode_token("invalid-base64!");
assert!(result.is_err());
}
#[test]
fn test_verify_token_string() {
let keypair = KeyPair::new();
let biscuit_builder = biscuit!(
r#"
right("alice", "resource1", "read");
right("alice", "resource1", "write");
check if subject($sub), resource($res), operation($op), right($sub, $res, $op);
"#
);
let biscuit = biscuit_builder.build(&keypair).unwrap();
let token_bytes = biscuit.to_vec().unwrap();
let token_string = encode_token(&token_bytes);
let result = verify_token_local(
&token_string,
keypair.public(),
"alice",
"resource1",
"read",
);
assert!(result.is_ok());
let result =
verify_token_local(&token_string, keypair.public(), "bob", "resource1", "read");
assert!(result.is_err());
}
#[test]
fn test_token_verification_from_json() {
let json_data =
fs::read_to_string("tests/test_tokens.json").expect("Failed to read test_tokens.json");
let tokens: Value =
serde_json::from_str(&json_data).expect("Failed to parse test_tokens.json");
let public_key = public_key_from_pem_file("tests/hessra_key.pem")
.expect("Failed to load test public key");
for token_value in tokens["tokens"].as_array().unwrap() {
let name = token_value["name"].as_str().unwrap();
let token_string = token_value["token"].as_str().unwrap();
let metadata = &token_value["metadata"];
let subject = metadata["subject"].as_str().unwrap();
let resource = metadata["resource"].as_str().unwrap();
let expected_result = metadata["expected_result"].as_bool().unwrap();
let description = metadata["description"].as_str().unwrap_or("No description");
println!("Testing token '{}': {}", name, description);
let result = parse_token(token_string, public_key).and_then(|biscuit| {
println!("Token blocks: {}", biscuit.print());
if metadata["type"].as_str().unwrap() == "singleton" {
verify_token_local(token_string, public_key, subject, resource, "read")
} else {
let service_nodes = vec![
ServiceNode {
component: "auth_service".to_string(),
public_key: "ed25519/0123456789abcdef0123456789abcdef".to_string(),
},
ServiceNode {
component: "payment_service".to_string(),
public_key: "ed25519/fedcba9876543210fedcba9876543210".to_string(),
},
];
verify_service_chain_token_local(
token_string,
public_key,
subject,
resource,
"read",
service_nodes,
None,
)
}
});
let verification_succeeded = result.is_ok();
assert_eq!(
verification_succeeded, expected_result,
"Token '{}' verification resulted in {}, expected: {} - {}",
name, verification_succeeded, expected_result, description
);
println!(
"✓ Token '{}' - Verification: {}",
name,
if verification_succeeded == expected_result {
"PASSED"
} else {
"FAILED"
}
);
}
}
#[test]
fn test_service_chain_tokens_from_json() {
let json_data =
fs::read_to_string("tests/test_tokens.json").expect("Failed to read test_tokens.json");
let tokens: Value =
serde_json::from_str(&json_data).expect("Failed to parse test_tokens.json");
let public_key = public_key_from_pem_file("tests/hessra_key.pem")
.expect("Failed to load test public key");
if let Some(tokens_array) = tokens["tokens"].as_array() {
if let Some(order_service_token) = tokens_array
.iter()
.find(|t| t["name"].as_str().unwrap() == "argo-cli1_access_order_service")
{
let token_string = order_service_token["token"].as_str().unwrap();
let subject = order_service_token["metadata"]["subject"].as_str().unwrap();
let resource = order_service_token["metadata"]["resource"]
.as_str()
.unwrap();
let expected_result = order_service_token["metadata"]["expected_result"]
.as_bool()
.unwrap();
let service_nodes = vec![
ServiceNode {
component: "auth_service".to_string(),
public_key: "ed25519/0123456789abcdef0123456789abcdef".to_string(),
},
ServiceNode {
component: "payment_service".to_string(),
public_key: "ed25519/fedcba9876543210fedcba9876543210".to_string(),
},
];
let result = verify_service_chain_token_local(
token_string,
public_key,
subject,
resource,
"read",
service_nodes,
None,
);
assert_eq!(
result.is_ok(),
expected_result,
"Service chain verification for '{}' resulted in {}, expected: {}",
order_service_token["name"].as_str().unwrap(),
result.is_ok(),
expected_result
);
}
}
}
#[test]
fn test_service_chain_lifecycle() {
let json_data = fs::read_to_string("tests/service_chain_tokens.json")
.expect("Failed to read service_chain_tokens.json");
let tokens: Value =
serde_json::from_str(&json_data).expect("Failed to parse service_chain_tokens.json");
let initial_token = tokens["tokens"][0]["token"].as_str().unwrap();
let token_after_auth = tokens["tokens"][1]["token"].as_str().unwrap();
let token_after_payment = tokens["tokens"][2]["token"].as_str().unwrap();
let final_token = tokens["tokens"][3]["token"].as_str().unwrap();
let subject = "uri:urn:test:argo-cli1";
let resource = "order_service";
let root_public_key = public_key_from_pem_file("tests/hessra_key.pem")
.expect("Failed to load test public key");
let auth_service_pk_str = tokens["tokens"][1]["metadata"]["service_nodes"][0]["public_key"]
.as_str()
.unwrap();
let payment_service_pk_str = tokens["tokens"][2]["metadata"]["service_nodes"][1]
["public_key"]
.as_str()
.unwrap();
let order_service_pk_str = tokens["tokens"][3]["metadata"]["service_nodes"][2]
["public_key"]
.as_str()
.unwrap();
let result = verify_token_local(initial_token, root_public_key, subject, resource, "read");
assert!(result.is_ok(), "Initial token verification failed");
let service_nodes_for_payment = vec![ServiceNode {
component: "auth_service".to_string(),
public_key: auth_service_pk_str.to_string(),
}];
let result = verify_service_chain_token_local(
token_after_auth,
root_public_key,
subject,
resource,
"read",
service_nodes_for_payment.clone(),
None,
);
assert!(
result.is_ok(),
"Token with auth attestation verification failed"
);
let service_nodes_for_order = vec![
ServiceNode {
component: "auth_service".to_string(),
public_key: auth_service_pk_str.to_string(),
},
ServiceNode {
component: "payment_service".to_string(),
public_key: payment_service_pk_str.to_string(),
},
];
let result = verify_service_chain_token_local(
token_after_payment,
root_public_key,
subject,
resource,
"read",
service_nodes_for_order.clone(),
None,
);
assert!(
result.is_ok(),
"Token with payment attestation verification failed"
);
let service_nodes_complete = vec![
ServiceNode {
component: "auth_service".to_string(),
public_key: auth_service_pk_str.to_string(),
},
ServiceNode {
component: "payment_service".to_string(),
public_key: payment_service_pk_str.to_string(),
},
ServiceNode {
component: "order_service".to_string(),
public_key: order_service_pk_str.to_string(),
},
];
let result = verify_service_chain_token_local(
final_token,
root_public_key,
subject,
resource,
"read",
service_nodes_complete.clone(),
None,
);
if result.is_err() {
println!("Error details: {:?}", result);
let decoded_final = decode_token(final_token).unwrap();
if let Ok(biscuit) = Biscuit::from(&decoded_final, root_public_key) {
println!("Token blocks: {}", biscuit.print());
} else {
println!("Failed to parse token");
}
}
assert!(result.is_ok(), "Final token verification failed");
let result = verify_service_chain_token_local(
token_after_auth,
root_public_key,
subject,
resource,
"read",
service_nodes_complete,
None,
);
assert!(
result.is_err(),
"Incomplete service chain should be rejected"
);
}
#[test]
fn test_multi_party_token_verification_lifecycle() {
let subject = "test@test.com".to_owned();
let resource = "res1".to_string();
let operation = "read".to_string();
let root = KeyPair::new();
let public_key = root.public();
let approval_service_key = KeyPair::new();
let approval_service_public_key = hex::encode(approval_service_key.public().to_bytes());
let approval_service_public_key = format!("ed25519/{}", approval_service_public_key);
let approval_service_node = ServiceNode {
component: "approval_service".to_string(),
public_key: approval_service_public_key.clone(),
};
let nodes = vec![approval_service_node];
let token = create_multi_party_biscuit(
subject.clone(),
resource.clone(),
operation.clone(),
root,
&nodes,
);
assert!(token.is_ok(), "Failed to create multi-party token");
let token = token.unwrap();
let token_string = encode_token(&token);
println!("✓ Multi-party token created successfully");
let result = verify_token_local(&token_string, public_key, &subject, &resource, &operation);
assert!(
result.is_err(),
"Multi-party token should fail verification without attestation"
);
println!("✓ Unattested multi-party token correctly failed verification");
let attested_token = add_multi_party_attestation(
token,
public_key,
"approval_service".to_string(),
approval_service_key,
);
assert!(
attested_token.is_ok(),
"Failed to add multi-party attestation"
);
let attested_token = attested_token.unwrap();
let attested_token_string = encode_token(&attested_token);
println!("✓ Multi-party attestation added successfully");
let result = verify_token_local(
&attested_token_string,
public_key,
&subject,
&resource,
&operation,
);
assert!(
result.is_ok(),
"Attested multi-party token should pass verification"
);
println!("✓ Attested multi-party token correctly passed verification");
let wrong_service_key = KeyPair::new();
let wrong_attested_token = add_multi_party_attestation(
decode_token(&token_string).unwrap(),
public_key,
"wrong_service".to_string(),
wrong_service_key,
);
assert!(wrong_attested_token.is_ok(), "Attestation should succeed");
let wrong_attested_token_string = encode_token(&wrong_attested_token.unwrap());
let result = verify_token_local(
&wrong_attested_token_string,
public_key,
&subject,
&resource,
&operation,
);
assert!(
result.is_err(),
"Token attested by wrong namespace should fail verification"
);
println!("✓ Token attested by wrong namespace correctly failed verification");
}
#[test]
fn test_multi_party_token_with_multiple_parties() {
let subject = "test@test.com".to_owned();
let resource = "sensitive_resource".to_string();
let operation = "admin".to_string();
let root = KeyPair::new();
let public_key = root.public();
let legal_dept_key = KeyPair::new();
let legal_dept_public_key = hex::encode(legal_dept_key.public().to_bytes());
let legal_dept_public_key = format!("ed25519/{}", legal_dept_public_key);
let legal_dept_node = ServiceNode {
component: "legal_dept".to_string(),
public_key: legal_dept_public_key.clone(),
};
let security_team_key = KeyPair::new();
let security_team_public_key = hex::encode(security_team_key.public().to_bytes());
let security_team_public_key = format!("ed25519/{}", security_team_public_key);
let security_team_node = ServiceNode {
component: "security_team".to_string(),
public_key: security_team_public_key.clone(),
};
let nodes = vec![legal_dept_node, security_team_node];
let token = create_multi_party_biscuit(
subject.clone(),
resource.clone(),
operation.clone(),
root,
&nodes,
);
assert!(token.is_ok(), "Failed to create multi-party token");
let token = token.unwrap();
let token_string = encode_token(&token);
let result = verify_token_local(&token_string, public_key, &subject, &resource, &operation);
assert!(result.is_err(), "Token should fail without attestations");
let partially_attested_token = add_multi_party_attestation(
decode_token(&token_string).unwrap(),
public_key,
"legal_dept".to_string(),
legal_dept_key,
)
.unwrap();
let partially_attested_token_string = encode_token(&partially_attested_token);
let result = verify_token_local(
&partially_attested_token_string,
public_key,
&subject,
&resource,
&operation,
);
assert!(
result.is_err(),
"Token should fail with only one of two required attestations"
);
let fully_attested_token = add_multi_party_attestation(
partially_attested_token,
public_key,
"security_team".to_string(),
security_team_key,
)
.unwrap();
let fully_attested_token_string = encode_token(&fully_attested_token);
let result = verify_token_local(
&fully_attested_token_string,
public_key,
&subject,
&resource,
&operation,
);
assert!(
result.is_ok(),
"Token should pass with both required attestations"
);
println!("✓ Multi-party token with multiple parties verified successfully");
}
}