p2p-swift / Tests / IrdP2PCryptoTests / RoomCryptoVectorTests.swift
540 words
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import Foundation
import Testing
@testable import IrdP2PCrypto

@Suite struct RoomCryptoVectorTests {
    @Test func derivationsMatchReference() throws {
        for d in try vector("room-crypto")["derivations"]!.arr {
            let rc = RoomCrypto(secret: try secret(d["secret"]!.str), appSalt: d["appSalt"]!.str, peerId: try peer("00000000000000000000000000000001"))
            #expect(rc.roomId.string == d["roomId"]!.str, "roomId \(d["appSalt"]!.str)")
            #expect(rc.roomTopic == d["roomTopic"]!.str)
            #expect(rc.dbName == d["dbName"]!.str)
            #expect(rc.mailboxRoomId == d["mailboxRoomId"]!.str)
            #expect(RoomCrypto.roomId(secret: try secret(d["secret"]!.str), appSalt: d["appSalt"]!.str) == rc.roomId)
        }
    }

    @Test func sealerIVsAreEpochThenBigEndianCounter() throws {
        let ivs = try vector("room-crypto")["sealerIvs"]!.arr.map(\.str)
        var s = Sealer(key: .init(size: .bits256), epoch: Bytes.fromHex(String(ivs[0].prefix(8)))!)
        for expected in ivs {
            let iv = s.nextIV()
            #expect(Bytes.hex(iv) == expected)
        }
    }

    /// Sealing with the vector's epoch reproduces every reference envelope byte for byte,
    /// and the recipient opens them all.
    @Test func envelopesAreByteExactAndOpen() throws {
        let e = try vector("room-crypto")["envelopes"]!
        let sec = try secret(e["secret"]!.str), salt = e["appSalt"]!.str
        let a = try peer(e["from"]!.str), b = try peer(e["to"]!.str)
        var sender = RoomCrypto(secret: sec, appSalt: salt, peerId: a, epoch: Bytes.fromHex(e["epoch"]!.str)!)
        var recipient = RoomCrypto(secret: sec, appSalt: salt, peerId: b)

        let hello = e["signalHello"]!, offer = e["signalOffer"]!, msg = e["msg"]!, bin = e["binary"]!
        let sealedHello = sender.sealSignal(hello["plaintext"]!, to: b)
        #expect(sealedHello.json.serialized == hello["envelope"]!.serialized)
        let sealedOffer = sender.sealSignal(offer["plaintext"]!, to: b)
        #expect(sealedOffer.json.serialized == offer["envelope"]!.serialized)
        let sealedMsg = sender.sealMessage(msg["plaintext"]!)
        #expect(sealedMsg.json.serialized == msg["envelope"]!.serialized)
        let sealedBin = sender.sealMessageBinary(bin["plaintext"]!.bytes)
        #expect(Bytes.hex(sealedBin) == bin["sealedHex"]!.str)

        let openedHello = recipient.openSignal(Envelope(json: hello["envelope"]!)!, from: a)
        #expect(openedHello == hello["plaintext"]!)
        let openedOffer = recipient.openSignal(Envelope(json: offer["envelope"]!)!, from: a)
        #expect(openedOffer == offer["plaintext"]!)
        let openedMsg = recipient.openMessage(Envelope(json: msg["envelope"]!)!, from: a)
        #expect(openedMsg == msg["plaintext"]!)
        let openedBin = recipient.openMessageBinary(Bytes.fromHex(bin["sealedHex"]!.str)!, from: a)
        #expect(openedBin == bin["plaintext"]!.bytes)
        // opened plaintext keeps the sender's key order
        #expect(openedHello!.serialized == hello["plaintext"]!.serialized)
    }

    @Test func atRestMatchesReference() throws {
        let v = try vector("room-crypto")
        let e = v["envelopes"]!, r = v["atRest"]!
        let rc = RoomCrypto(secret: try secret(e["secret"]!.str), appSalt: e["appSalt"]!.str, peerId: try peer(e["from"]!.str))
        let mbx = r["mbx"]!
        let pt = mbx["plaintext"]!.serializedData
        #expect(Bytes.hex(rc.mailboxCipher.seal(pt, iv: Bytes.fromHex(mbx["ivHex"]!.str)!)) == mbx["sealedHex"]!.str)
        #expect(rc.mailboxCipher.open(Bytes.fromHex(mbx["sealedHex"]!.str)!) == pt)
        let entry = r["entry"]!
        #expect(Bytes.hex(rc.logEntryCipher.seal(entry["plaintextBytes"]!.bytes, iv: Bytes.fromHex(entry["ivHex"]!.str)!)) == entry["sealedHex"]!.str)
        // random-IV path round-trips and never repeats an IV
        let s1 = rc.mailboxCipher.seal(pt), s2 = rc.mailboxCipher.seal(pt)
        #expect(s1 != s2)
        #expect(rc.mailboxCipher.open(s1) == pt)
        #expect(rc.mailboxCipher.open(s2) == pt)
    }

    @Test func tamperTableAllOpenToNil() throws {
        let v = try vector("room-crypto")
        let e = v["envelopes"]!
        let sec = try secret(e["secret"]!.str), salt = e["appSalt"]!.str
        let a = try peer(e["from"]!.str), b = try peer(e["to"]!.str)
        for (name, c) in v["tamper"]!.obj.pairs {
            let opener = try peer(c["by"]?.str ?? b.hex)
            let from = try peer(c["from"]?.str ?? a.hex)
            var rc = RoomCrypto(secret: try secret(c["secret"]?.str ?? sec.base64URL), appSalt: c["appSalt"]?.str ?? salt, peerId: opener)
            switch c["open"]!.str {
            case "signal":
                var opened: JSONValue? = nil
                if let env = Envelope(json: c["env"]!) { opened = rc.openSignal(env, from: from) }
                #expect(opened == nil, "\(name)")
            case "msg":
                let opened = rc.openMessage(Envelope(json: c["env"]!)!, from: from)
                #expect(opened == nil, "\(name)")
            case "binary":
                let opened = rc.openMessageBinary(Bytes.fromHex(c["sealedHex"]!.str)!, from: from)
                #expect(opened == nil, "\(name)")
            case "atRest":
                let cipher = c["aadSuffix"]?.str == "entry" ? rc.logEntryCipher : rc.mailboxCipher
                #expect(cipher.open(Bytes.fromHex(c["sealedHex"]!.str)!) == nil, "\(name)")
            default: Issue.record("unknown tamper kind \(name)")
            }
        }
    }

    @Test func keyCacheIsBounded() {
        var cache = KeyCache(limit: 3)
        for i in 0..<10 { cache.put("k\(i)", .init(size: .bits256)) }
        #expect(cache.count == 3)
        #expect(cache.get("k0") == nil)
        #expect(cache.get("k9") != nil)
    }

    @Test func freshRoomCryptoUsesRandomEpochs() throws {
        let sec = RoomSecret.random(), p = PeerId.random()
        var x = RoomCrypto(secret: sec, appSalt: "t", peerId: p)
        var y = RoomCrypto(secret: sec, appSalt: "t", peerId: p)
        let (ex, ey) = (x.sealMessage(.null), y.sealMessage(.null))
        #expect(ex.iv != ey.iv)
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-swift.git