sz-orm-storage 1.2.2

SZ-ORM Storage Extension - S3 real impl (s3-sdk feature) + 6 in-memory mock providers (Aliyun/Huawei/Qiniu/Tencent/UpYun/Default)
Documentation
//! # 签名算法集成测试
//!
//! 验证 `signing` 模块中各签名原语的正确性,使用标准测试向量(RFC 4231 / RFC 2202 / AWS 官方文档)。
//!
//! ## 启用方式
//!
//! 本测试文件整体在 `s3-compat` 或 `qiniu-kodo` feature 下编译:
//! - `cargo test --features s3-compat -p sz-orm-storage --test signing_test`
//! - `cargo test --features qiniu-kodo -p sz-orm-storage --test signing_test`
//! - `cargo test --features s3-compat,qiniu-kodo -p sz-orm-storage --test signing_test`

#![cfg(any(feature = "s3-compat", feature = "qiniu-kodo"))]

use sz_orm_storage::signing;

// ===================== HMAC-SHA256 测试(RFC 4231)=====================
//
// RFC 4231 Section 4: Test Cases for HMAC-SHA-256

#[test]
fn test_hmac_sha256_rfc4231_case1() {
    // TestCase 1: key=0x0b*20, data="Hi There"
    let key = [0x0bu8; 20];
    let mac = signing::compute_hmac_sha256(&key, b"Hi There");
    assert_eq!(
        hex::encode(&mac),
        "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
    );
}

#[test]
fn test_hmac_sha256_rfc4231_case2() {
    // TestCase 2: key="Jefe", data="what do ya want for nothing?"
    let key = b"Jefe";
    let mac = signing::compute_hmac_sha256(key, b"what do ya want for nothing?");
    assert_eq!(
        hex::encode(&mac),
        "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
    );
}

#[test]
fn test_hmac_sha256_rfc4231_case4() {
    // TestCase 4: key=0x01..0x19 (25 bytes), data=0xcd*50
    let key: Vec<u8> = (1..=25u8).collect();
    let data = vec![0xcdu8; 50];
    let mac = signing::compute_hmac_sha256(&key, &data);
    assert_eq!(
        hex::encode(&mac),
        "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
    );
}

#[test]
fn test_hex_hmac_sha256_returns_hex_string() {
    let key = [0x0bu8; 20];
    let hex_mac = signing::hex_hmac_sha256(&key, b"Hi There");
    assert_eq!(
        hex_mac,
        "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
    );
}

// ===================== SHA256 测试(NIST FIPS 180-4)=====================

#[test]
fn test_sha256_empty_string() {
    assert_eq!(
        signing::hex_sha256(b""),
        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    );
}

#[test]
fn test_sha256_abc() {
    assert_eq!(
        signing::hex_sha256(b"abc"),
        "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
    );
}

#[test]
fn test_sha256_long_message() {
    // "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
    assert_eq!(
        signing::hex_sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
        "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
    );
}

// ===================== AWS Sig V4 签名密钥派生测试=====================
//
// AWS 官方文档示例:
// https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html

#[test]
fn test_derive_signing_key_aws_doc_example_iam() {
    // AWS 官方文档示例:secret=wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
    // date=20150830, region=us-east-1, service=iam
    // 期望签名 key(十六进制):
    // c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9
    let key = signing::derive_signing_key(
        "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
        "20150830",
        "us-east-1",
        "iam",
    );
    assert_eq!(
        hex::encode(key),
        "c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9"
    );
}

#[test]
fn test_derive_signing_key_aws_doc_example_s3() {
    // AWS 官方文档示例(S3 service):
    // secret=wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
    // date=20130524, region=us-east-1, service=s3
    // 注:此处验证结构属性(长度、确定性),精确值对照见 iam 示例
    let key = signing::derive_signing_key(
        "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
        "20130524",
        "us-east-1",
        "s3",
    );
    // 验证派生 key 非空且长度为 32 字节(SHA256 输出)
    assert_eq!(key.len(), 32);
    // 验证确定性:相同输入应产生相同输出
    let key2 = signing::derive_signing_key(
        "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
        "20130524",
        "us-east-1",
        "s3",
    );
    assert_eq!(key, key2);
}

#[test]
fn test_derive_signing_key_different_inputs_produce_different_keys() {
    let key1 = signing::derive_signing_key("secret1", "20260101", "us-east-1", "s3");
    let key2 = signing::derive_signing_key("secret2", "20260101", "us-east-1", "s3");
    assert_ne!(key1, key2);

    let key3 = signing::derive_signing_key("secret1", "20260102", "us-east-1", "s3");
    assert_ne!(key1, key3);

    let key4 = signing::derive_signing_key("secret1", "20260101", "us-west-2", "s3");
    assert_ne!(key1, key4);

    let key5 = signing::derive_signing_key("secret1", "20260101", "us-east-1", "iam");
    assert_ne!(key1, key5);
}

