ciphern 0.2.1

Enterprise-grade cryptographic library
Documentation
// Copyright (c) 2025 Kirky.X
//
// Licensed under the MIT License
// See LICENSE file in the project root for full license information.

use crate::cipher::pkcs7::Pkcs7Padding;
use crate::cipher::provider::SymmetricCipher;
use crate::error::{CryptoError, Result};
use crate::i18n::translate;
use crate::key::Key;
use crate::random::SecureRandom;
use crate::side_channel::{
    protect_critical_operation, RotatingSboxMasking, SideChannelConfig, SideChannelContext,
};
use crate::types::Algorithm;
use std::sync::{Arc, Mutex};

pub struct Sm4GcmProvider {
    side_channel_context: Option<Arc<Mutex<SideChannelContext>>>,
    _rotating_sbox: Option<Arc<Mutex<RotatingSboxMasking>>>,
}

impl Sm4GcmProvider {
    pub fn new() -> Self {
        let side_channel_context = Some(Arc::new(Mutex::new(SideChannelContext::new(
            SideChannelConfig::default(),
        ))));
        let rotating_sbox = RotatingSboxMasking::new(4)
            .ok()
            .map(|sbox| Arc::new(Mutex::new(sbox))); // 4个轮换S盒

        Self {
            side_channel_context,
            _rotating_sbox: rotating_sbox,
        }
    }

    /// 创建一个新的 SM4-GCM 提供者,使用自定义侧信道配置
    #[allow(dead_code)]
    pub fn with_side_channel_config(config: SideChannelConfig) -> Self {
        let side_channel_context = Some(Arc::new(Mutex::new(SideChannelContext::new(config))));
        let rotating_sbox = RotatingSboxMasking::new(4)
            .ok()
            .map(|sbox| Arc::new(Mutex::new(sbox))); // 4个轮换S盒

        Self {
            side_channel_context,
            _rotating_sbox: rotating_sbox,
        }
    }
}

impl Default for Sm4GcmProvider {
    fn default() -> Self {
        Self::new()
    }
}

impl Sm4GcmProvider {
    /// 内部加密方法,不包含侧信道防护
    fn encrypt_internal(
        &self,
        key: &Key,
        plaintext: &[u8],
        nonce: &[u8],
        aad: Option<&[u8]>,
        use_padding: bool,
    ) -> Result<Vec<u8>> {
        use ghash::{
            universal_hash::{KeyInit, UniversalHash},
            GHash,
        };
        use sm4::cipher::{KeyIvInit, StreamCipher};
        use sm4::Sm4;

        let secret = key.secret_bytes()?;
        let key_bytes: [u8; 16] = secret.as_bytes().try_into().map_err(|_| {
            CryptoError::KeyError("Invalid SM4 key length, must be 128 bits".into())
        })?;

        // 1. 准备数据,如果需要则进行填充
        let data_to_encrypt = if use_padding {
            Pkcs7Padding::pad(plaintext, 16)?
        } else {
            plaintext.to_vec()
        };

        // 2. 对 AAD 进行 GHASH
        let mut ghash = GHash::new(&key_bytes.into());
        match aad {
            Some(a) if !a.is_empty() => {
                ghash.update_padded(a);
            }
            _ => {}
        }

        // 3. 使用 SM4-CTR 加密
        let mut iv = [0u8; 16];
        iv[..12].copy_from_slice(nonce);
        iv[15] = 2; // GCM 从 2 开始计数用于数据(1 用于标签)

        let mut ciphertext = data_to_encrypt;
        type Sm4Ctr = ctr::Ctr128BE<Sm4>;
        let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
        cipher.apply_keystream(&mut ciphertext);

        // 4. 对密文进行 GHASH
        ghash.update_padded(&ciphertext);

        // 5. 对长度进行 GHASH
        let mut len_block = [0u8; 16];
        let aad_len = aad.map(|a| a.len() as u64).unwrap_or(0) * 8;
        let ct_len = (ciphertext.len() as u64) * 8;
        len_block[..8].copy_from_slice(&aad_len.to_be_bytes());
        len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
        ghash.update_padded(&len_block);

        let mut tag = ghash.finalize();

        // 6. 加密标签
        let mut j0 = [0u8; 16];
        j0[..12].copy_from_slice(nonce);
        j0[15] = 1;
        let mut tag_mask = [0u8; 16];
        let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
        mask_cipher.apply_keystream(&mut tag_mask);

        for i in 0..16 {
            tag[i] ^= tag_mask[i];
        }

        let mut result = ciphertext;
        result.extend_from_slice(&tag);
        Ok(result)
    }

