using System.Security.Cryptography;
using System.Text;
namespace Infrastructure.Kyc.Nice;
///
/// 의 invariants self-check.
///
/// dev 환경 부팅 시 1회 실행해서 PBKDF2 출력 길이, AES-GCM 라운드트립, HMAC 라운드트립이
/// 깨지지 않았음을 확인. 라이브러리/런타임 업데이트로 silently 동작이 바뀌는 회귀를 방지.
///
/// 진짜 NICE 호환성 검증은 별도 phase (xUnit + NICE 공식 PHP/Node.js sample 의 골든벡터) 에서.
///
public static class NiceCryptoInvariants
{
///
/// 실패 시 throw.
///
public static void EnsureOrThrow()
{
EnsureBase64UrlRoundTrip();
EnsureDeriveKeysShape();
EnsureHmacRoundTrip();
EnsureAesGcmRoundTrip();
}
private static void EnsureBase64UrlRoundTrip()
{
// 패딩 길이 0/1/2 byte 모두 라운드트립
var samples = new[]
{
new byte[] { 0x00 },
new byte[] { 0x01, 0x02 },
new byte[] { 0xFF, 0xFE, 0xFD },
Encoding.UTF8.GetBytes("hello world")
};
foreach (var s in samples)
{
var encoded = NiceCrypto.Base64UrlEncode(s);
if (encoded.Contains('+') || encoded.Contains('/') || encoded.EndsWith('='))
{
throw new CryptographicException($"Base64UrlEncode 가 URL-safe + no-padding 규칙을 위반: '{encoded}'");
}
var decoded = NiceCrypto.Base64UrlDecode(encoded);
if (!decoded.AsSpan().SequenceEqual(s))
{
throw new CryptographicException("Base64Url 라운드트립 실패.");
}
}
}
private static void EnsureDeriveKeysShape()
{
// 임의 vector — NICE 호환 vector 아님. 길이/결정성만 검증.
var (aesKey, hmacKey) = NiceCrypto.DeriveKeys("ticket-self-check", "transaction-id-self-check", 1000);
if (aesKey.Length != 32)
{
throw new CryptographicException($"DeriveKeys: AES key length = {aesKey.Length} (expected 32).");
}
if (hmacKey.Length != 32)
{
throw new CryptographicException($"DeriveKeys: HMAC key length = {hmacKey.Length} (expected 32).");
}
// 같은 입력 → 같은 출력 (deterministic)
var (aesKey2, hmacKey2) = NiceCrypto.DeriveKeys("ticket-self-check", "transaction-id-self-check", 1000);
if (!aesKey.AsSpan().SequenceEqual(aesKey2) || !hmacKey.AsSpan().SequenceEqual(hmacKey2))
{
throw new CryptographicException("DeriveKeys 가 결정론적이지 않습니다.");
}
}
private static void EnsureHmacRoundTrip()
{
var hmacKey = new byte[32];
RandomNumberGenerator.Fill(hmacKey);
const string encData = "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789";
var integrity = NiceCrypto.ComputeIntegrity(hmacKey, encData);
if (!NiceCrypto.VerifyIntegrity(hmacKey, encData, integrity))
{
throw new CryptographicException("HMAC ComputeIntegrity ↔ VerifyIntegrity 라운드트립 실패.");
}
// 다른 키로는 fail
var otherKey = new byte[32];
RandomNumberGenerator.Fill(otherKey);
if (NiceCrypto.VerifyIntegrity(otherKey, encData, integrity))
{
throw new CryptographicException("HMAC VerifyIntegrity 가 잘못된 키를 통과시켰습니다.");
}
}
private static void EnsureAesGcmRoundTrip()
{
var aesKey = new byte[32];
RandomNumberGenerator.Fill(aesKey);
const string plaintext = "{\"name\":\"홍길동\",\"birthdate\":\"19800101\",\"gender\":\"1\"}";
// NICE 와 동일한 16 byte IV + ciphertext+tag 레이아웃 (BouncyCastle 기반).
var encData = NiceCrypto.EncryptAesGcm(aesKey, plaintext);
var decrypted = NiceCrypto.DecryptAesGcm(aesKey, encData);
if (decrypted != plaintext)
{
throw new CryptographicException($"AES-GCM 라운드트립 실패. expected='{plaintext}', got='{decrypted}'");
}
// 위변조 검출: 임의 1 byte 변경 시 throw
var tamperedBytes = NiceCrypto.Base64UrlDecode(encData);
tamperedBytes[tamperedBytes.Length - 1] ^= 0x01;
var tampered = NiceCrypto.Base64UrlEncode(tamperedBytes);
try
{
NiceCrypto.DecryptAesGcm(aesKey, tampered);
throw new CryptographicException("AES-GCM 위변조된 데이터를 무사 통과시켰습니다.");
}
catch (Org.BouncyCastle.Crypto.InvalidCipherTextException)
{
// 정상 — GCM tag 가 위변조 감지
}
}
}