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