use dat::certificate::DatCertificate;
use dat::crypto::{DatCrypto, DatCryptoAlgorithm};
use dat::error::{DatError, DatRetry};
use dat::manager::DatManager;
use dat::signature::{DatSignature, DatSignatureAlgorithm};
use dat::util::now_unix_timestamp;
use std::str::FromStr;
const SIG: DatSignatureAlgorithm = DatSignatureAlgorithm::EcdsaP256;
const CRY: DatCryptoAlgorithm = DatCryptoAlgorithm::IvAes256Gcm;
fn issuable_manager(cid: u64) -> DatManager {
let now = now_unix_timestamp();
let manager = DatManager::new();
manager
.import_certificates(vec![DatCertificate::generate(cid, now - 10, 200, 100, SIG, CRY).unwrap()], true)
.unwrap();
manager
}
fn err_of<T>(r: Result<T, DatError>) -> DatError {
match r {
Ok(_) => panic!("expected an error, got Ok"),
Err(e) => e,
}
}
fn code_of<T>(r: Result<T, DatError>) -> &'static str {
err_of(r).code()
}
#[test]
fn expired_token_is_not_malformed() {
let manager = issuable_manager(1);
let dat = manager.issue("p", "s").unwrap();
let rest = dat.splitn(2, '.').nth(1).unwrap();
let expired = format!("{}.{}", now_unix_timestamp() - 1, rest);
assert_eq!(code_of(manager.parse(expired)), "DAT_TOKEN_EXPIRED");
}
#[test]
fn expire_exactly_now_is_expired() {
let manager = issuable_manager(1);
let dat = manager.issue("p", "s").unwrap();
let rest = dat.splitn(2, '.').nth(1).unwrap();
let at_now = format!("{}.{}", now_unix_timestamp(), rest);
assert_eq!(code_of(manager.parse(at_now)), "DAT_TOKEN_EXPIRED");
}
#[test]
fn malformed_token_shapes() {
let manager = issuable_manager(1);
let dat = manager.issue("p", "s").unwrap();
let parts: Vec<&str> = dat.split('.').collect();
assert_eq!(code_of(manager.parse("1.2.3".to_string())), "DAT_TOKEN_MALFORMED");
assert_eq!(code_of(manager.parse(format!("{dat}.extra"))), "DAT_TOKEN_MALFORMED");
assert_eq!(
code_of(manager.parse(format!("+{}.{}", parts[0], parts[1..].join(".")))),
"DAT_TOKEN_MALFORMED"
);
assert_eq!(
code_of(manager.parse(format!("{}.zz.{}", parts[0], parts[2..].join(".")))),
"DAT_TOKEN_MALFORMED"
);
}
#[test]
fn empty_signature_is_sig_malformed_not_mismatch() {
let manager = issuable_manager(1);
let dat = manager.issue("p", "s").unwrap();
let parts: Vec<&str> = dat.split('.').collect();
let no_sig = format!("{}.", parts[..4].join("."));
assert_eq!(code_of(manager.parse(no_sig)), "DAT_SIG_MALFORMED");
}
#[test]
fn forged_signature_is_sig_mismatch() {
let now = now_unix_timestamp();
let victim = issuable_manager(7);
let attacker = DatManager::new();
attacker
.import_certificates(vec![DatCertificate::generate(7, now - 10, 200, 100, SIG, CRY).unwrap()], true)
.unwrap();
let forged = attacker.issue("p", "s").unwrap();
let err = err_of(victim.parse(forged));
assert_eq!(err.code(), "DAT_SIG_MISMATCH");
assert!(err.security_event(), "위조는 보안 이벤트로 표시되어야 한다");
}
#[test]
fn tampered_secure_is_crypto_tag_mismatch() {
let manager = issuable_manager(1);
let dat = manager.issue("plain", "secure-payload").unwrap();
let mut parts: Vec<String> = dat.split('.').map(str::to_string).collect();
let secure = parts[3].clone();
let last = secure.chars().last().unwrap();
let flipped = if last == 'A' { 'B' } else { 'A' };
parts[3] = format!("{}{}", &secure[..secure.len() - 1], flipped);
let err = err_of(manager.parse_without_verify(parts.join(".")));
assert_eq!(err.code(), "DAT_CRYPTO_TAG_MISMATCH");
assert!(err.security_event());
}
#[test]
fn unknown_cid_is_cert_not_found() {
let manager = issuable_manager(1);
let other = issuable_manager(999);
let dat = other.issue("p", "s").unwrap();
assert_eq!(code_of(manager.parse(dat)), "DAT_CERT_NOT_FOUND");
}
#[test]
fn duplicate_cid_on_import() {
let now = now_unix_timestamp();
let manager = DatManager::new();
let certs = vec![
DatCertificate::generate(5, now - 10, 200, 100, SIG, CRY).unwrap(),
DatCertificate::generate(5, now - 10, 200, 100, SIG, CRY).unwrap(),
];
assert_eq!(code_of(manager.import_certificates(certs, true)), "DAT_CERT_DUPLICATE_CID");
}
#[test]
fn malformed_certificate_shapes() {
assert_eq!(code_of(DatCertificate::from_str("a.b.c")), "DAT_CERT_MALFORMED");
assert_eq!(
code_of(DatCertificate::from_str("zz.1.2.3.ECDSA-P256.IV-AES256-GCM.AAAA.AAAA")),
"DAT_CERT_MALFORMED"
);
assert_eq!(
code_of(DatCertificate::from(1, u64::MAX, 1, 0, DatSignature::generate(SIG).unwrap(), DatCrypto::generate(CRY))),
"DAT_CERT_MALFORMED"
);
}
#[test]
fn no_certificate_at_all() {
let manager = DatManager::new();
let err = manager.issue("p", "s").unwrap_err();
assert_eq!(err.code(), "DAT_MANAGER_NO_CERTIFICATE");
assert_eq!(err.retry(), DatRetry::Transient);
}
#[test]
fn issuance_window_not_yet_open_is_transient() {
let now = now_unix_timestamp();
let manager = DatManager::new();
manager
.import_certificates(vec![DatCertificate::generate(1, now + 3600, 200, 100, SIG, CRY).unwrap()], true)
.unwrap();
let err = manager.issue("p", "s").unwrap_err();
assert_eq!(err.code(), "DAT_MANAGER_NO_ISSUABLE_CERTIFICATE");
assert_eq!(err.cause().unwrap().code(), "DAT_CERT_NOT_YET_ISSUABLE");
assert_eq!(err.retry(), DatRetry::Transient);
}
#[test]
fn issuance_window_closed_is_permanent() {
let now = now_unix_timestamp();
let manager = DatManager::new();
manager
.import_certificates(vec![DatCertificate::generate(1, now - 500, 100, 3600, SIG, CRY).unwrap()], true)
.unwrap();
let err = manager.issue("p", "s").unwrap_err();
assert_eq!(err.code(), "DAT_MANAGER_NO_ISSUABLE_CERTIFICATE");
assert_eq!(err.cause().unwrap().code(), "DAT_CERT_ISSUANCE_ENDED");
assert_eq!(err.retry(), DatRetry::Permanent);
}
#[test]
fn verify_only_certificate_cannot_issue() {
let now = now_unix_timestamp();
let source = DatCertificate::generate(1, now - 10, 200, 100, SIG, CRY).unwrap();
let verify_only = DatCertificate::from_str(&source.export(true).unwrap()).unwrap();
let manager = DatManager::new();
manager.import_certificates(vec![verify_only], true).unwrap();
let err = manager.issue("p", "s").unwrap_err();
assert_eq!(err.code(), "DAT_MANAGER_NO_ISSUABLE_CERTIFICATE");
assert_eq!(err.cause().unwrap().code(), "DAT_CERT_VERIFY_ONLY");
assert_eq!(err.retry(), DatRetry::Permanent);
}
#[test]
fn unknown_algorithm_names() {
assert_eq!(code_of(DatSignatureAlgorithm::from_str("NOPE")), "DAT_CONFIG_ALG_UNSUPPORTED");
assert_eq!(code_of(DatCryptoAlgorithm::from_str("NOPE")), "DAT_CONFIG_ALG_UNSUPPORTED");
}
#[test]
fn wrong_key_size_is_key_invalid() {
assert_eq!(code_of(DatCrypto::from_key(CRY, &[0u8; 7])), "DAT_KEY_INVALID");
assert_eq!(
code_of(DatSignature::from_key(DatSignatureAlgorithm::HmacSha256Mfs, &[0u8; 7])),
"DAT_KEY_INVALID"
);
assert_eq!(code_of(DatSignature::from_key(SIG, &[0u8; 7])), "DAT_KEY_INVALID");
}
#[test]
fn hmac_verify_only_export_is_structurally_unsupported() {
let hmac = DatSignature::generate(DatSignatureAlgorithm::HmacSha256Mfs).unwrap();
assert_eq!(code_of(hmac.export_verify_only_key()), "DAT_KEY_VERIFY_ONLY_UNSUPPORTED");
}
#[test]
fn signing_with_verify_only_key_is_key_missing() {
let source = DatSignature::generate(SIG).unwrap();
let public_only = DatSignature::from_key(SIG, &source.export_verify_only_key().unwrap()).unwrap();
assert_eq!(code_of(public_only.sign(b"body")), "DAT_SIG_KEY_MISSING");
}
#[test]
fn ciphertext_shorter_than_iv() {
let crypto = DatCrypto::generate(CRY);
assert_eq!(code_of(crypto.decrypt(vec![0u8; 5])), "DAT_CRYPTO_DATA_INVALID");
}
#[test]
fn empty_secure_payload_is_not_an_error() {
let crypto = DatCrypto::generate(CRY);
assert!(crypto.encrypt(&[]).unwrap().is_empty());
assert!(crypto.decrypt(vec![]).unwrap().is_empty());
}
#[test]
fn every_code_is_well_formed() {
let samples = [
DatError::TokenMalformed("x"),
DatError::TokenExpired,
DatError::CertExpired,
DatError::CertNotSynced,
DatError::SigMismatch,
DatError::CryptoTagMismatch,
DatError::KeyInvalid("x"),
DatError::ManagerNoCertificate,
DatError::CmsUnauthorized,
DatError::CmsSyncInProgress,
DatError::ConfigAlgUnsupported("x".into()),
DatError::InternalUnavailable("x"),
];
for e in samples {
let code = e.code();
assert!(code.starts_with("DAT_"), "{code} must start with DAT_");
assert!(
code.chars().all(|c| c.is_ascii_uppercase() || c == '_'),
"{code} must be SCREAMING_SNAKE_CASE"
);
assert!(e.to_string().starts_with(code));
}
}
#[test]
fn state_signals_are_not_failures() {
assert_eq!(DatError::CmsSyncInProgress.retry(), DatRetry::State);
assert_eq!(DatError::CmsVersionReset.retry(), DatRetry::State);
}
#[test]
fn permanent_cms_errors_must_not_be_retried() {
for e in [DatError::CmsUnauthorized, DatError::CmsForbidden, DatError::CmsEndpointNotFound] {
assert_eq!(e.retry(), DatRetry::Permanent, "{}", e.code());
}
for e in [DatError::CmsUnreachable("x".into()), DatError::CmsServerError(503)] {
assert_eq!(e.retry(), DatRetry::Transient, "{}", e.code());
}
}