p2p-swift / Sources / IrdP2PCrypto / Sealer.swift
344 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
import Foundation
import CryptoKit

/// Seals under one per-session key with IV = 4 random epoch bytes ‖ 8-byte
/// big-endian counter. One sealer per key per room session; the counter never
/// repeats within an epoch and no API accepts a caller-supplied IV.
public struct Sealer: Sendable {
    let key: SymmetricKey
    public let epoch: Data
    public private(set) var counter: UInt64

    init(key: SymmetricKey) {
        self.init(key: key, epoch: Bytes.random(4), counter: 0)
    }

    /// Deterministic sealer — for tests and vector reproduction only.
    init(key: SymmetricKey, epoch: Data, counter: UInt64 = 0) {
        precondition(epoch.count == 4, "epoch is 4 bytes")
        self.key = key
        self.epoch = epoch
        self.counter = counter
    }

    mutating func nextIV() -> Data {
        var iv = Data(capacity: 12)
        iv.append(epoch)
        var be = counter.bigEndian
        withUnsafeBytes(of: &be) { iv.append(contentsOf: $0) }
        counter &+= 1
        return iv
    }

    /// Seals a JSON value (serialized `JSON.stringify`-style) into a JSON envelope.
    public mutating func seal(_ value: JSONValue, aad: Data) -> Envelope {
        let iv = nextIV()
        return Envelope(iv: iv, ciphertext: AEAD.seal(value.serializedData, key: key, iv: iv, aad: aad))
    }

    /// Seals raw bytes into the binary layout `0x01 ‖ iv ‖ ct‖tag`.
    public mutating func sealBinary(_ data: Data, aad: Data) -> Data {
        let iv = nextIV()
        return AEAD.binary(prefix: BinaryEnvelopePrefix.session, iv: iv, ciphertext: AEAD.seal(data, key: key, iv: iv, aad: aad))
    }
}

/// Room-wide at-rest cipher (mailbox payloads): random 12-byte IVs, because
/// many sessions share the key and counters would collide. Layout
/// `0x02 ‖ iv ‖ ct‖tag`.
public struct AtRestCipher: Sendable {
    let key: SymmetricKey
    let aad: Data

    init(key: SymmetricKey, aad: Data) {
        self.key = key
        self.aad = aad
    }

    public func seal(_ data: Data) -> Data {
        seal(data, iv: Bytes.random(12))
    }

    /// Deterministic variant for tests and vector reproduction.
    func seal(_ data: Data, iv: Data) -> Data {
        AEAD.binary(prefix: BinaryEnvelopePrefix.atRest, iv: iv, ciphertext: AEAD.seal(data, key: key, iv: iv, aad: aad))
    }

    /// `nil` on any failure.
    public func open(_ data: Data) -> Data? {
        guard let (iv, ct) = AEAD.splitBinary(data, expectedPrefix: BinaryEnvelopePrefix.atRest) else { return nil }
        return AEAD.open(ct, key: key, iv: iv, aad: aad)
    }
}

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