Closer/iphone/CloserTests/CryptoTests/AnswerCryptoTests.swift

114 lines
4.0 KiB
Swift

import XCTest
import CryptoKit
@testable import Closer
final class AnswerCryptoTests: XCTestCase {
private let coupleId = "couple-test-456"
private let userId = "user-test-789"
private let questionId = "question-test-abc"
private var key: CoupleKeyMaterial {
CoupleKeyMaterial(rawBytes: Data(repeating: 0xAB, count: 32))
}
func testEncryptDecryptRoundTrip() throws {
let plaintext = "This is my private answer."
let payload = try AnswerCrypto.encrypt(
answerPlaintext: plaintext,
userId: userId,
questionId: questionId,
coupleId: coupleId,
key: key
)
XCTAssertEqual(payload.schemaVersion, AnswerCrypto.schemaVersion)
XCTAssertTrue(payload.ciphertext.hasPrefix(FieldEncryptor.prefix))
let recovered = try AnswerCrypto.decrypt(payload, key: key)
XCTAssertEqual(recovered, plaintext)
}
func testEncodeDecodeRoundTrip() throws {
let plaintext = "Round-trip via outer wrapper"
let payload = try AnswerCrypto.encrypt(
answerPlaintext: plaintext,
userId: userId,
questionId: questionId,
coupleId: coupleId,
key: key
)
let encoded = try AnswerCrypto.encode(payload)
XCTAssertTrue(encoded.hasPrefix(FieldEncryptor.prefix))
let decoded = try AnswerCrypto.decode(encoded)
XCTAssertEqual(decoded.userId, userId)
XCTAssertEqual(decoded.questionId, questionId)
XCTAssertEqual(decoded.coupleId, coupleId)
XCTAssertEqual(decoded.schemaVersion, AnswerCrypto.schemaVersion)
let recovered = try AnswerCrypto.decrypt(decoded, key: key)
XCTAssertEqual(recovered, plaintext)
}
func testAADMismatchRejects() throws {
let plaintext = "AAD-bound answer"
let payload = try AnswerCrypto.encrypt(
answerPlaintext: plaintext,
userId: userId,
questionId: questionId,
coupleId: coupleId,
key: key
)
// Tamper the questionId in the payload so the AAD would differ.
var tampered = payload
tampered = SecureAnswerPayload(
schemaVersion: payload.schemaVersion,
coupleId: payload.coupleId,
userId: payload.userId,
questionId: payload.questionId + "x",
ciphertext: payload.ciphertext,
createdAt: payload.createdAt
)
XCTAssertThrowsError(try AnswerCrypto.decrypt(tampered, key: key))
}
func testTamperedCiphertextRejects() throws {
let plaintext = "Tamper me"
let payload = try AnswerCrypto.encrypt(
answerPlaintext: plaintext,
userId: userId,
questionId: questionId,
coupleId: coupleId,
key: key
)
var chars = Array(payload.ciphertext)
let prefixEnd = FieldEncryptor.prefix.count
chars[prefixEnd + 5] ^= 0x01
let tamperedCiphertext = String(chars)
let tamperedPayload = SecureAnswerPayload(
schemaVersion: payload.schemaVersion,
coupleId: payload.coupleId,
userId: payload.userId,
questionId: payload.questionId,
ciphertext: tamperedCiphertext,
createdAt: payload.createdAt
)
XCTAssertThrowsError(try AnswerCrypto.decrypt(tamperedPayload, key: key))
}
func testOuterWrapperTamperRejects() throws {
let plaintext = "Outer tamper"
let payload = try AnswerCrypto.encrypt(
answerPlaintext: plaintext,
userId: userId,
questionId: questionId,
coupleId: coupleId,
key: key
)
let encoded = try AnswerCrypto.encode(payload)
var chars = Array(encoded)
let prefixEnd = FieldEncryptor.prefix.count
chars[prefixEnd + 3] ^= 0x01
let tampered = String(chars)
XCTAssertThrowsError(try AnswerCrypto.decode(tampered))
}
}