// ===================== UTC 时间组件测试=====================

#[test]
fn test_utc_now_components_format() {
    let (date_stamp, amz_date) = signing::utc_now_components();
    // date_stamp: YYYYMMDD (8 chars)
    assert_eq!(
        date_stamp.len(),
        8,
        "date_stamp should be 8 chars: {}",
        date_stamp
    );
    // amz_date: YYYYMMDDTHHMMSSZ (16 chars)
    assert_eq!(
        amz_date.len(),
        16,
        "amz_date should be 16 chars: {}",
        amz_date
    );
    // amz_date 以 date_stamp 开头
    assert!(
        amz_date.starts_with(&date_stamp),
        "amz_date '{}' should start with date_stamp '{}'",
        amz_date,
        date_stamp
    );
    // amz_date 第 9 个字符是 'T'
    assert_eq!(
        &amz_date[8..9],
        "T",
        "amz_date should have 'T' at position 8"
    );
    // amz_date 以 'Z' 结尾
    assert!(amz_date.ends_with('Z'), "amz_date should end with 'Z'");
}

#[test]
fn test_utc_now_components_all_digits() {
    let (date_stamp, amz_date) = signing::utc_now_components();
    // date_stamp 应全部为数字
    assert!(
        date_stamp.chars().all(|c| c.is_ascii_digit()),
        "date_stamp should be all digits: {}",
        date_stamp
    );
    // amz_date 除 T 和 Z 外应全部为数字
    let amz_digits: String = amz_date.chars().filter(|c| c.is_ascii_digit()).collect();
    assert_eq!(
        amz_digits.len(),
        14,
        "amz_date should have 14 digits: {}",
        amz_date
    );
}

#[test]
fn test_utc_now_components_reasonable_date() {
    let (date_stamp, _) = signing::utc_now_components();
    // 解析年份(前 4 位),应在合理范围内(2020-2030)
    let year: u32 = date_stamp[0..4].parse().expect("year should be parseable");
    assert!(
        (2020..=2030).contains(&year),
        "year {} should be in 2020-2030",
        year
    );
    // 解析月份(5-6 位),应在 1-12
    let month: u32 = date_stamp[4..6].parse().expect("month should be parseable");
    assert!(
        (1..=12).contains(&month),
        "month {} should be in 1-12",
        month
    );
    // 解析日期(7-8 位),应在 1-31
    let day: u32 = date_stamp[6..8].parse().expect("day should be parseable");
    assert!((1..=31).contains(&day), "day {} should be in 1-31", day);
}

// ===================== HMAC-SHA1 测试(RFC 2202)=====================
//
// 仅在 qiniu-kodo feature(提供 sha1 依赖)下编译

#[cfg(feature = "qiniu-kodo")]
mod hmac_sha1_tests {
    use sz_orm_storage::signing;

    #[test]
    fn test_hmac_sha1_rfc2202_case1() {
        // RFC 2202 TestCase 1: key=0x0b*20, data="Hi There"
        let key = [0x0bu8; 20];
        let mac = signing::compute_hmac_sha1(&key, b"Hi There");
        assert_eq!(
            hex::encode(&mac),
            "b617318655057264e28bc0b6fb378c8ef146be00"
        );
    }

    #[test]
    fn test_hmac_sha1_rfc2202_case2() {
        // RFC 2202 TestCase 2: key="Jefe", data="what do ya want for nothing?"
        let key = b"Jefe";
        let mac = signing::compute_hmac_sha1(key, b"what do ya want for nothing?");
        assert_eq!(
            hex::encode(&mac),
            "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"
        );
    }

    #[test]
    fn test_hmac_sha1_rfc2202_case4() {
        // RFC 2202 TestCase 4: key=0x01..0x19 (25 bytes), data=0xcd*50
        let key: Vec<u8> = (1..=25u8).collect();
        let data = vec![0xcdu8; 50];
        let mac = signing::compute_hmac_sha1(&key, &data);
        assert_eq!(
            hex::encode(&mac),
            "4c9007f4026250c6bc8414f9bf50c86c2d7235da"
        );
    }

    #[test]
    fn test_hmac_sha1_empty_data() {
        let key = b"test-key";
        let mac = signing::compute_hmac_sha1(key, b"");
        // 验证非空输出(20 字节)
        assert_eq!(mac.len(), 20);
        // 验证确定性
        let mac2 = signing::compute_hmac_sha1(key, b"");
        assert_eq!(mac, mac2);
    }

    #[test]
    fn test_hmac_sha1_different_keys_produce_different_macs() {
        let data = b"test data";
        let mac1 = signing::compute_hmac_sha1(b"key1", data);
        let mac2 = signing::compute_hmac_sha1(b"key2", data);
        assert_ne!(mac1, mac2);
    }
}