    /// 内部解密方法,不包含侧信道防护
    fn decrypt_internal(
        &self,
        key: &Key,
        ciphertext_with_tag: &[u8],
        nonce: &[u8],
        aad: Option<&[u8]>,
        use_padding: bool,
    ) -> Result<Vec<u8>> {
        use ghash::{
            universal_hash::{KeyInit, UniversalHash},
            GHash,
        };
        use sm4::cipher::{KeyIvInit, StreamCipher};
        use sm4::Sm4;

        if ciphertext_with_tag.len() < 16 {
            return Err(CryptoError::DecryptionFailed(
                "Ciphertext too short for tag".into(),
            ));
        }

        let (ciphertext, received_tag) =
            ciphertext_with_tag.split_at(ciphertext_with_tag.len() - 16);

        let secret = key.secret_bytes()?;
        let key_bytes: [u8; 16] = secret
            .as_bytes()
            .try_into()
            .map_err(|_| CryptoError::KeyError("无效的 SM4 密钥长度,必须是 128 位".into()))?;

        // 1. 对 AAD 进行 GHASH
        let mut ghash = GHash::new(&key_bytes.into());
        if let Some(a) = aad {
            if !a.is_empty() {
                ghash.update_padded(a);
            }
        }

        // 2. 对密文进行 GHASH
        ghash.update_padded(ciphertext);

        // 3. 对长度进行 GHASH
        let mut len_block = [0u8; 16];
        let aad_len = aad.map(|a| a.len() as u64).unwrap_or(0) * 8;
        let ct_len = (ciphertext.len() as u64) * 8;
        len_block[..8].copy_from_slice(&aad_len.to_be_bytes());
        len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
        ghash.update_padded(&len_block);

        let mut tag = ghash.finalize();

        // 4. 加密标签掩码
        let mut j0 = [0u8; 16];
        j0[..12].copy_from_slice(nonce);
        j0[15] = 1;
        let mut tag_mask = [0u8; 16];
        type Sm4Ctr = ctr::Ctr128BE<Sm4>;
        let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
        mask_cipher.apply_keystream(&mut tag_mask);

        for i in 0..16 {
            tag[i] ^= tag_mask[i];
        }

        // 5. 验证标签
        use subtle::ConstantTimeEq;
        if tag.as_slice().ct_eq(received_tag).unwrap_u8() != 1 {
            return Err(CryptoError::DecryptionFailed("标签不匹配".into()));
        }

        // 6. 解密密文
        let mut iv = [0u8; 16];
        iv[..12].copy_from_slice(nonce);
        iv[15] = 2;

        let mut plaintext = ciphertext.to_vec();
        let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
        cipher.apply_keystream(&mut plaintext);

        // 7. 如果需要则移除填充
        if use_padding {
            Pkcs7Padding::unpad(&plaintext, 16)
        } else {
            Ok(plaintext)
        }
    }
}

impl SymmetricCipher for Sm4GcmProvider {
    fn encrypt(&self, key: &Key, plaintext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
        if key.algorithm() != Algorithm::SM4GCM {
            return Err(CryptoError::UnsupportedAlgorithm(
                "Key algo mismatch".into(),
            ));
        }

        // FIPS 检查:SM4 通常不被 FIPS 140-3 批准
        if crate::fips::FipsContext::is_enabled() {
            return Err(CryptoError::FipsError(translate(
                "error.sm4_not_allowed_in_fips_mode",
            )));
        }

        // 生成 Nonce
        let mut nonce = [0u8; 12];
        SecureRandom::new()?.fill(&mut nonce)?;

        let ciphertext = if let Some(context) = &self.side_channel_context {
            let mut context_guard = context.lock().unwrap();
            protect_critical_operation(&mut context_guard, || {
                self.encrypt_internal(key, plaintext, &nonce, aad, true)
            })?
        } else {
            self.encrypt_internal(key, plaintext, &nonce, aad, true)?
        };

        // 前置 Nonce
        let mut result = nonce.to_vec();
        result.extend_from_slice(&ciphertext);

        Ok(result)
    }

