use crate::crypto::VerifyingKey;
use crate::errors::{Error, IdentityAuthedRequestError};
use crate::payloads::IdentityAuthedRequestPayload;
use crate::traits::IdentityProvider;
use crate::{crypto, utils};
const EXPIRY_TIMEOUT: u64 = 60 * 60;
const FUTURE_TIMESTAMP_TOLERANCE: u64 = 5 * 60;
pub fn initiate(
command: &'static str,
identity_provider: &impl IdentityProvider,
) -> IdentityAuthedRequestPayload {
let timestamp = utils::unix_timestamp();
let signature = identity_provider.sign(&command_message_bytes(command, timestamp));
IdentityAuthedRequestPayload {
command,
verifying_key: identity_provider.verifying_key(),
timestamp,
signature,
}
}
pub fn verify(
request: &IdentityAuthedRequestPayload,
verified_parties: &[VerifyingKey],
) -> Result<(), IdentityAuthedRequestError> {
if !verified_parties.contains(&request.verifying_key) {
Err(IdentityAuthedRequestError::Unauthorized(
Error::UnauthorizedParty,
))
} else if request.timestamp + EXPIRY_TIMEOUT < utils::unix_timestamp() {
Err(IdentityAuthedRequestError::Expired)
} else if utils::unix_timestamp() + FUTURE_TIMESTAMP_TOLERANCE < request.timestamp {
Err(IdentityAuthedRequestError::InvalidTimestamp)
} else {
Ok(crypto::verify_signature(
&request.verifying_key,
&command_message_bytes(request.command, request.timestamp),
&request.signature,
)?)
}
}
fn command_message_bytes(command: &str, timestamp: u64) -> Vec<u8> {
utils::prefix_message_bytes(format!("{}{}", command, timestamp).as_bytes())
}
#[cfg(test)]
mod test {
use super::*;
use crate::errors::CryptoError;
use crate::test_utils::MockECDSAIdentityProvider;
#[test]
fn identity_authed_request_initiation_and_verification_works() {
let identity_provider = MockECDSAIdentityProvider::generate();
let payload = initiate("command", &identity_provider);
for (verified_parties, timestamp_modification, signature_modification, expected_result) in [
(vec![identity_provider.verifying_key()], None, None, Ok(())),
(
vec![],
None,
None,
Err(IdentityAuthedRequestError::Unauthorized(
Error::UnauthorizedParty,
)),
),
(
vec![identity_provider.verifying_key()],
Some(-(EXPIRY_TIMEOUT as i64 + 1)),
None,
Err(IdentityAuthedRequestError::Expired),
),
(
vec![identity_provider.verifying_key()],
Some(FUTURE_TIMESTAMP_TOLERANCE as i64 + 1),
None,
Err(IdentityAuthedRequestError::InvalidTimestamp),
),
(
vec![identity_provider.verifying_key()],
None,
Some(identity_provider.sign(b"Hello, world!")),
Err(IdentityAuthedRequestError::Unauthorized(Error::Crypto(
CryptoError::InvalidSignature,
))),
),
] {
let mut modified_payload = payload.clone();
if let Some(delta) = timestamp_modification {
modified_payload.timestamp = (modified_payload.timestamp as i64 + delta) as u64;
}
if let Some(modified_signature) = signature_modification {
modified_payload.signature = modified_signature;
}
let result = verify(&modified_payload, &verified_parties);
assert_eq!(result, expected_result);
}
}
}