wxpay-rs 2.0.2

WeChat Pay API v3 Rust SDK
Documentation
//! 验签器模块
//!
//! 提供响应签名验证功能。

use async_trait::async_trait;
use base64::Engine;
use der::Encode;
use rsa::pkcs8::DecodePublicKey;
use rsa::{Pkcs1v15Sign, RsaPublicKey};
use sha2::{Digest, Sha256};
use x509_cert::Certificate;

use crate::error::{WxPayError, WxPayResult};

/// 验签器 trait
///
/// 定义了验证响应签名的接口。
#[async_trait]
pub trait Verifier: Send + Sync {
    /// 验证签名
    ///
    /// # 参数
    ///
    /// * `message` - 原始消息
    /// * `signature` - Base64 编码的签名
    ///
    /// # 返回
    ///
    /// 验证成功返回 Ok(true),失败返回错误
    async fn verify(&self, message: &str, signature: &str) -> WxPayResult<bool>;

    /// 验证签名(使用指定证书序列号)
    ///
    /// # 参数
    ///
    /// * `message` - 原始消息
    /// * `signature` - Base64 编码的签名
    /// * `serial_number` - 证书序列号
    ///
    /// # 返回
    ///
    /// 验证成功返回 Ok(true),失败返回错误
    async fn verify_with_serial(
        &self,
        message: &str,
        signature: &str,
        serial_number: &str,
    ) -> WxPayResult<bool>;
}

/// SHA256-RSA 验签器
///
/// 使用 SHA256WithRSA 算法验证响应签名。
///
/// # 示例
///
/// ```rust,no_run
/// use wxpay_rs::auth::{Verifier, Sha256RsaVerifier};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let cert_pem = std::fs::read_to_string("path/to/platform_cert.pem")?;
///     let verifier = Sha256RsaVerifier::new(vec![cert_pem.as_bytes().to_vec()])?;
///
///     let result = verifier.verify("test message", "dGVzdF9zaWduYXR1cmU=").await?;
///     let _ = result;
///     Ok(())
/// }
/// ```
pub struct Sha256RsaVerifier {
    /// 证书列表(序列号 -> 公钥)
    certificates: Vec<(String, RsaPublicKey)>,
}

impl Sha256RsaVerifier {
    /// 创建新的 SHA256-RSA 验签器
    ///
    /// # 参数
    ///
    /// * `certificates` - 证书列表(PEM 格式)
    ///
    /// # 返回
    ///
    /// 返回验签器实例
    pub fn new(certificates: Vec<Vec<u8>>) -> WxPayResult<Self> {
        let mut parsed_certs = Vec::new();

        for cert_der in certificates {
            let cert = Self::parse_certificate(&cert_der)?;
            let serial_number = Self::extract_serial_number(&cert)?;
            let public_key = Self::extract_public_key(&cert)?;
            parsed_certs.push((serial_number, public_key));
        }

        Ok(Self {
            certificates: parsed_certs,
        })
    }

    /// 解析证书
    fn parse_certificate(der: &[u8]) -> WxPayResult<Certificate> {
        use der::Decode;
        Certificate::from_der(der)
            .map_err(|e| WxPayError::CertificateParseError(format!("证书解析失败:{}", e)))
    }

    /// 提取证书序列号
    fn extract_serial_number(cert: &Certificate) -> WxPayResult<String> {
        // 使用证书的序列号
        let serial = &cert.tbs_certificate().serial_number();
        let bytes = serial.as_bytes();
        Ok(hex::encode(bytes))
    }

    /// 提取公钥
    fn extract_public_key(cert: &Certificate) -> WxPayResult<RsaPublicKey> {
        // 从证书中提取公钥
        let spki = cert.tbs_certificate().subject_public_key_info();
        let spki_der = spki
            .to_der()
            .map_err(|e| WxPayError::CertificateParseError(format!("提取证书 SPKI 失败:{}", e)))?;

        let public_key = RsaPublicKey::from_public_key_der(&spki_der)
            .map_err(|e| WxPayError::CertificateParseError(format!("提取公钥失败:{}", e)))?;
        Ok(public_key)
    }