    fn decrypt(&self, key: &Key, ciphertext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
        if key.algorithm() != Algorithm::SM4GCM {
            return Err(CryptoError::UnsupportedAlgorithm(
                "Key algo mismatch".into(),
            ));
        }

        if crate::fips::FipsContext::is_enabled() {
            return Err(CryptoError::FipsError(
                "SM4 not allowed in FIPS mode".into(),
            ));
        }

        if ciphertext.len() < 12 + 16 {
            // Nonce(12) + Tag(16)
            return Err(CryptoError::DecryptionFailed(translate(
                "error.invalid_length",
            )));
        }

        let (nonce, data) = ciphertext.split_at(12);

        if let Some(context) = &self.side_channel_context {
            let mut context_guard = context.lock().unwrap();
            protect_critical_operation(&mut context_guard, || {
                self.decrypt_internal(key, data, nonce, aad, true)
            })
        } else {
            self.decrypt_internal(key, data, nonce, aad, true)
        }
    }

    fn algorithm(&self) -> Algorithm {
        Algorithm::SM4GCM
    }

    fn encrypt_with_nonce(
        &self,
        key: &Key,
        plaintext: &[u8],
        nonce: &[u8],
        aad: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        if key.algorithm() != Algorithm::SM4GCM {
            return Err(CryptoError::UnsupportedAlgorithm(
                "Key algo mismatch".into(),
            ));
        }

        if nonce.len() != 12 {
            return Err(CryptoError::EncryptionFailed("Invalid nonce length".into()));
        }

        if let Some(context) = &self.side_channel_context {
            let mut context_guard = context.lock().unwrap();
            protect_critical_operation(&mut context_guard, || {
                self.encrypt_internal(key, plaintext, nonce, aad, false)
            })
        } else {
            self.encrypt_internal(key, plaintext, nonce, aad, false)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sm4_pkcs7_padding() {
        let provider = Sm4GcmProvider::new();
        let key_data = vec![0x01; 16];
        let key = Key::new_active(Algorithm::SM4GCM, key_data).unwrap();

        // 1. 测试非块大小倍数的数据 (11字节)
        let plaintext = b"Hello SM4!!";
        let ciphertext = provider.encrypt(&key, plaintext, None).unwrap();
        let decrypted = provider.decrypt(&key, &ciphertext, None).unwrap();
        assert_eq!(decrypted, plaintext);

        // 2. 测试刚好是块大小倍数的数据 (16字节)
        let plaintext_16 = b"1234567890123456";
        let ciphertext_16 = provider.encrypt(&key, plaintext_16, None).unwrap();
        let decrypted_16 = provider.decrypt(&key, &ciphertext_16, None).unwrap();
        assert_eq!(decrypted_16, plaintext_16);

        // 3. 验证内部加密是否真的添加了填充 (16字节数据加密后长度应为 Nonce(12) + PaddedData(32) + Tag(16) = 60)
        assert_eq!(ciphertext_16.len(), 12 + 32 + 16);
    }

    #[test]
    fn test_sm4_with_side_channel_protection() {
        let provider = Sm4GcmProvider::new();
        let key_data = vec![0x01; 16]; // SM4 使用 128 位密钥
        let mut key = Key::new(Algorithm::SM4GCM, key_data).unwrap();

        // 使用前激活密钥
        key.activate(None).unwrap();

        let plaintext = b"Hello, SM4 with side-channel protection!";

        // 测试带侧信道防护的加密
        let ciphertext = provider.encrypt(&key, plaintext, None).unwrap();
        assert!(!ciphertext.is_empty());
        assert_ne!(ciphertext, plaintext);

        // 测试带侧信道防护的解密
        let decrypted = provider.decrypt(&key, &ciphertext, None).unwrap();
        assert_eq!(decrypted, plaintext);

        // 通过检查上下文是否存在来验证是否应用了侧信道防护
        // (调试输出将显示已应用防护)
        println!("SM4 encryption/decryption with side-channel protection completed successfully");
    }

    #[test]
    fn test_sm4_fips_rejection() {
        // 测试启用 FIPS 模式时 SM4 被拒绝
        // 注意:在实际实现中,我们会在此处启用 FIPS 模式
        // 目前,我们只测试解密方法中现有的 FIPS 检查

        // crate::fips::FipsContext::set_enabled(true);

        let provider = Sm4GcmProvider::new();
        let key_data = vec![0x01; 16];
        let key = Key::new_active(Algorithm::SM4GCM, key_data).unwrap();
        let plaintext = b"Test data";

        // 由于我们无法轻松地在测试中启用/禁用 FIPS 模式,
        // 我们通过检查实现来验证 FIPS 检查是否存在
        // 实际的 FIPS 拒绝测试在注册表或更高级别测试中进行

        // 测试未启用 FIPS 时加密正常工作
        let result = provider.encrypt(&key, plaintext, None);

        // crate::fips::FipsContext::set_enabled(false);

        assert!(result.is_ok());
    }
}