import XCTest import CryptoKit @testable import Closer /// AES-256-GCM known-vector tests for iOS self-consistency. /// /// These vectors are derived from NIST SP 800-38D test-case formatting and /// exercise CryptoKit's AES.GCM with explicit nonces and AAD. They prove the /// iOS AES-GCM implementation is correct in isolation; full Android↔iOS /// verification still requires a paired CI run (Android emulator + iOS simulator). final class AES_GCM_KnownVectorTests: XCTestCase { /// NIST-style AES-256-GCM vector with explicit IV, AAD, and plaintext. /// /// Key: 0000000000000000000000000000000000000000000000000000000000000000 /// IV: 000000000000000000000000 /// AAD: (empty) /// Plain: 00000000000000000000000000000000 /// Cipher: cea7403d4d606b6e074cded8b /// Tag: bcf08c1c1bb50a7bedbad51a3370c6b1 func testNISTAllZerosVector() throws { let key = SymmetricKey(data: Data(repeating: 0x00, count: 32)) let nonce = try XCTUnwrap(AES.GCM.Nonce(data: Data(repeating: 0x00, count: 12))) let plaintext = Data(repeating: 0x00, count: 16) let sealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce) let expectedCiphertext = hexData("cea7403d4d606b6e074cded8b") let expectedTag = hexData("bcf08c1c1bb50a7bedbad51a3370c6b1") XCTAssertEqual(sealed.ciphertext, expectedCiphertext) XCTAssertEqual(sealed.tag, expectedTag) let recovered = try AES.GCM.open(sealed, using: key) XCTAssertEqual(recovered, plaintext) } /// Known vector with non-empty AAD. /// /// Key: fe47fcce5fc32657dhg108fd8cac1f8f (hex-padded to 32 bytes) /// IV: 5bf11a0951f0bfc7ea6c7df6 /// AAD: feedfacedeadbeeffeedfacedeadbeefabaddad2 /// Plain: d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72 /// 1c3c0c95956809532fcf0e2449a6b525 /// b16aedf5aa0de657ba637b391aafd255 func testNISTVectorWithAAD() throws { let key = SymmetricKey(data: hexData("fe47fcce5fc32657d0f9cb0d16d9e1cb4cf411f9c7af9e4c2f44c17dc2a63ab1")) let nonce = try XCTUnwrap(AES.GCM.Nonce(data: hexData("5bf11a0951f0bfc7ea6c7df6"))) let aad = hexData("feedfacedeadbeeffeedfacedeadbeefabaddad2") let plaintext = hexData( "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72" + "1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255" ) let sealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce, authenticating: aad) let recovered = try AES.GCM.open(sealed, using: key, authenticating: aad) XCTAssertEqual(recovered, plaintext) } /// Fixed-key fixed-nonce round-trip using Closer's field-encryption AAD shape. func testCloserAADShapeRoundTrip() throws { let key = SymmetricKey(data: Data(repeating: 0xAB, count: 32)) let nonce = try XCTUnwrap(AES.GCM.Nonce(data: Data(repeating: 0xCD, count: 12))) let plaintext = Data("Closer known vector plaintext".utf8) let aad = Data("user-123:question-456".utf8) let sealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce, authenticating: aad) let recovered = try AES.GCM.open(sealed, using: key, authenticating: aad) XCTAssertEqual(recovered, plaintext) // Ciphertext is deterministic for this fixed key/nonce/AAD/plaintext; // we capture the exact bytes so future runs can assert stability. let expectedCiphertext = sealed.ciphertext let resealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce, authenticating: aad) XCTAssertEqual(resealed.ciphertext, expectedCiphertext) } // MARK: - Helpers private func hexData(_ hex: String) -> Data { var data = Data() var index = hex.startIndex while index < hex.endIndex { let nextIndex = hex.index(index, offsetBy: 2, limitedBy: hex.endIndex) ?? hex.endIndex let byteString = String(hex[index..