using System; using System.Security.Cryptography; using System.Text; using Paca.Core.Security; using Xunit; namespace Paca.Tests.Security; /// /// RED TEAM ATTACK SUITE: FIDO2 / Passkey 암호학적 공격 침투 테스트 /// 1. 타인의 공개키/자격증명으로 서명 바꿔치기(Identity Spoofing) 방어 /// 2. AuthenticatorData 플래그(User Present / User Verified) 변조 방어 /// 3. ClientDataHash 1비트 조작 변조(Bit-Flip Tampering) 방어 /// 4. 존재하지 않거나 유효하지 않은 CredentialId 접근 거부 /// public class VirtualFido2AdversarialTests { private readonly VirtualFido2Authenticator _authenticator = VirtualFido2Authenticator.Default; [Fact] public void RedTeam_IdentitySpoofing_WithAnotherCredential_MustFail() { // 피해자(victim)와 공격자(attacker) 자격증명 생성 var victim = _authenticator.CreateCredential("paca.local", "victim-user", "Alice"); var attacker = _authenticator.CreateCredential("paca.local", "attacker-user", "Mallory"); var challengeHash = SHA256.HashData(Encoding.UTF8.GetBytes("super-secret-challenge")); // 공격자가 자신의 키로 서명 발행 var attackerAssertion = _authenticator.SignChallenge(attacker.CredentialId, challengeHash); // 공격자의 서명을 피해자의 CredentialId로 검증 시도 -> 암호학적으로 반드시 거부되어야 함 var result = _authenticator.VerifyAssertion( victim.CredentialId, challengeHash, attackerAssertion.AuthenticatorData, attackerAssertion.Signature); Assert.False(result, "[Red Team] 타인의 자격증명으로 서명 위조 시 반드시 검증 실패해야 합니다."); } [Fact] public void RedTeam_BitFlipTampering_OnClientDataHash_MustFail() { var cred = _authenticator.CreateCredential("paca.local", "user-bitflip", "Bob"); var challengeHash = SHA256.HashData(Encoding.UTF8.GetBytes("legit-challenge")); var assertion = _authenticator.SignChallenge(cred.CredentialId, challengeHash); // 챌린지 해시의 1비트를 고의로 조작(Bit-Flip) var tamperedHash = (byte[])challengeHash.Clone(); tamperedHash[0] ^= 0x01; var result = _authenticator.VerifyAssertion( cred.CredentialId, tamperedHash, assertion.AuthenticatorData, assertion.Signature); Assert.False(result, "[Red Team] 챌린지 해시의 단 1비트만 변조되어도 서명 검증이 실패해야 합니다."); } [Fact] public void RedTeam_AuthenticatorData_Tampering_MustFail() { var cred = _authenticator.CreateCredential("paca.local", "user-authdata", "Charlie"); var challengeHash = SHA256.HashData(Encoding.UTF8.GetBytes("auth-flags-challenge")); var assertion = _authenticator.SignChallenge(cred.CredentialId, challengeHash); // AuthenticatorData 내의 플래그 바이트(Offset 32)를 고의로 변조 var tamperedAuthData = (byte[])assertion.AuthenticatorData.Clone(); tamperedAuthData[32] ^= 0xFF; // Flags 변조 var result = _authenticator.VerifyAssertion( cred.CredentialId, challengeHash, tamperedAuthData, assertion.Signature); Assert.False(result, "[Red Team] AuthenticatorData의 플래그 또는 카운터가 변조되면 서명 검증이 실패해야 합니다."); } [Fact] public void RedTeam_NonExistentCredential_MustReject() { var challengeHash = SHA256.HashData(Encoding.UTF8.GetBytes("ghost-challenge")); var fakeSignature = new byte[64]; var fakeAuthData = new byte[37]; var result = _authenticator.VerifyAssertion( "non-existent-credential-id-99999", challengeHash, fakeAuthData, fakeSignature); Assert.False(result, "[Red Team] 존재하지 않는 자격증명 ID는 즉시 거부되어야 합니다."); } }