@immahq/aegis 0.0.1
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 +21 -0
- package/README.md +257 -0
- package/dist/constants.js +42 -0
- package/dist/crypto-manager.js +306 -0
- package/dist/e2ee.js +207 -0
- package/dist/group-manager.js +606 -0
- package/dist/identity-manager.js +112 -0
- package/dist/index.js +11 -0
- package/dist/logger.js +23 -0
- package/dist/prekey-manager.js +63 -0
- package/dist/ratchet-manager.js +130 -0
- package/dist/ratchet.js +168 -0
- package/dist/replay-protection.js +37 -0
- package/dist/session-manager.js +177 -0
- package/dist/session.js +131 -0
- package/dist/storage.js +54 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +27 -0
- package/package.json +54 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
|
|
2
|
+
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
|
|
3
|
+
import { Logger } from "./logger.js";
|
|
4
|
+
import { ERRORS } from "./constants.js";
|
|
5
|
+
import { SessionKeyExchange } from "./session.js";
|
|
6
|
+
import { validatePublicBundle } from "./utils.js";
|
|
7
|
+
export class SessionManager {
|
|
8
|
+
constructor(storage) {
|
|
9
|
+
Object.defineProperty(this, "storage", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: void 0
|
|
14
|
+
});
|
|
15
|
+
this.storage = storage;
|
|
16
|
+
}
|
|
17
|
+
async createSession(identity, peerBundle) {
|
|
18
|
+
try {
|
|
19
|
+
Logger.log("Session", "Creating new session as initiator");
|
|
20
|
+
validatePublicBundle(peerBundle);
|
|
21
|
+
const isValid = ml_dsa65.verify(peerBundle.preKey.signature, peerBundle.preKey.key, peerBundle.dsaPublicKey);
|
|
22
|
+
if (!isValid) {
|
|
23
|
+
throw new Error(ERRORS.INVALID_PREKEY_SIGNATURE);
|
|
24
|
+
}
|
|
25
|
+
const { sessionId, rootKey, sendingChainKey, receivingChainKey, ciphertext, confirmationMac, } = SessionKeyExchange.createInitiatorSession(identity, peerBundle);
|
|
26
|
+
const ratchetKeyPair = ml_kem768.keygen();
|
|
27
|
+
const session = {
|
|
28
|
+
sessionId,
|
|
29
|
+
peerUserId: peerBundle.userId,
|
|
30
|
+
peerDsaPublicKey: peerBundle.dsaPublicKey,
|
|
31
|
+
rootKey,
|
|
32
|
+
currentRatchetKeyPair: ratchetKeyPair,
|
|
33
|
+
peerRatchetPublicKey: null,
|
|
34
|
+
sendingChain: {
|
|
35
|
+
chainKey: sendingChainKey,
|
|
36
|
+
messageNumber: 0,
|
|
37
|
+
},
|
|
38
|
+
receivingChain: {
|
|
39
|
+
chainKey: receivingChainKey,
|
|
40
|
+
messageNumber: 0,
|
|
41
|
+
},
|
|
42
|
+
previousSendingChainLength: 0,
|
|
43
|
+
skippedMessageKeys: new Map(),
|
|
44
|
+
highestReceivedMessageNumber: -1,
|
|
45
|
+
maxSkippedMessages: 100,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
lastUsed: Date.now(),
|
|
48
|
+
isInitiator: true,
|
|
49
|
+
ratchetCount: 0,
|
|
50
|
+
state: "CREATED",
|
|
51
|
+
confirmed: false,
|
|
52
|
+
confirmationMac,
|
|
53
|
+
// Simple replay protection
|
|
54
|
+
receivedMessageIds: new Set(),
|
|
55
|
+
replayWindowSize: 100,
|
|
56
|
+
lastProcessedTimestamp: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
await this.storage.saveSession(sessionId, session);
|
|
59
|
+
Logger.log("Session", "Session created successfully as initiator", {
|
|
60
|
+
sessionId: sessionId.substring(0, 16) + "...",
|
|
61
|
+
});
|
|
62
|
+
return { sessionId, ciphertext, confirmationMac };
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
Logger.error("Session", "Failed to create session", error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async createResponderSession(identity, peerBundle, ciphertext, initiatorConfirmationMac) {
|
|
70
|
+
try {
|
|
71
|
+
Logger.log("Session", "Creating session as responder");
|
|
72
|
+
validatePublicBundle(peerBundle);
|
|
73
|
+
const isValidSignature = ml_dsa65.verify(peerBundle.preKey.signature, peerBundle.preKey.key, peerBundle.dsaPublicKey);
|
|
74
|
+
if (!isValidSignature) {
|
|
75
|
+
throw new Error(ERRORS.INVALID_PREKEY_SIGNATURE);
|
|
76
|
+
}
|
|
77
|
+
const { sessionId, rootKey, sendingChainKey, receivingChainKey, confirmationMac, isValid, } = SessionKeyExchange.createResponderSession(identity, peerBundle, ciphertext, initiatorConfirmationMac);
|
|
78
|
+
if (!isValid && initiatorConfirmationMac) {
|
|
79
|
+
throw new Error(ERRORS.KEY_CONFIRMATION_FAILED);
|
|
80
|
+
}
|
|
81
|
+
const ratchetKeyPair = ml_kem768.keygen();
|
|
82
|
+
const session = {
|
|
83
|
+
sessionId,
|
|
84
|
+
peerUserId: peerBundle.userId,
|
|
85
|
+
peerDsaPublicKey: peerBundle.dsaPublicKey,
|
|
86
|
+
rootKey,
|
|
87
|
+
currentRatchetKeyPair: ratchetKeyPair,
|
|
88
|
+
peerRatchetPublicKey: null,
|
|
89
|
+
sendingChain: {
|
|
90
|
+
chainKey: sendingChainKey,
|
|
91
|
+
messageNumber: 0,
|
|
92
|
+
},
|
|
93
|
+
receivingChain: {
|
|
94
|
+
chainKey: receivingChainKey,
|
|
95
|
+
messageNumber: 0,
|
|
96
|
+
},
|
|
97
|
+
previousSendingChainLength: 0,
|
|
98
|
+
skippedMessageKeys: new Map(),
|
|
99
|
+
highestReceivedMessageNumber: -1,
|
|
100
|
+
maxSkippedMessages: 100,
|
|
101
|
+
createdAt: Date.now(),
|
|
102
|
+
lastUsed: Date.now(),
|
|
103
|
+
isInitiator: false,
|
|
104
|
+
ratchetCount: 0,
|
|
105
|
+
state: "CREATED",
|
|
106
|
+
confirmed: isValid && initiatorConfirmationMac !== undefined,
|
|
107
|
+
confirmationMac,
|
|
108
|
+
// Simple replay protection
|
|
109
|
+
receivedMessageIds: new Set(),
|
|
110
|
+
replayWindowSize: 100,
|
|
111
|
+
lastProcessedTimestamp: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
await this.storage.saveSession(sessionId, session);
|
|
114
|
+
Logger.log("Session", "Session created as responder", {
|
|
115
|
+
sessionId: sessionId.substring(0, 16) + "...",
|
|
116
|
+
keyConfirmed: session.confirmed,
|
|
117
|
+
});
|
|
118
|
+
return { sessionId, confirmationMac, isValid };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
Logger.error("Session", "Failed to create responder session", error);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async confirmSession(sessionId, responderConfirmationMac) {
|
|
126
|
+
try {
|
|
127
|
+
Logger.log("Session", "Confirming session keys");
|
|
128
|
+
const session = await this.storage.getSession(sessionId);
|
|
129
|
+
if (!session)
|
|
130
|
+
throw new Error(ERRORS.SESSION_NOT_FOUND);
|
|
131
|
+
if (!session.sendingChain) {
|
|
132
|
+
throw new Error("No sending chain available");
|
|
133
|
+
}
|
|
134
|
+
const isValid = SessionKeyExchange.verifyKeyConfirmation(sessionId, session.rootKey, session.receivingChain.chainKey, responderConfirmationMac);
|
|
135
|
+
if (isValid) {
|
|
136
|
+
session.confirmed = true;
|
|
137
|
+
session.state = "KEY_CONFIRMED";
|
|
138
|
+
await this.storage.saveSession(sessionId, session);
|
|
139
|
+
Logger.log("Session", "Session keys confirmed successfully");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
session.state = "ERROR";
|
|
143
|
+
await this.storage.saveSession(sessionId, session);
|
|
144
|
+
Logger.warn("Session", "Key confirmation failed");
|
|
145
|
+
}
|
|
146
|
+
return isValid;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
Logger.error("Session", "Failed to confirm session", error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async getSessions() {
|
|
154
|
+
const sessionIds = await this.storage.listSessions();
|
|
155
|
+
const sessions = [];
|
|
156
|
+
for (const sessionId of sessionIds) {
|
|
157
|
+
const session = await this.storage.getSession(sessionId);
|
|
158
|
+
if (session) {
|
|
159
|
+
sessions.push(session);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return sessions;
|
|
163
|
+
}
|
|
164
|
+
async cleanupOldSessions(maxAge) {
|
|
165
|
+
const sessions = await this.getSessions();
|
|
166
|
+
const cutoff = Date.now() - (maxAge || 30 * 24 * 60 * 60 * 1000); // Default 30 days
|
|
167
|
+
for (const session of sessions) {
|
|
168
|
+
if (session.lastUsed < cutoff) {
|
|
169
|
+
await this.storage.deleteSession(session.sessionId);
|
|
170
|
+
Logger.log("Cleanup", "Removed old session", {
|
|
171
|
+
sessionId: session.sessionId.substring(0, 16) + "...",
|
|
172
|
+
age: Math.round((Date.now() - session.lastUsed) / (1000 * 60 * 60 * 24)) + " days",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
|
|
2
|
+
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
|
|
3
|
+
import { blake3 } from "@noble/hashes/blake3.js";
|
|
4
|
+
import { bytesToHex, concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
5
|
+
import { KemRatchet } from "./ratchet";
|
|
6
|
+
export class SessionKeyExchange {
|
|
7
|
+
// Helper: Sort two Uint8Arrays lexicographically and return in consistent order
|
|
8
|
+
static getSortedKeys(key1, key2) {
|
|
9
|
+
const str1 = bytesToHex(key1);
|
|
10
|
+
const str2 = bytesToHex(key2);
|
|
11
|
+
return str1 < str2 ? [key1, key2] : [key2, key1];
|
|
12
|
+
}
|
|
13
|
+
// Helper: Create deterministic session ID
|
|
14
|
+
static createSessionId(key1, key2, ciphertext) {
|
|
15
|
+
const [sortedKey1, sortedKey2] = this.getSortedKeys(key1, key2);
|
|
16
|
+
return bytesToHex(blake3(concatBytes(sortedKey1, sortedKey2, ciphertext), { dkLen: 32 }));
|
|
17
|
+
}
|
|
18
|
+
// For initiator (Alice): creates session with Bob's bundle
|
|
19
|
+
static createInitiatorSession(localIdentity, peerBundle) {
|
|
20
|
+
// Validate inputs
|
|
21
|
+
if (!(localIdentity.kemKeyPair.publicKey instanceof Uint8Array)) {
|
|
22
|
+
throw new Error("localIdentity.kemKeyPair.publicKey is not Uint8Array");
|
|
23
|
+
}
|
|
24
|
+
if (!(peerBundle.preKey.key instanceof Uint8Array)) {
|
|
25
|
+
throw new Error("peerBundle.preKey.key is not Uint8Array");
|
|
26
|
+
}
|
|
27
|
+
if (!(peerBundle.kemPublicKey instanceof Uint8Array)) {
|
|
28
|
+
throw new Error("peerBundle.kemPublicKey is not Uint8Array");
|
|
29
|
+
}
|
|
30
|
+
if (!(peerBundle.preKey.signature instanceof Uint8Array)) {
|
|
31
|
+
throw new Error("peerBundle.preKey.signature is not Uint8Array");
|
|
32
|
+
}
|
|
33
|
+
// Verify the prekey signature
|
|
34
|
+
const isValidPreKeySignature = SessionKeyExchange.verifyPreKeySignature(peerBundle.preKey.key, peerBundle.preKey.signature, peerBundle.dsaPublicKey);
|
|
35
|
+
if (!isValidPreKeySignature) {
|
|
36
|
+
throw new Error("Invalid prekey signature");
|
|
37
|
+
}
|
|
38
|
+
// Perform KEM with peer's prekey
|
|
39
|
+
const prekeyResult = ml_kem768.encapsulate(peerBundle.preKey.key);
|
|
40
|
+
if (!prekeyResult || typeof prekeyResult !== "object") {
|
|
41
|
+
throw new Error("ml_kem768.encapsulate returned invalid result");
|
|
42
|
+
}
|
|
43
|
+
const prekeySecret = prekeyResult.sharedSecret;
|
|
44
|
+
const ciphertext = prekeyResult.cipherText;
|
|
45
|
+
if (!(prekeySecret instanceof Uint8Array)) {
|
|
46
|
+
throw new Error(`prekeySecret is not Uint8Array, got ${typeof prekeySecret}`);
|
|
47
|
+
}
|
|
48
|
+
if (!(ciphertext instanceof Uint8Array)) {
|
|
49
|
+
throw new Error(`ciphertext is not Uint8Array, got ${typeof ciphertext}`);
|
|
50
|
+
}
|
|
51
|
+
// Create session ID (both parties will compute same)
|
|
52
|
+
const sessionId = this.createSessionId(localIdentity.kemKeyPair.publicKey, peerBundle.kemPublicKey, ciphertext);
|
|
53
|
+
// Derive keys - BOTH PARTIES MUST USE EXACT SAME INPUTS
|
|
54
|
+
const [sortedKey1, sortedKey2] = this.getSortedKeys(localIdentity.kemKeyPair.publicKey, peerBundle.kemPublicKey);
|
|
55
|
+
const combined = concatBytes(prekeySecret, ciphertext, sortedKey1, sortedKey2);
|
|
56
|
+
const rootKey = blake3(combined, { dkLen: 32 });
|
|
57
|
+
// Initial chain keys: Initiator sends on A, receives on B
|
|
58
|
+
const sendingChainKey = blake3(concatBytes(rootKey, utf8ToBytes("chain_a")), {
|
|
59
|
+
dkLen: 32,
|
|
60
|
+
});
|
|
61
|
+
const receivingChainKey = blake3(concatBytes(rootKey, utf8ToBytes("chain_b")), {
|
|
62
|
+
dkLen: 32,
|
|
63
|
+
});
|
|
64
|
+
// Generate confirmation MAC
|
|
65
|
+
const confirmationMac = KemRatchet.generateConfirmationMac(sessionId, rootKey, sendingChainKey, false);
|
|
66
|
+
return {
|
|
67
|
+
sessionId,
|
|
68
|
+
rootKey,
|
|
69
|
+
sendingChainKey,
|
|
70
|
+
receivingChainKey,
|
|
71
|
+
ciphertext,
|
|
72
|
+
confirmationMac,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
static createResponderSession(localIdentity, peerBundle, ciphertext, initiatorConfirmationMac) {
|
|
76
|
+
// Validate inputs
|
|
77
|
+
if (!(ciphertext instanceof Uint8Array)) {
|
|
78
|
+
throw new Error("ciphertext is not Uint8Array");
|
|
79
|
+
}
|
|
80
|
+
if (!localIdentity.preKeySecret ||
|
|
81
|
+
!(localIdentity.preKeySecret instanceof Uint8Array)) {
|
|
82
|
+
throw new Error("No valid prekey secret available");
|
|
83
|
+
}
|
|
84
|
+
if (!(localIdentity.kemKeyPair.publicKey instanceof Uint8Array)) {
|
|
85
|
+
throw new Error("localIdentity.kemKeyPair.publicKey is not Uint8Array");
|
|
86
|
+
}
|
|
87
|
+
if (!(peerBundle.kemPublicKey instanceof Uint8Array)) {
|
|
88
|
+
throw new Error("peerBundle.kemPublicKey is not Uint8Array");
|
|
89
|
+
}
|
|
90
|
+
// Decapsulate prekey ciphertext
|
|
91
|
+
const prekeySecret = ml_kem768.decapsulate(ciphertext, localIdentity.preKeySecret);
|
|
92
|
+
if (!(prekeySecret instanceof Uint8Array)) {
|
|
93
|
+
throw new Error(`ml_kem768.decapsulate returned non-Uint8Array: ${typeof prekeySecret}`);
|
|
94
|
+
}
|
|
95
|
+
const sessionId = this.createSessionId(localIdentity.kemKeyPair.publicKey, peerBundle.kemPublicKey, ciphertext);
|
|
96
|
+
// Derive keys - MUST USE EXACT SAME INPUTS AS INITIATOR
|
|
97
|
+
const [sortedKey1, sortedKey2] = this.getSortedKeys(localIdentity.kemKeyPair.publicKey, peerBundle.kemPublicKey);
|
|
98
|
+
const combined = concatBytes(prekeySecret, ciphertext, sortedKey1, sortedKey2);
|
|
99
|
+
const rootKey = blake3(combined, { dkLen: 32 });
|
|
100
|
+
// Initial chain keys: Responder sends on B, receives on A
|
|
101
|
+
const sendingChainKey = blake3(concatBytes(rootKey, utf8ToBytes("chain_b")), {
|
|
102
|
+
dkLen: 32,
|
|
103
|
+
});
|
|
104
|
+
const receivingChainKey = blake3(concatBytes(rootKey, utf8ToBytes("chain_a")), {
|
|
105
|
+
dkLen: 32,
|
|
106
|
+
});
|
|
107
|
+
// Generate response confirmation MAC
|
|
108
|
+
const confirmationMac = KemRatchet.generateConfirmationMac(sessionId, rootKey, sendingChainKey, true);
|
|
109
|
+
// Verify initiator's MAC if provided
|
|
110
|
+
let isValid = true;
|
|
111
|
+
if (initiatorConfirmationMac) {
|
|
112
|
+
isValid = KemRatchet.verifyConfirmationMac(sessionId, rootKey, receivingChainKey, initiatorConfirmationMac, false);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
sessionId,
|
|
116
|
+
rootKey,
|
|
117
|
+
sendingChainKey,
|
|
118
|
+
receivingChainKey,
|
|
119
|
+
confirmationMac,
|
|
120
|
+
isValid,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Verify key confirmation (for initiator to verify responder's MAC)
|
|
124
|
+
static verifyKeyConfirmation(sessionId, rootKey, chainKey, responseMac) {
|
|
125
|
+
return KemRatchet.verifyConfirmationMac(sessionId, rootKey, chainKey, responseMac, true);
|
|
126
|
+
}
|
|
127
|
+
// Verify prekey signature
|
|
128
|
+
static verifyPreKeySignature(preKey, signature, dsaPublicKey) {
|
|
129
|
+
return ml_dsa65.verify(signature, preKey, dsaPublicKey);
|
|
130
|
+
}
|
|
131
|
+
}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class MemoryStorage {
|
|
2
|
+
constructor() {
|
|
3
|
+
Object.defineProperty(this, "identity", {
|
|
4
|
+
enumerable: true,
|
|
5
|
+
configurable: true,
|
|
6
|
+
writable: true,
|
|
7
|
+
value: null
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(this, "sessions", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: new Map()
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async saveIdentity(identity) {
|
|
17
|
+
this.identity = identity;
|
|
18
|
+
}
|
|
19
|
+
async getIdentity() {
|
|
20
|
+
return this.identity;
|
|
21
|
+
}
|
|
22
|
+
async deleteIdentity() {
|
|
23
|
+
this.identity = null;
|
|
24
|
+
}
|
|
25
|
+
async saveSession(sessionId, session) {
|
|
26
|
+
// Deep clone to avoid reference issues
|
|
27
|
+
const sessionCopy = {
|
|
28
|
+
...session,
|
|
29
|
+
skippedMessageKeys: new Map(session.skippedMessageKeys),
|
|
30
|
+
receivedMessageIds: new Set(session.receivedMessageIds),
|
|
31
|
+
};
|
|
32
|
+
this.sessions.set(sessionId, sessionCopy);
|
|
33
|
+
}
|
|
34
|
+
async getSession(sessionId) {
|
|
35
|
+
const session = this.sessions.get(sessionId);
|
|
36
|
+
if (!session)
|
|
37
|
+
return null;
|
|
38
|
+
// Return a deep clone
|
|
39
|
+
return {
|
|
40
|
+
...session,
|
|
41
|
+
skippedMessageKeys: new Map(session.skippedMessageKeys),
|
|
42
|
+
receivedMessageIds: new Set(session.receivedMessageIds),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async deleteSession(sessionId) {
|
|
46
|
+
this.sessions.delete(sessionId);
|
|
47
|
+
}
|
|
48
|
+
async listSessions() {
|
|
49
|
+
return Array.from(this.sessions.keys());
|
|
50
|
+
}
|
|
51
|
+
async deleteAllSessions() {
|
|
52
|
+
this.sessions.clear();
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { blake3 } from "@noble/hashes/blake3.js";
|
|
2
|
+
import { bytesToHex, concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
3
|
+
export function deriveMessageKey(chainKey) {
|
|
4
|
+
return blake3(concatBytes(chainKey, utf8ToBytes("msg_key")), { dkLen: 32 });
|
|
5
|
+
}
|
|
6
|
+
export function deriveNewChainKey(chainKey) {
|
|
7
|
+
return blake3(concatBytes(chainKey, utf8ToBytes("chain_key")), { dkLen: 32 });
|
|
8
|
+
}
|
|
9
|
+
export function generateSessionId(localKemPublicKey, peerKemPublicKey, preKey) {
|
|
10
|
+
return bytesToHex(blake3(concatBytes(localKemPublicKey, peerKemPublicKey, preKey), {
|
|
11
|
+
dkLen: 32,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export function validatePublicBundle(bundle) {
|
|
15
|
+
if (!bundle || !bundle.preKey || !bundle.preKey.key) {
|
|
16
|
+
throw new Error("Invalid peer bundle");
|
|
17
|
+
}
|
|
18
|
+
if (!bundle.userId || typeof bundle.userId !== "string") {
|
|
19
|
+
throw new Error("Invalid userId in bundle");
|
|
20
|
+
}
|
|
21
|
+
if (!bundle.kemPublicKey || !bundle.dsaPublicKey) {
|
|
22
|
+
throw new Error("Missing public keys in bundle");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function serializeHeader(header) {
|
|
26
|
+
return utf8ToBytes(JSON.stringify(header));
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@immahq/aegis",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lightweight, storage-agnostic library for client-side End-to-End (E2E) encryption",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scope": "immahq",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "npm run clean && tsc --build",
|
|
17
|
+
"clean": "rimraf ./dist",
|
|
18
|
+
"demo": "tsx examples/demo.ts",
|
|
19
|
+
"demo:group": "tsx examples/group-demo.ts",
|
|
20
|
+
"test": "vitest run"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+ssh://git@github.com/imma-hq/aegis.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"react native",
|
|
29
|
+
"native",
|
|
30
|
+
"select",
|
|
31
|
+
"dropdown",
|
|
32
|
+
"option"
|
|
33
|
+
],
|
|
34
|
+
"author": "Aegis",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/imma-hq/aegis/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/imma-hq/aegis#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@noble/ciphers": "^2.0.1",
|
|
42
|
+
"@noble/curves": "^2.0.1",
|
|
43
|
+
"@noble/hashes": "^2.0.1",
|
|
44
|
+
"@noble/post-quantum": "^0.5.2",
|
|
45
|
+
"buffer": "^6.0.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.0.3",
|
|
49
|
+
"rimraf": "^6.1.2",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vitest": "^4.0.16"
|
|
53
|
+
}
|
|
54
|
+
}
|