eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import CryptoKit
2import XCTest
3@testable import LChatCore
4
5// 🤖 Cross-implementation vector: the envelope below was sealed by the home
6// server's JS crypto (home/web/static/crypto.js) with a fixed PRF output and
7// DEK. If Swift opens it to the expected server list, the KEK derivation,
8// AES-GCM primitive layout, DEK unwrap, padding and envelope framing all match
9// the web byte-for-byte — so a backup written on the web restores on the phone.
10// Regenerate with `node home/test/gen-home-vector.mjs`.
11final class HomeCryptoTests: XCTestCase {
12 // Base64url inputs from the JS generator.
13 let prf = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8"
14 let dekRaw = "AAcOFRwjKjE4P0ZNVFtiaXB3foWMk5qhqK-2vcTL0tk"
15 let credId = "cred-abc"
16 let wrapped = "LfDy9OjW1vWNWfVcpShcxANG7mcWIpsrDtS2yrlhic7KwdN1abiQIF8zOg7EdHF5p9oaI0f09LGg_Wvl"
17
18 private func data(_ b64url: String) -> Data { Data(base64urlEncoded: b64url)! }
19
20 func testKEKUnwrapsTheJSWrappedDEK() throws {
21 let kek = HomeCrypto.kekFromPRF(data(prf))
22 let dek = try HomeCrypto.unwrapDEK(wrapped, kek: kek)
23 XCTAssertEqual(dek.withUnsafeBytes { Data($0) }, data(dekRaw),
24 "Swift KEK + unwrap must recover the JS-wrapped DEK")
25 }
26
27 func testOpensJSSealedEnvelope() throws {
28 // The full JS-sealed envelope (truncated here is not allowed — it's the
29 // exact bytes the generator printed).
30 let envelope = data(HomeCryptoVectors.envelope)
31 let dek = SymmetricKey(data: data(dekRaw))
32 let (objJSON, keys) = try HomeCrypto.openBlob(envelope, dek: dek)
33 XCTAssertEqual(keys[credId], wrapped, "header preserves the wrapped-DEK map")
34
35 struct Blob: Decodable {
36 struct Server: Decodable { let url: String; let name: String }
37 let v: Int
38 let servers: [Server]
39 }
40 let blob = try JSONDecoder().decode(Blob.self, from: objJSON)
41 XCTAssertEqual(blob.v, 1)
42 XCTAssertEqual(blob.servers.first?.url, "https://test.example.com")
43 XCTAssertEqual(blob.servers.first?.name, "Test")
44 }
45
46 func testSwiftRoundTrip() throws {
47 let dek = HomeCrypto.generateDEK()
48 let kek = HomeCrypto.kekFromPRF(Data("some-prf-output-32-bytes--------!".utf8))
49 let wrappedDEK = try HomeCrypto.wrapDEK(dek, kek: kek)
50 let obj = Data(#"{"v":1,"servers":[{"url":"https://a.example","name":"A"}]}"#.utf8)
51 let sealed = try HomeCrypto.sealBlob(objJSON: obj, dek: dek, keys: ["c1": wrappedDEK])
52
53 // Unwrap the DEK the way a real open would, then read it back.
54 let reDEK = try HomeCrypto.unwrapDEK(wrappedDEK, kek: kek)
55 let (out, keys) = try HomeCrypto.openBlob(sealed, dek: reDEK)
56 XCTAssertEqual(out, obj)
57 XCTAssertEqual(keys["c1"], wrappedDEK)
58 }
59
60 func testPaddingHidesLength() throws {
61 let small = try HomeCrypto.pad(Data([1, 2, 3]))
62 let bigger = try HomeCrypto.pad(Data(repeating: 9, count: 5000))
63 XCTAssertEqual(small.count, HomeCrypto.blobPad)
64 XCTAssertEqual(bigger.count, HomeCrypto.blobPad)
65 XCTAssertEqual(try HomeCrypto.unpad(small), Data([1, 2, 3]))
66 XCTAssertEqual(try HomeCrypto.unpad(bigger), Data(repeating: 9, count: 5000))
67 }
68}
69