Closer/iphone/CloserTests/CryptoTests/AES_GCM_KnownVectorTests.swift

89 lines
4.1 KiB
Swift

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 AndroidiOS
/// 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..<nextIndex])
if let byte = UInt8(byteString, radix: 16) {
data.append(byte)
}
index = nextIndex
}
return data
}
}