feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,97 @@
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는 즉시 거부되어야 합니다.");
}
}