@haven-chat-org/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +71 -0
- package/dist/crypto/backup.d.ts +87 -0
- package/dist/crypto/backup.js +62 -0
- package/dist/crypto/backup.js.map +1 -0
- package/dist/crypto/double-ratchet.d.ts +104 -0
- package/dist/crypto/double-ratchet.js +274 -0
- package/dist/crypto/double-ratchet.js.map +1 -0
- package/dist/crypto/file.d.ts +14 -0
- package/dist/crypto/file.js +20 -0
- package/dist/crypto/file.js.map +1 -0
- package/dist/crypto/index.d.ts +9 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/keys.d.ts +61 -0
- package/dist/crypto/keys.js +79 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/crypto/passphrase.d.ts +10 -0
- package/dist/crypto/passphrase.js +142 -0
- package/dist/crypto/passphrase.js.map +1 -0
- package/dist/crypto/profile.d.ts +31 -0
- package/dist/crypto/profile.js +73 -0
- package/dist/crypto/profile.js.map +1 -0
- package/dist/crypto/sender-keys.d.ts +76 -0
- package/dist/crypto/sender-keys.js +170 -0
- package/dist/crypto/sender-keys.js.map +1 -0
- package/dist/crypto/sender-keys.test.d.ts +1 -0
- package/dist/crypto/sender-keys.test.js +272 -0
- package/dist/crypto/sender-keys.test.js.map +1 -0
- package/dist/crypto/utils.d.ts +41 -0
- package/dist/crypto/utils.js +102 -0
- package/dist/crypto/utils.js.map +1 -0
- package/dist/crypto/x3dh.d.ts +45 -0
- package/dist/crypto/x3dh.js +106 -0
- package/dist/crypto/x3dh.js.map +1 -0
- package/dist/export/__tests__/archive.test.d.ts +1 -0
- package/dist/export/__tests__/archive.test.js +276 -0
- package/dist/export/__tests__/archive.test.js.map +1 -0
- package/dist/export/archive.d.ts +38 -0
- package/dist/export/archive.js +107 -0
- package/dist/export/archive.js.map +1 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/reader.d.ts +27 -0
- package/dist/export/reader.js +101 -0
- package/dist/export/reader.js.map +1 -0
- package/dist/export/signing.d.ts +15 -0
- package/dist/export/signing.js +44 -0
- package/dist/export/signing.js.map +1 -0
- package/dist/export/types.d.ts +128 -0
- package/dist/export/types.js +3 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/net/api.d.ts +200 -0
- package/dist/net/api.js +715 -0
- package/dist/net/api.js.map +1 -0
- package/dist/net/api.test.d.ts +1 -0
- package/dist/net/api.test.js +884 -0
- package/dist/net/api.test.js.map +1 -0
- package/dist/net/index.d.ts +2 -0
- package/dist/net/index.js +3 -0
- package/dist/net/index.js.map +1 -0
- package/dist/net/ws.d.ts +71 -0
- package/dist/net/ws.js +257 -0
- package/dist/net/ws.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/memory.d.ts +24 -0
- package/dist/store/memory.js +50 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/types.d.ts +23 -0
- package/dist/store/types.js +2 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types.d.ts +850 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X3DH (Extended Triple Diffie-Hellman) key agreement.
|
|
3
|
+
*
|
|
4
|
+
* Establishes a shared secret between Alice (initiator) and Bob (responder)
|
|
5
|
+
* even if Bob is offline. Alice fetches Bob's key bundle from the server.
|
|
6
|
+
*
|
|
7
|
+
* Protocol:
|
|
8
|
+
* DH1 = DH(IK_A, SPK_B) — Alice's identity × Bob's signed prekey
|
|
9
|
+
* DH2 = DH(EK_A, IK_B) — Alice's ephemeral × Bob's identity
|
|
10
|
+
* DH3 = DH(EK_A, SPK_B) — Alice's ephemeral × Bob's signed prekey
|
|
11
|
+
* DH4 = DH(EK_A, OPK_B) — Alice's ephemeral × Bob's one-time prekey (if available)
|
|
12
|
+
* SK = HKDF(DH1 || DH2 || DH3 || DH4)
|
|
13
|
+
*
|
|
14
|
+
* All identity keys are Ed25519 (converted to X25519 for DH).
|
|
15
|
+
* All prekeys and ephemeral keys are X25519 natively.
|
|
16
|
+
*/
|
|
17
|
+
import { type IdentityKeyPair, type DHKeyPair } from "./keys.js";
|
|
18
|
+
import type { KeyBundle } from "../types.js";
|
|
19
|
+
export interface X3DHResult {
|
|
20
|
+
/** The derived shared secret (32 bytes). Initializes the Double Ratchet. */
|
|
21
|
+
sharedKey: Uint8Array;
|
|
22
|
+
/** Associated data: IK_A || IK_B (for AEAD binding in Double Ratchet). */
|
|
23
|
+
associatedData: Uint8Array;
|
|
24
|
+
/** The ephemeral public key Alice used (sent to Bob in the initial message). */
|
|
25
|
+
ephemeralPublicKey: Uint8Array;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Alice (initiator): compute X3DH shared secret from Bob's key bundle.
|
|
29
|
+
*
|
|
30
|
+
* @param aliceIdentity Alice's long-term Ed25519 identity keypair
|
|
31
|
+
* @param bobBundle Bob's key bundle fetched from the server
|
|
32
|
+
* @returns The shared key, associated data, and ephemeral key to include in the initial message
|
|
33
|
+
*/
|
|
34
|
+
export declare function x3dhInitiate(aliceIdentity: IdentityKeyPair, bobBundle: KeyBundle): X3DHResult;
|
|
35
|
+
/**
|
|
36
|
+
* Bob (responder): compute X3DH shared secret from Alice's initial message.
|
|
37
|
+
*
|
|
38
|
+
* @param bobIdentity Bob's long-term Ed25519 identity keypair
|
|
39
|
+
* @param bobSignedPreKey Bob's signed prekey pair (the one used in his bundle)
|
|
40
|
+
* @param bobOneTimePreKey Bob's one-time prekey pair (null if not used)
|
|
41
|
+
* @param aliceIdentityPub Alice's Ed25519 public identity key (from the initial message)
|
|
42
|
+
* @param aliceEphemeralPub Alice's ephemeral X25519 public key (from the initial message)
|
|
43
|
+
* @returns The shared key and associated data
|
|
44
|
+
*/
|
|
45
|
+
export declare function x3dhRespond(bobIdentity: IdentityKeyPair, bobSignedPreKey: DHKeyPair, bobOneTimePreKey: DHKeyPair | null, aliceIdentityPub: Uint8Array, aliceEphemeralPub: Uint8Array): Omit<X3DHResult, "ephemeralPublicKey">;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X3DH (Extended Triple Diffie-Hellman) key agreement.
|
|
3
|
+
*
|
|
4
|
+
* Establishes a shared secret between Alice (initiator) and Bob (responder)
|
|
5
|
+
* even if Bob is offline. Alice fetches Bob's key bundle from the server.
|
|
6
|
+
*
|
|
7
|
+
* Protocol:
|
|
8
|
+
* DH1 = DH(IK_A, SPK_B) — Alice's identity × Bob's signed prekey
|
|
9
|
+
* DH2 = DH(EK_A, IK_B) — Alice's ephemeral × Bob's identity
|
|
10
|
+
* DH3 = DH(EK_A, SPK_B) — Alice's ephemeral × Bob's signed prekey
|
|
11
|
+
* DH4 = DH(EK_A, OPK_B) — Alice's ephemeral × Bob's one-time prekey (if available)
|
|
12
|
+
* SK = HKDF(DH1 || DH2 || DH3 || DH4)
|
|
13
|
+
*
|
|
14
|
+
* All identity keys are Ed25519 (converted to X25519 for DH).
|
|
15
|
+
* All prekeys and ephemeral keys are X25519 natively.
|
|
16
|
+
*/
|
|
17
|
+
import { dh, ed25519PkToX25519, ed25519SkToX25519, generateDHKeyPair, verifySignature, } from "./keys.js";
|
|
18
|
+
import { hkdf, fromBase64 } from "./utils.js";
|
|
19
|
+
const X3DH_INFO = new TextEncoder().encode("haven_x3dh");
|
|
20
|
+
const PADDING = new Uint8Array(32).fill(0xff);
|
|
21
|
+
/**
|
|
22
|
+
* Alice (initiator): compute X3DH shared secret from Bob's key bundle.
|
|
23
|
+
*
|
|
24
|
+
* @param aliceIdentity Alice's long-term Ed25519 identity keypair
|
|
25
|
+
* @param bobBundle Bob's key bundle fetched from the server
|
|
26
|
+
* @returns The shared key, associated data, and ephemeral key to include in the initial message
|
|
27
|
+
*/
|
|
28
|
+
export function x3dhInitiate(aliceIdentity, bobBundle) {
|
|
29
|
+
// Decode Bob's keys from base64
|
|
30
|
+
const bobIdentityEd = fromBase64(bobBundle.identity_key);
|
|
31
|
+
const bobSignedPreKey = fromBase64(bobBundle.signed_prekey);
|
|
32
|
+
const bobSignedPreKeySig = fromBase64(bobBundle.signed_prekey_sig);
|
|
33
|
+
const bobOneTimePreKey = bobBundle.one_time_prekey
|
|
34
|
+
? fromBase64(bobBundle.one_time_prekey)
|
|
35
|
+
: null;
|
|
36
|
+
// Verify Bob's signed prekey signature using his Ed25519 identity key
|
|
37
|
+
if (!verifySignature(bobSignedPreKeySig, bobSignedPreKey, bobIdentityEd)) {
|
|
38
|
+
throw new Error("X3DH: invalid signed prekey signature");
|
|
39
|
+
}
|
|
40
|
+
// Convert identity keys to X25519 for DH
|
|
41
|
+
const aliceIdentityX = ed25519SkToX25519(aliceIdentity.privateKey);
|
|
42
|
+
const bobIdentityX = ed25519PkToX25519(bobIdentityEd);
|
|
43
|
+
// Generate Alice's ephemeral X25519 keypair
|
|
44
|
+
const ephemeral = generateDHKeyPair();
|
|
45
|
+
// Compute DH values
|
|
46
|
+
const dh1 = dh(aliceIdentityX, bobSignedPreKey);
|
|
47
|
+
const dh2 = dh(ephemeral.privateKey, bobIdentityX);
|
|
48
|
+
const dh3 = dh(ephemeral.privateKey, bobSignedPreKey);
|
|
49
|
+
// Concatenate: F || DH1 || DH2 || DH3 [|| DH4]
|
|
50
|
+
const parts = [PADDING, dh1, dh2, dh3];
|
|
51
|
+
if (bobOneTimePreKey) {
|
|
52
|
+
parts.push(dh(ephemeral.privateKey, bobOneTimePreKey));
|
|
53
|
+
}
|
|
54
|
+
const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
|
|
55
|
+
const ikm = new Uint8Array(totalLen);
|
|
56
|
+
let offset = 0;
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
ikm.set(part, offset);
|
|
59
|
+
offset += part.length;
|
|
60
|
+
}
|
|
61
|
+
// Derive shared key: HKDF(salt=zeros, ikm, info="haven_x3dh", L=32)
|
|
62
|
+
const salt = new Uint8Array(32);
|
|
63
|
+
const sharedKey = hkdf(salt, ikm, X3DH_INFO, 32);
|
|
64
|
+
// Associated data binds the session to both identities
|
|
65
|
+
const associatedData = new Uint8Array(64);
|
|
66
|
+
associatedData.set(aliceIdentity.publicKey, 0);
|
|
67
|
+
associatedData.set(bobIdentityEd, 32);
|
|
68
|
+
return { sharedKey, associatedData, ephemeralPublicKey: ephemeral.publicKey };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Bob (responder): compute X3DH shared secret from Alice's initial message.
|
|
72
|
+
*
|
|
73
|
+
* @param bobIdentity Bob's long-term Ed25519 identity keypair
|
|
74
|
+
* @param bobSignedPreKey Bob's signed prekey pair (the one used in his bundle)
|
|
75
|
+
* @param bobOneTimePreKey Bob's one-time prekey pair (null if not used)
|
|
76
|
+
* @param aliceIdentityPub Alice's Ed25519 public identity key (from the initial message)
|
|
77
|
+
* @param aliceEphemeralPub Alice's ephemeral X25519 public key (from the initial message)
|
|
78
|
+
* @returns The shared key and associated data
|
|
79
|
+
*/
|
|
80
|
+
export function x3dhRespond(bobIdentity, bobSignedPreKey, bobOneTimePreKey, aliceIdentityPub, aliceEphemeralPub) {
|
|
81
|
+
// Convert identity keys to X25519 for DH
|
|
82
|
+
const bobIdentityX = ed25519SkToX25519(bobIdentity.privateKey);
|
|
83
|
+
const aliceIdentityX = ed25519PkToX25519(aliceIdentityPub);
|
|
84
|
+
// Compute DH values (mirror of Alice's computation)
|
|
85
|
+
const dh1 = dh(bobSignedPreKey.privateKey, aliceIdentityX);
|
|
86
|
+
const dh2 = dh(bobIdentityX, aliceEphemeralPub);
|
|
87
|
+
const dh3 = dh(bobSignedPreKey.privateKey, aliceEphemeralPub);
|
|
88
|
+
const parts = [PADDING, dh1, dh2, dh3];
|
|
89
|
+
if (bobOneTimePreKey) {
|
|
90
|
+
parts.push(dh(bobOneTimePreKey.privateKey, aliceEphemeralPub));
|
|
91
|
+
}
|
|
92
|
+
const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
|
|
93
|
+
const ikm = new Uint8Array(totalLen);
|
|
94
|
+
let offset = 0;
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
ikm.set(part, offset);
|
|
97
|
+
offset += part.length;
|
|
98
|
+
}
|
|
99
|
+
const salt = new Uint8Array(32);
|
|
100
|
+
const sharedKey = hkdf(salt, ikm, X3DH_INFO, 32);
|
|
101
|
+
const associatedData = new Uint8Array(64);
|
|
102
|
+
associatedData.set(aliceIdentityPub, 0);
|
|
103
|
+
associatedData.set(bobIdentity.publicKey, 32);
|
|
104
|
+
return { sharedKey, associatedData };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=x3dh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x3dh.js","sourceRoot":"","sources":["../../src/crypto/x3dh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
2
|
+
import { initSodium, getSodium, toBase64 } from "../../crypto/utils.js";
|
|
3
|
+
import { HavenArchiveBuilder } from "../archive.js";
|
|
4
|
+
import { HavenArchiveReader } from "../reader.js";
|
|
5
|
+
import { signManifest, verifyManifest, computeFileHash } from "../signing.js";
|
|
6
|
+
// ── Test Data ───────────────────────────────────────────
|
|
7
|
+
function makeChannelExport(name = "general") {
|
|
8
|
+
return {
|
|
9
|
+
channel: {
|
|
10
|
+
id: "ch-001",
|
|
11
|
+
name,
|
|
12
|
+
type: "text",
|
|
13
|
+
encrypted: true,
|
|
14
|
+
category: "Text Channels",
|
|
15
|
+
created_at: "2025-03-01T00:00:00Z",
|
|
16
|
+
},
|
|
17
|
+
exported_at: "2026-02-22T12:00:00Z",
|
|
18
|
+
exported_by: "user-001",
|
|
19
|
+
message_count: 2,
|
|
20
|
+
date_range: { from: "2025-03-15T14:30:00Z", to: "2025-03-15T15:00:00Z" },
|
|
21
|
+
messages: [
|
|
22
|
+
{
|
|
23
|
+
id: "msg-001",
|
|
24
|
+
sender_id: "user-001",
|
|
25
|
+
sender_name: "alice",
|
|
26
|
+
sender_display_name: "Alice",
|
|
27
|
+
timestamp: "2025-03-15T14:30:00Z",
|
|
28
|
+
text: "Hello everyone!",
|
|
29
|
+
content_type: "text/plain",
|
|
30
|
+
formatting: null,
|
|
31
|
+
edited: false,
|
|
32
|
+
reply_to: null,
|
|
33
|
+
type: "user",
|
|
34
|
+
reactions: [{ emoji: "wave", count: 2, users: ["bob", "charlie"] }],
|
|
35
|
+
pinned: false,
|
|
36
|
+
attachments: [],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "msg-002",
|
|
40
|
+
sender_id: "user-002",
|
|
41
|
+
sender_name: "bob",
|
|
42
|
+
sender_display_name: "Bob",
|
|
43
|
+
timestamp: "2025-03-15T15:00:00Z",
|
|
44
|
+
text: "Hey Alice!",
|
|
45
|
+
content_type: "text/plain",
|
|
46
|
+
formatting: null,
|
|
47
|
+
edited: false,
|
|
48
|
+
reply_to: "msg-001",
|
|
49
|
+
type: "user",
|
|
50
|
+
reactions: [],
|
|
51
|
+
pinned: true,
|
|
52
|
+
attachments: [],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function makeServerExport() {
|
|
58
|
+
return {
|
|
59
|
+
server: {
|
|
60
|
+
id: "srv-001",
|
|
61
|
+
name: "Test Server",
|
|
62
|
+
description: "A test server",
|
|
63
|
+
icon_url: null,
|
|
64
|
+
created_at: "2025-03-01T00:00:00Z",
|
|
65
|
+
},
|
|
66
|
+
categories: [{ id: "cat-001", name: "Text Channels", position: 0 }],
|
|
67
|
+
channels: [
|
|
68
|
+
{
|
|
69
|
+
id: "ch-001",
|
|
70
|
+
name: "general",
|
|
71
|
+
type: "text",
|
|
72
|
+
category_id: "cat-001",
|
|
73
|
+
position: 0,
|
|
74
|
+
encrypted: true,
|
|
75
|
+
is_private: false,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
roles: [
|
|
79
|
+
{
|
|
80
|
+
id: "role-001",
|
|
81
|
+
name: "Moderator",
|
|
82
|
+
color: "#3498db",
|
|
83
|
+
permissions: 8192,
|
|
84
|
+
position: 1,
|
|
85
|
+
is_default: false,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
members: [
|
|
89
|
+
{
|
|
90
|
+
user_id: "user-001",
|
|
91
|
+
username: "alice",
|
|
92
|
+
display_name: "Alice",
|
|
93
|
+
nickname: null,
|
|
94
|
+
roles: ["role-001"],
|
|
95
|
+
joined_at: "2025-03-01T00:00:00Z",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
emojis: [],
|
|
99
|
+
permission_overwrites: [],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ── Tests ───────────────────────────────────────────────
|
|
103
|
+
beforeAll(async () => {
|
|
104
|
+
await initSodium();
|
|
105
|
+
});
|
|
106
|
+
describe("HavenArchiveBuilder + HavenArchiveReader", () => {
|
|
107
|
+
const metadata = {
|
|
108
|
+
exportedBy: { user_id: "user-001", username: "alice", identity_key: "" },
|
|
109
|
+
serverId: "srv-001",
|
|
110
|
+
instanceUrl: "https://haven.example.com",
|
|
111
|
+
};
|
|
112
|
+
it("round-trips channel data through build and read", async () => {
|
|
113
|
+
const sodium = getSodium();
|
|
114
|
+
const kp = sodium.crypto_sign_keypair();
|
|
115
|
+
const meta = {
|
|
116
|
+
...metadata,
|
|
117
|
+
exportedBy: { ...metadata.exportedBy, identity_key: toBase64(kp.publicKey) },
|
|
118
|
+
};
|
|
119
|
+
const builder = new HavenArchiveBuilder(meta);
|
|
120
|
+
builder.addChannel(makeChannelExport());
|
|
121
|
+
builder.addServerMeta(makeServerExport());
|
|
122
|
+
builder.addAuditLog([{ action: "test", timestamp: "2026-02-22T12:00:00Z" }]);
|
|
123
|
+
const attachment = new TextEncoder().encode("fake image data");
|
|
124
|
+
builder.addAttachment("att-001", attachment);
|
|
125
|
+
const zip = await builder.build(kp.privateKey);
|
|
126
|
+
expect(zip).toBeInstanceOf(Uint8Array);
|
|
127
|
+
expect(zip.byteLength).toBeGreaterThan(0);
|
|
128
|
+
const reader = await HavenArchiveReader.fromBlob(zip);
|
|
129
|
+
const manifest = reader.getManifest();
|
|
130
|
+
expect(manifest.version).toBe(1);
|
|
131
|
+
expect(manifest.format).toBe("haven-export");
|
|
132
|
+
expect(manifest.exported_by.username).toBe("alice");
|
|
133
|
+
expect(manifest.server_id).toBe("srv-001");
|
|
134
|
+
expect(manifest.message_count).toBe(2);
|
|
135
|
+
expect(manifest.user_signature).toBeDefined();
|
|
136
|
+
expect(Object.keys(manifest.files)).toContain("channels/general.json");
|
|
137
|
+
expect(Object.keys(manifest.files)).toContain("server.json");
|
|
138
|
+
expect(Object.keys(manifest.files)).toContain("attachments/att-001.bin");
|
|
139
|
+
expect(Object.keys(manifest.files)).toContain("audit-log.json");
|
|
140
|
+
// Channel export round-trip
|
|
141
|
+
const channel = reader.getChannelExport("general");
|
|
142
|
+
expect(channel).not.toBeNull();
|
|
143
|
+
expect(channel.message_count).toBe(2);
|
|
144
|
+
expect(channel.messages[0].text).toBe("Hello everyone!");
|
|
145
|
+
expect(channel.messages[1].pinned).toBe(true);
|
|
146
|
+
// Server meta round-trip
|
|
147
|
+
const server = reader.getServerMeta();
|
|
148
|
+
expect(server).not.toBeNull();
|
|
149
|
+
expect(server.server.name).toBe("Test Server");
|
|
150
|
+
expect(server.roles).toHaveLength(1);
|
|
151
|
+
// Attachment round-trip
|
|
152
|
+
const att = reader.getAttachment("attachments/att-001.bin");
|
|
153
|
+
expect(att).not.toBeNull();
|
|
154
|
+
expect(new TextDecoder().decode(att)).toBe("fake image data");
|
|
155
|
+
// Audit log round-trip
|
|
156
|
+
const log = reader.getAuditLog();
|
|
157
|
+
expect(log).not.toBeNull();
|
|
158
|
+
expect(log).toHaveLength(1);
|
|
159
|
+
expect(log[0].action).toBe("test");
|
|
160
|
+
});
|
|
161
|
+
it("verification passes for untampered archive", async () => {
|
|
162
|
+
const sodium = getSodium();
|
|
163
|
+
const kp = sodium.crypto_sign_keypair();
|
|
164
|
+
const meta = {
|
|
165
|
+
...metadata,
|
|
166
|
+
exportedBy: { ...metadata.exportedBy, identity_key: toBase64(kp.publicKey) },
|
|
167
|
+
};
|
|
168
|
+
const builder = new HavenArchiveBuilder(meta);
|
|
169
|
+
builder.addChannel(makeChannelExport());
|
|
170
|
+
const zip = await builder.build(kp.privateKey);
|
|
171
|
+
const reader = await HavenArchiveReader.fromBlob(zip);
|
|
172
|
+
const result = await reader.verify();
|
|
173
|
+
expect(result.valid).toBe(true);
|
|
174
|
+
expect(result.issues).toHaveLength(0);
|
|
175
|
+
});
|
|
176
|
+
it("verification fails for tampered file", async () => {
|
|
177
|
+
const sodium = getSodium();
|
|
178
|
+
const kp = sodium.crypto_sign_keypair();
|
|
179
|
+
const meta = {
|
|
180
|
+
...metadata,
|
|
181
|
+
exportedBy: { ...metadata.exportedBy, identity_key: toBase64(kp.publicKey) },
|
|
182
|
+
};
|
|
183
|
+
const builder = new HavenArchiveBuilder(meta);
|
|
184
|
+
builder.addChannel(makeChannelExport());
|
|
185
|
+
const zip = await builder.build(kp.privateKey);
|
|
186
|
+
// Read the archive, tamper with a file, then verify
|
|
187
|
+
const reader = await HavenArchiveReader.fromBlob(zip);
|
|
188
|
+
// Access internal files and tamper
|
|
189
|
+
const manifest = reader.getManifest();
|
|
190
|
+
// Tamper: change a hash in the manifest to simulate file corruption
|
|
191
|
+
const firstFile = Object.keys(manifest.files)[0];
|
|
192
|
+
manifest.files[firstFile].sha256 = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
193
|
+
// Re-verify with tampered manifest — the hash won't match
|
|
194
|
+
const result = await reader.verify();
|
|
195
|
+
// The reader still has the original manifest, so we need to test differently.
|
|
196
|
+
// Let's instead rebuild with tampered data.
|
|
197
|
+
// Actually the reader's verify checks files against its own manifest,
|
|
198
|
+
// and since we mutated the manifest object in place, it should fail.
|
|
199
|
+
expect(result.valid).toBe(false);
|
|
200
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
201
|
+
expect(result.issues[0]).toContain("Hash mismatch");
|
|
202
|
+
});
|
|
203
|
+
it("verification fails for missing file referenced in manifest", async () => {
|
|
204
|
+
const sodium = getSodium();
|
|
205
|
+
const kp = sodium.crypto_sign_keypair();
|
|
206
|
+
const meta = {
|
|
207
|
+
...metadata,
|
|
208
|
+
exportedBy: { ...metadata.exportedBy, identity_key: toBase64(kp.publicKey) },
|
|
209
|
+
};
|
|
210
|
+
const builder = new HavenArchiveBuilder(meta);
|
|
211
|
+
builder.addChannel(makeChannelExport());
|
|
212
|
+
const zip = await builder.build(kp.privateKey);
|
|
213
|
+
const reader = await HavenArchiveReader.fromBlob(zip);
|
|
214
|
+
const manifest = reader.getManifest();
|
|
215
|
+
// Add a fake file entry to manifest
|
|
216
|
+
manifest.files["channels/nonexistent.json"] = { sha256: "abc", size: 100 };
|
|
217
|
+
const result = await reader.verify();
|
|
218
|
+
expect(result.valid).toBe(false);
|
|
219
|
+
expect(result.issues.some((i) => i.includes("Missing file"))).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe("signManifest / verifyManifest", () => {
|
|
223
|
+
it("round-trips signing and verification", async () => {
|
|
224
|
+
const sodium = getSodium();
|
|
225
|
+
const kp = sodium.crypto_sign_keypair();
|
|
226
|
+
const manifest = {
|
|
227
|
+
version: 1,
|
|
228
|
+
format: "haven-export",
|
|
229
|
+
exported_by: {
|
|
230
|
+
user_id: "user-001",
|
|
231
|
+
username: "alice",
|
|
232
|
+
identity_key: toBase64(kp.publicKey),
|
|
233
|
+
},
|
|
234
|
+
exported_at: "2026-02-22T12:00:00Z",
|
|
235
|
+
instance_url: "https://haven.example.com",
|
|
236
|
+
files: {},
|
|
237
|
+
message_count: 0,
|
|
238
|
+
date_range: { from: "2026-01-01T00:00:00Z", to: "2026-02-22T12:00:00Z" },
|
|
239
|
+
};
|
|
240
|
+
const sig = signManifest(manifest, kp.privateKey);
|
|
241
|
+
expect(typeof sig).toBe("string");
|
|
242
|
+
expect(sig.length).toBeGreaterThan(0);
|
|
243
|
+
expect(verifyManifest(manifest, sig, kp.publicKey)).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
it("rejects invalid signature", async () => {
|
|
246
|
+
const sodium = getSodium();
|
|
247
|
+
const kp1 = sodium.crypto_sign_keypair();
|
|
248
|
+
const kp2 = sodium.crypto_sign_keypair();
|
|
249
|
+
const manifest = {
|
|
250
|
+
version: 1,
|
|
251
|
+
format: "haven-export",
|
|
252
|
+
exported_by: {
|
|
253
|
+
user_id: "user-001",
|
|
254
|
+
username: "alice",
|
|
255
|
+
identity_key: toBase64(kp1.publicKey),
|
|
256
|
+
},
|
|
257
|
+
exported_at: "2026-02-22T12:00:00Z",
|
|
258
|
+
instance_url: "https://haven.example.com",
|
|
259
|
+
files: {},
|
|
260
|
+
message_count: 0,
|
|
261
|
+
date_range: { from: "2026-01-01T00:00:00Z", to: "2026-02-22T12:00:00Z" },
|
|
262
|
+
};
|
|
263
|
+
const sig = signManifest(manifest, kp1.privateKey);
|
|
264
|
+
// Verify with wrong key should fail
|
|
265
|
+
expect(verifyManifest(manifest, sig, kp2.publicKey)).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe("computeFileHash", () => {
|
|
269
|
+
it("produces consistent SHA-256 hex digest", async () => {
|
|
270
|
+
const data = new TextEncoder().encode("hello world");
|
|
271
|
+
const hash = await computeFileHash(data);
|
|
272
|
+
// SHA-256 of "hello world"
|
|
273
|
+
expect(hash).toBe("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
//# sourceMappingURL=archive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.test.js","sourceRoot":"","sources":["../../../src/export/__tests__/archive.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAG9E,2DAA2D;AAE3D,SAAS,iBAAiB,CAAC,IAAI,GAAG,SAAS;IACzC,OAAO;QACL,OAAO,EAAE;YACP,EAAE,EAAE,QAAQ;YACZ,IAAI;YACJ,IAAI,EAAE,MAAM;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,eAAe;YACzB,UAAU,EAAE,sBAAsB;SACnC;QACD,WAAW,EAAE,sBAAsB;QACnC,WAAW,EAAE,UAAU;QACvB,aAAa,EAAE,CAAC;QAChB,UAAU,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,sBAAsB,EAAE;QACxE,QAAQ,EAAE;YACR;gBACE,EAAE,EAAE,SAAS;gBACb,SAAS,EAAE,UAAU;gBACrB,WAAW,EAAE,OAAO;gBACpB,mBAAmB,EAAE,OAAO;gBAC5B,SAAS,EAAE,sBAAsB;gBACjC,IAAI,EAAE,iBAAiB;gBACvB,YAAY,EAAE,YAAY;gBAC1B,UAAU,EAAE,IAAI;gBAChB,MAAM,EAAE,KAAK;gBACb,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,MAAM;gBACZ,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;gBACnE,MAAM,EAAE,KAAK;gBACb,WAAW,EAAE,EAAE;aAChB;YACD;gBACE,EAAE,EAAE,SAAS;gBACb,SAAS,EAAE,UAAU;gBACrB,WAAW,EAAE,KAAK;gBAClB,mBAAmB,EAAE,KAAK;gBAC1B,SAAS,EAAE,sBAAsB;gBACjC,IAAI,EAAE,YAAY;gBAClB,YAAY,EAAE,YAAY;gBAC1B,UAAU,EAAE,IAAI;gBAChB,MAAM,EAAE,KAAK;gBACb,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,MAAM;gBACZ,SAAS,EAAE,EAAE;gBACb,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,EAAE;aAChB;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO;QACL,MAAM,EAAE;YACN,EAAE,EAAE,SAAS;YACb,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,eAAe;YAC5B,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,sBAAsB;SACnC;QACD,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACnE,QAAQ,EAAE;YACR;gBACE,EAAE,EAAE,QAAQ;gBACZ,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,MAAM;gBACZ,WAAW,EAAE,SAAS;gBACtB,QAAQ,EAAE,CAAC;gBACX,SAAS,EAAE,IAAI;gBACf,UAAU,EAAE,KAAK;aAClB;SACF;QACD,KAAK,EAAE;YACL;gBACE,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,SAAS;gBAChB,WAAW,EAAE,IAAI;gBACjB,QAAQ,EAAE,CAAC;gBACX,UAAU,EAAE,KAAK;aAClB;SACF;QACD,OAAO,EAAE;YACP;gBACE,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,OAAO;gBACjB,YAAY,EAAE,OAAO;gBACrB,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,CAAC,UAAU,CAAC;gBACnB,SAAS,EAAE,sBAAsB;aAClC;SACF;QACD,MAAM,EAAE,EAAE;QACV,qBAAqB,EAAE,EAAE;KAC1B,CAAC;AACJ,CAAC;AAED,2DAA2D;AAE3D,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,UAAU,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,MAAM,QAAQ,GAAG;QACf,UAAU,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE;QACxE,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,2BAA2B;KACzC,CAAC;IAEF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG;YACX,GAAG,QAAQ;YACX,UAAU,EAAE,EAAE,GAAG,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE;SAC7E,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC;QAE7E,MAAM,UAAU,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC/D,OAAO,CAAC,aAAa,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAE7C,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QAEtC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACvE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC7D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAEhE,4BAA4B;QAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,OAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,OAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/C,yBAAyB;QACzB,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChD,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAEtC,wBAAwB;QACxB,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAI,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAE/D,uBAAuB;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG;YACX,GAAG,QAAQ;YACX,UAAU,EAAE,EAAE,GAAG,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE;SAC7E,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG;YACX,GAAG,QAAQ;YACX,UAAU,EAAE,EAAE,GAAG,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE;SAC7E,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAE/C,oDAAoD;QACpD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,mCAAmC;QACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QAEtC,oEAAoE;QACpE,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,kEAAkE,CAAC;QAEtG,0DAA0D;QAC1D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACrC,8EAA8E;QAC9E,4CAA4C;QAC5C,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG;YACX,GAAG,QAAQ;YACX,UAAU,EAAE,EAAE,GAAG,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE;SAC7E,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QAEtC,oCAAoC;QACpC,QAAQ,CAAC,KAAK,CAAC,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAE3E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAExC,MAAM,QAAQ,GAAG;YACf,OAAO,EAAE,CAAU;YACnB,MAAM,EAAE,cAAuB;YAC/B,WAAW,EAAE;gBACX,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,OAAO;gBACjB,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC;aACrC;YACD,WAAW,EAAE,sBAAsB;YACnC,YAAY,EAAE,2BAA2B;YACzC,KAAK,EAAE,EAAE;YACT,aAAa,EAAE,CAAC;YAChB,UAAU,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,sBAAsB,EAAE;SACzE,CAAC;QAEF,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAEtC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAEzC,MAAM,QAAQ,GAAG;YACf,OAAO,EAAE,CAAU;YACnB,MAAM,EAAE,cAAuB;YAC/B,WAAW,EAAE;gBACX,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,OAAO;gBACjB,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;aACtC;YACD,WAAW,EAAE,sBAAsB;YACnC,YAAY,EAAE,2BAA2B;YACzC,KAAK,EAAE,EAAE;YACT,aAAa,EAAE,CAAC;YAChB,UAAU,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,EAAE,sBAAsB,EAAE;SACzE,CAAC;QAEF,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QACnD,oCAAoC;QACpC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;QACzC,2BAA2B;QAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HavenChannelExport, HavenServerExport } from "./types.js";
|
|
2
|
+
interface ArchiveMetadata {
|
|
3
|
+
exportedBy: {
|
|
4
|
+
user_id: string;
|
|
5
|
+
username: string;
|
|
6
|
+
identity_key: string;
|
|
7
|
+
};
|
|
8
|
+
scope?: "server" | "channel" | "dm";
|
|
9
|
+
serverId?: string;
|
|
10
|
+
channelId?: string;
|
|
11
|
+
instanceUrl: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds a .haven archive (ZIP) containing channel exports,
|
|
15
|
+
* server metadata, attachments, and an optional audit log.
|
|
16
|
+
*/
|
|
17
|
+
export declare class HavenArchiveBuilder {
|
|
18
|
+
private metadata;
|
|
19
|
+
private channels;
|
|
20
|
+
private attachments;
|
|
21
|
+
private serverMeta;
|
|
22
|
+
private auditLog;
|
|
23
|
+
private messageCount;
|
|
24
|
+
private earliestDate;
|
|
25
|
+
private latestDate;
|
|
26
|
+
constructor(metadata: ArchiveMetadata);
|
|
27
|
+
addChannel(channelExport: HavenChannelExport): void;
|
|
28
|
+
addAttachment(id: string, data: Uint8Array): void;
|
|
29
|
+
addServerMeta(serverExport: HavenServerExport): void;
|
|
30
|
+
addAuditLog(entries: any[]): void;
|
|
31
|
+
/**
|
|
32
|
+
* Build the .haven archive as a ZIP Uint8Array.
|
|
33
|
+
* If signingKey is provided, the manifest is signed with Ed25519.
|
|
34
|
+
*/
|
|
35
|
+
build(signingKey?: Uint8Array): Promise<Uint8Array>;
|
|
36
|
+
private updateDateRange;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { zipSync } from "fflate";
|
|
2
|
+
import { signManifest, computeFileHash } from "./signing.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a .haven archive (ZIP) containing channel exports,
|
|
5
|
+
* server metadata, attachments, and an optional audit log.
|
|
6
|
+
*/
|
|
7
|
+
export class HavenArchiveBuilder {
|
|
8
|
+
metadata;
|
|
9
|
+
channels = new Map();
|
|
10
|
+
attachments = new Map();
|
|
11
|
+
serverMeta = null;
|
|
12
|
+
auditLog = null;
|
|
13
|
+
messageCount = 0;
|
|
14
|
+
earliestDate = null;
|
|
15
|
+
latestDate = null;
|
|
16
|
+
constructor(metadata) {
|
|
17
|
+
this.metadata = metadata;
|
|
18
|
+
}
|
|
19
|
+
addChannel(channelExport) {
|
|
20
|
+
const name = channelExport.channel.name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
21
|
+
const dir = channelExport.channel.type === "dm" || channelExport.channel.type === "group_dm"
|
|
22
|
+
? "dms"
|
|
23
|
+
: "channels";
|
|
24
|
+
const key = `${dir}/${name}.json`;
|
|
25
|
+
const data = new TextEncoder().encode(JSON.stringify(channelExport, null, 2));
|
|
26
|
+
this.channels.set(key, data);
|
|
27
|
+
this.messageCount += channelExport.message_count;
|
|
28
|
+
this.updateDateRange(channelExport.date_range.from, channelExport.date_range.to);
|
|
29
|
+
}
|
|
30
|
+
addAttachment(id, data) {
|
|
31
|
+
this.attachments.set(`attachments/${id}.bin`, data);
|
|
32
|
+
}
|
|
33
|
+
addServerMeta(serverExport) {
|
|
34
|
+
this.serverMeta = new TextEncoder().encode(JSON.stringify(serverExport, null, 2));
|
|
35
|
+
}
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
addAuditLog(entries) {
|
|
38
|
+
this.auditLog = new TextEncoder().encode(JSON.stringify(entries, null, 2));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the .haven archive as a ZIP Uint8Array.
|
|
42
|
+
* If signingKey is provided, the manifest is signed with Ed25519.
|
|
43
|
+
*/
|
|
44
|
+
async build(signingKey) {
|
|
45
|
+
const files = {};
|
|
46
|
+
const fileHashes = {};
|
|
47
|
+
// Collect all files
|
|
48
|
+
for (const [path, data] of this.channels) {
|
|
49
|
+
files[path] = data;
|
|
50
|
+
}
|
|
51
|
+
for (const [path, data] of this.attachments) {
|
|
52
|
+
files[path] = data;
|
|
53
|
+
}
|
|
54
|
+
if (this.serverMeta) {
|
|
55
|
+
files["server.json"] = this.serverMeta;
|
|
56
|
+
}
|
|
57
|
+
if (this.auditLog) {
|
|
58
|
+
files["audit-log.json"] = this.auditLog;
|
|
59
|
+
}
|
|
60
|
+
// Compute hashes for all files
|
|
61
|
+
for (const [path, data] of Object.entries(files)) {
|
|
62
|
+
fileHashes[path] = {
|
|
63
|
+
sha256: await computeFileHash(data),
|
|
64
|
+
size: data.byteLength,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Build manifest
|
|
68
|
+
const manifest = {
|
|
69
|
+
version: 1,
|
|
70
|
+
format: "haven-export",
|
|
71
|
+
exported_by: this.metadata.exportedBy,
|
|
72
|
+
exported_at: new Date().toISOString(),
|
|
73
|
+
instance_url: this.metadata.instanceUrl,
|
|
74
|
+
files: fileHashes,
|
|
75
|
+
message_count: this.messageCount,
|
|
76
|
+
date_range: {
|
|
77
|
+
from: this.earliestDate ?? new Date().toISOString(),
|
|
78
|
+
to: this.latestDate ?? new Date().toISOString(),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
if (this.metadata.serverId)
|
|
82
|
+
manifest.server_id = this.metadata.serverId;
|
|
83
|
+
if (this.metadata.channelId)
|
|
84
|
+
manifest.channel_id = this.metadata.channelId;
|
|
85
|
+
if (this.metadata.scope)
|
|
86
|
+
manifest.scope = this.metadata.scope;
|
|
87
|
+
// Sign if key provided
|
|
88
|
+
if (signingKey) {
|
|
89
|
+
manifest.user_signature = signManifest(manifest, signingKey);
|
|
90
|
+
}
|
|
91
|
+
// Add manifest to archive
|
|
92
|
+
files["manifest.json"] = new TextEncoder().encode(JSON.stringify(manifest, null, 2));
|
|
93
|
+
// Build ZIP
|
|
94
|
+
const zippable = {};
|
|
95
|
+
for (const [path, data] of Object.entries(files)) {
|
|
96
|
+
zippable[path] = data;
|
|
97
|
+
}
|
|
98
|
+
return zipSync(zippable);
|
|
99
|
+
}
|
|
100
|
+
updateDateRange(from, to) {
|
|
101
|
+
if (!this.earliestDate || from < this.earliestDate)
|
|
102
|
+
this.earliestDate = from;
|
|
103
|
+
if (!this.latestDate || to > this.latestDate)
|
|
104
|
+
this.latestDate = to;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/export/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { HavenManifest, HavenChannelExport, HavenExportMessage, HavenAttachmentRef, HavenServerExport, } from "./types.js";
|
|
2
|
+
export { signManifest, verifyManifest, computeFileHash } from "./signing.js";
|
|
3
|
+
export { HavenArchiveBuilder } from "./archive.js";
|
|
4
|
+
export { HavenArchiveReader } from "./reader.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HavenManifest, HavenChannelExport, HavenServerExport } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads and verifies a .haven archive (ZIP).
|
|
4
|
+
*/
|
|
5
|
+
export declare class HavenArchiveReader {
|
|
6
|
+
private files;
|
|
7
|
+
private manifest;
|
|
8
|
+
private constructor();
|
|
9
|
+
/**
|
|
10
|
+
* Parse a .haven archive from raw ZIP bytes.
|
|
11
|
+
*/
|
|
12
|
+
static fromBlob(data: Uint8Array): Promise<HavenArchiveReader>;
|
|
13
|
+
getManifest(): HavenManifest;
|
|
14
|
+
getChannelExport(channelName: string): HavenChannelExport | null;
|
|
15
|
+
/** Return all channel exports (channels/ and dms/ directories). */
|
|
16
|
+
getChannelExports(): HavenChannelExport[];
|
|
17
|
+
getServerMeta(): HavenServerExport | null;
|
|
18
|
+
getAttachment(fileRef: string): Uint8Array | null;
|
|
19
|
+
getAuditLog(): any[] | null;
|
|
20
|
+
/**
|
|
21
|
+
* Verify archive integrity: file hashes and optional Ed25519 signature.
|
|
22
|
+
*/
|
|
23
|
+
verify(): Promise<{
|
|
24
|
+
valid: boolean;
|
|
25
|
+
issues: string[];
|
|
26
|
+
}>;
|
|
27
|
+
}
|