97 lines
4 KiB
C#
97 lines
4 KiB
C#
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Paca.Core.Security;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Security;
|
|
|
|
/// <summary>
|
|
/// RED TEAM ATTACK SUITE: FIDO2 / Passkey 암호학적 공격 침투 테스트
|
|
/// 1. 타인의 공개키/자격증명으로 서명 바꿔치기(Identity Spoofing) 방어
|
|
/// 2. AuthenticatorData 플래그(User Present / User Verified) 변조 방어
|
|
/// 3. ClientDataHash 1비트 조작 변조(Bit-Flip Tampering) 방어
|
|
/// 4. 존재하지 않거나 유효하지 않은 CredentialId 접근 거부
|
|
/// </summary>
|
|
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는 즉시 거부되어야 합니다.");
|
|
}
|
|
}
|