    /// 构建验签消息
    ///
    /// 微信支付 API v3 验签格式:
    /// TIMESTAMP\nNONCE_STR\nBODY\n
    pub fn build_verify_message(timestamp: i64, nonce: &str, body: &str) -> String {
        format!("{}\n{}\n{}\n", timestamp, nonce, body)
    }

    /// 使用公钥验证签名
    fn verify_signature(
        public_key: &RsaPublicKey,
        message: &str,
        signature: &str,
    ) -> WxPayResult<bool> {
        // 计算 SHA256 哈希
        let mut hasher = Sha256::new();
        hasher.update(message.as_bytes());
        let hash = hasher.finalize();

        // Base64 解码签名
        let signature_bytes = base64::engine::general_purpose::STANDARD
            .decode(signature)
            .map_err(|e| WxPayError::InvalidSignatureFormat(format!("Base64 解码失败:{}", e)))?;

        // 验证签名
        match public_key.verify(Pkcs1v15Sign::new::<Sha256>(), &hash, &signature_bytes) {
            Ok(()) => Ok(true),
            Err(_) => Ok(false),
        }
    }
}

#[async_trait]
impl Verifier for Sha256RsaVerifier {
    async fn verify(&self, message: &str, signature: &str) -> WxPayResult<bool> {
        // 使用第一个证书验证
        let (_, public_key) = self.certificates.first().ok_or_else(|| {
            WxPayError::CertificateVerificationError("没有可用的证书".to_string())
        })?;

        Self::verify_signature(public_key, message, signature)
    }

    async fn verify_with_serial(
        &self,
        message: &str,
        signature: &str,
        serial_number: &str,
    ) -> WxPayResult<bool> {
        // 查找匹配的证书
        let (_, public_key) = self
            .certificates
            .iter()
            .find(|(serial, _)| serial == serial_number)
            .ok_or_else(|| WxPayError::CertificateNotFound(serial_number.to_string()))?;

        Self::verify_signature(public_key, message, signature)
    }
}

