import XCTest import CryptoKit @testable import Closer final class FieldEncryptorTests: XCTestCase { private let key = SymmetricKey(size: .bits256) func testStringRoundTripWithoutAAD() throws { let plaintext = "hello, closer" let blob = try FieldEncryptor.encryptString(plaintext, key: key, aad: nil) XCTAssertTrue(blob.hasPrefix(FieldEncryptor.prefix)) let recovered = try FieldEncryptor.decryptString(blob, key: key, aad: nil) XCTAssertEqual(recovered, plaintext) } func testStringRoundTripWithAAD() throws { let plaintext = "secret payload" let aad = "couple-123".data(using: .utf8)! let blob = try FieldEncryptor.encryptString(plaintext, key: key, aad: aad) let recovered = try FieldEncryptor.decryptString(blob, key: key, aad: aad) XCTAssertEqual(recovered, plaintext) } func testBinaryRoundTrip() throws { let plaintext = Data((0..<64).map { $0 }) let aad = Data([0x00, 0x01, 0x02]) let ct = try FieldEncryptor.encrypt(plaintext, key: key, aad: aad) let recovered = try FieldEncryptor.decrypt(ct, key: key, aad: aad) XCTAssertEqual(recovered, plaintext) } func testTamperedCiphertextThrows() throws { let plaintext = "tamper me" let blob = try FieldEncryptor.encryptString(plaintext, key: key, aad: nil) var bytes = Array(blob.utf8) // Flip a bit in the base64 payload portion. let prefixEnd = FieldEncryptor.prefix.count bytes[prefixEnd + 5] ^= 0x01 let tampered = String(bytes: bytes, encoding: .utf8)! XCTAssertThrowsError(try FieldEncryptor.decryptString(tampered, key: key, aad: nil)) } func testWrongAADThrows() throws { let plaintext = "aad bound" let blob = try FieldEncryptor.encryptString(plaintext, key: key, aad: Data([0xAB])) XCTAssertThrowsError(try FieldEncryptor.decryptString(blob, key: key, aad: Data([0xBA]))) } func testKnownVectorCryptoKitRoundTrip() throws { // NIST-style AES-256-GCM test vector derived from CryptoKit itself: // fixed key + fixed nonce + fixed plaintext should round-trip deterministically. let keyBytes = Data(repeating: 0xAB, count: 32) let fixedKey = SymmetricKey(data: keyBytes) let nonce = AES.GCM.Nonce(data: Data(repeating: 0xCD, count: 12))! let plaintext = Data("known vector plaintext".utf8) let sealed = try AES.GCM.seal(plaintext, using: fixedKey, nonce: nonce) let ct = sealed.combined! let recovered = try AES.GCM.open(try AES.GCM.SealedBox(combined: ct), using: fixedKey) XCTAssertEqual(recovered, plaintext) } }