impl std::fmt::Debug for Sha256RsaVerifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Sha256RsaVerifier")
            .field("certificates_count", &self.certificates.len())
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::auth::{Sha256RsaSigner, Signer};
    use base64::Engine;

    /// 测试用自签名证书(DER,base64 编码)及其配套 PKCS#8 私钥(PEM)。
    /// 由 openssl 离线生成,CN=wxpay-rs-test,2048-bit,SHA256WithRSA。
    const TEST_CERT_DER_B64: &str = "MIIDMTCCAhmgAwIBAgIUO0KjQ4nVBzRZyR/2689auBsGMPcwDQYJKoZIhvcNAQELBQAwJzEWMBQGA1UEAwwNd3hwYXktcnMtdGVzdDENMAsGA1UECgwEdGVzdDAgFw0yNjA2MTYwNDE3MDlaGA8yMDUzMTEwMTA0MTcwOVowJzEWMBQGA1UEAwwNd3hwYXktcnMtdGVzdDENMAsGA1UECgwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAXC1vTGcxi6aB67mWFz4W/9d9TaElw+da2OGbcxUGEQzvd2axnTKu87Fm0mGzh18qzwGVYq8MiefPTn7aWsN0CQ02+3RcT/QHnmfYduTjJkjHrM5+wmWtwSgsxN8Txg2GYZ5GYT4vc4nYv0gB4xlbkh/dPbQTrn7wGiETyfAthed1cNMxkPUwAePd/KAKfZNkcqotR/2XNih5orlZz0DySY7p0xx59eSyS8koln5L8bR/7VoVw3wxojsYWBSR2zOCcSVkpbF+K/+tdC3ZPzxlwrQCmzKmXl+W51b0jMqOlOsm0bHxHi1V9nS95DPfFNTuOhFTuZSMHKF9J6zpATe0CAwEAAaNTMFEwHQYDVR0OBBYEFGo+jzczvrST9JVBo875auuysJS3MB8GA1UdIwQYMBaAFGo+jzczvrST9JVBo875auuysJS3MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAChV2tnTzVIRbSHRrP0unCUYxf9mPldpVVB3Zbzb+S1oMllYwtUuNCgOuaIWz8LlA2A9yEoV5zvPJfrQFNJ3KYrMyAXJ7Q9UDFMSpP5aaqvtIq1GcLfw8EiyuGN3nQwHBPA2AN3JznDufWY5LI2TLDwiX/mv8U4ZzWHMOye7huI3AEIVTXv01NXWleI2TA/MxTMppaO8t5lzlaDXgPMnZqW5qsuHZzGk+aq07SO9KitKO4E5PoNYfE6ywWn13mOZrRklCtT9mauaE/kCHIAQPuyfWrZ2lvkjWIefQ/onZBxAKP5z6VcSb3Z/3G85MQm9kSnwUFjKw7yuauDv/wyq/AU=";

    const TEST_PRIVATE_KEY_PEM: &str = "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDQFwtb0xnMYumg\neu5lhc+Fv/XfU2hJcPnWtjhm3MVBhEM73dmsZ0yrvOxZtJhs4dfKs8BlWKvDInnz\n05+2lrDdAkNNvt0XE/0B55n2Hbk4yZIx6zOfsJlrcEoLMTfE8YNhmGeRmE+L3OJ2\nL9IAeMZW5If3T20E65+8BohE8nwLYXndXDTMZD1MAHj3fygCn2TZHKqLUf9lzYoe\naK5Wc9A8kmO6dMcefXkskvJKJZ+S/G0f+1aFcN8MaI7GFgUkdszgnElZKWxfiv/r\nXQt2T88ZcK0Apsypl5fludW9IzKjpTrJtGx8R4tVfZ0veQz3xTU7joRU7mUjByhf\nSes6QE3tAgMBAAECgf8ZVV+Mo6arELULVJaxcBj+WjW/epK3s4lhxSLDYx1LXKQo\nJa+FIw5dL3hBc5BwW7kUdHh33ikLGKdq3S4UjJlQ+XWNgYRpIDCCitpeRurF1G8i\npKp5m9u8Y29K7YhcnF/iVyuaDhuhFhh79avGDZjCpg/ni+6PKssc7llTYNy5MGya\nBNkxzXX2Oo5WI1IBOptOEUb6iWYz5FoAf91Ai0K8mFuB5tPCv67DqB2Rq4c6LMoX\nVzwzMZ64GhzYC6vyjltzMjtYTIDvheOZsOUgJe1pAaChwiGRDpmuf8/oybSQFFsy\n1PYF+TddnNk0NOQCPI0qXLHE2OXtdDAigPiA5v8CgYEA6/BnV4O/ZS34WvaGucPx\nQp9s59FolMyWtwELLxOZaO1LPAa9pdNC1+IfUl6zpeRu2z1kNG9f2TbgtTVrF7Lu\n5XvuhJ2OqnL8GgGYpS0vj2Sx5XRO8/pgxiAnpRy7Mkp1jA4+ZTpNQH3FoA6LZZfM\n1v/ijOH9NeHUWEw64OE/OoMCgYEA4ch19Yp73ijLvEUyAkqYrvPOkm7G02mlRD4T\nTUe2tGe8HUbOZGi5CphvItto9mssPDDsEVLilkrPDKlg3899L+ZLE8vHzw6QVoaK\n8LDQaapWbW3LazwLAna4kpNDd06h+Rx7j/n1lha6Vj/2dbEQhAAllos92B7SCNf8\nYIiXqs8CgYACC3tZztKB1fwpDantQj19DlSrTa1SXNORkni+V7Ukq6nTQ1uxbDtQ\nE62h0SBNd8VeMRIFQlHaWBdqeqQK+IoJgyF2FMd/wq9cqlbgV5vp6j2Ad5mXk7vy\n+6RcUfttXCfYpubziaXRwUVNNdMPdllYI6+a+Ppw1Rw6B68a89jQcQKBgFaW+JY4\njBTBdJE5wFocnb3LBxgln98IjzdCz0g+DpXVitF3jEP53a1wlH67wt9ubsKOyJpE\nPV4CRrHGa76p5oruOTDYYELKhRSJ+NMiHGvJxeelyfPQTTCes16TV7Zz066j+8dV\nx5fOE5xsX2r3gyv8mm3H7OnruAVoQAQNno0FAoGBAOvD07di46NEaY7OTGzt4JwE\nWa/0KzWvrQ6SCaHUnZ1yIqL6jEV7RCxKGr206cW9nlG2+n2QqAC8dinDrdLspLZG\noEqm/DoCUaghQOGnh7teguj3eqS+MHU5T/ugSJdJoMNtpQ/BlSnqkWLPoh+yrvh5\nmVKYyABhNkZONhC533bA\n-----END PRIVATE KEY-----\n";

    fn test_cert_der() -> Vec<u8> {
        base64::engine::general_purpose::STANDARD
            .decode(TEST_CERT_DER_B64)
            .expect("测试证书 base64 解码应成功")
    }

    fn test_signer() -> Sha256RsaSigner {
        Sha256RsaSigner::new("1900000109", TEST_PRIVATE_KEY_PEM.as_bytes(), "CERT123456")
            .expect("测试签名器应创建成功")
    }

    #[test]
    fn test_build_verify_message() {
        let message = Sha256RsaVerifier::build_verify_message(
            1609459200,
            "test_nonce",
            r#"{"code":"SUCCESS"}"#,
        );

        assert!(message.starts_with("1609459200\n"));
        assert!(message.contains("test_nonce"));
        assert!(message.ends_with("\n"));
    }

    #[tokio::test]
    async fn test_verify_valid_signature() {
        // 用配套私钥对消息签名,再用证书中的公钥验签 —— 端到端验证验签器正确性。
        let verifier = Sha256RsaVerifier::new(vec![test_cert_der()]).unwrap();
        let signer = test_signer();

        let message = r#"{"code":"SUCCESS","message":"成功"}"#;
        let signature = signer.sign(message).await.unwrap();

        let valid = verifier.verify(message, &signature).await.unwrap();
        assert!(valid, "匹配公钥验签应返回 true");
    }

    #[tokio::test]
    async fn test_verify_tampered_message_returns_false() {
        let verifier = Sha256RsaVerifier::new(vec![test_cert_der()]).unwrap();
        let signer = test_signer();

        let signature = signer.sign("original message").await.unwrap();

        // 篡改消息内容:验签应失败(返回 false,而非报错)。
        let valid = verifier
            .verify("tampered message", &signature)
            .await
            .unwrap();
        assert!(!valid, "篡改消息后验签应返回 false");
    }

    #[tokio::test]
    async fn test_verify_with_serial_matches_certificate() {
        let der = test_cert_der();
        let verifier = Sha256RsaVerifier::new(vec![der.clone()]).unwrap();
        let signer = test_signer();

        // 用与验签器相同的方式推导证书序列号(私有方法在本模块内可见)。
        let cert = Sha256RsaVerifier::parse_certificate(&der).unwrap();
        let serial = Sha256RsaVerifier::extract_serial_number(&cert).unwrap();

        let message = "serial-scoped verify";
        let signature = signer.sign(message).await.unwrap();

        // 指定正确序列号:验签通过。
        let ok = verifier
            .verify_with_serial(message, &signature, &serial)
            .await
            .unwrap();
        assert!(ok);

        // 指定不存在的序列号:返回 CertificateNotFound 错误。
        let missing = verifier
            .verify_with_serial(message, &signature, "NON_EXISTENT_SERIAL")
            .await;
        assert!(matches!(missing, Err(WxPayError::CertificateNotFound(_))));
    }

    #[tokio::test]
    async fn test_verify_with_no_certificates_errors() {
        // 没有任何证书时,verify 应返回错误(而非 panic)。
        let verifier = Sha256RsaVerifier::new(vec![]).unwrap();
        let result = verifier.verify("msg", "sig").await;
        assert!(matches!(
            result,
            Err(WxPayError::CertificateVerificationError(_))
        ));
    }

    #[test]
    fn test_verifier_rejects_invalid_certificate() {
        // 非法的 DER 数据应解析失败。
        let result = Sha256RsaVerifier::new(vec![b"not a valid der".to_vec()]);
        assert!(matches!(result, Err(WxPayError::CertificateParseError(_))));
    }

    #[tokio::test]
    async fn test_verify_rejects_malformed_signature() {
        let verifier = Sha256RsaVerifier::new(vec![test_cert_der()]).unwrap();
        // 非法 base64 的签名应返回 InvalidSignatureFormat 错误。
        let result = verifier.verify("msg", "!!!not-base64!!!").await;
        assert!(matches!(result, Err(WxPayError::InvalidSignatureFormat(_))));
    }
}