@de-otio/chaoskb-client 0.3.6 → 0.3.8
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/dist/cli/bootstrap.d.ts +11 -3
- package/dist/cli/bootstrap.d.ts.map +1 -1
- package/dist/cli/bootstrap.js +181 -126
- package/dist/cli/bootstrap.js.map +1 -1
- package/dist/cli/commands/config.d.ts +30 -4
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +289 -134
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/devices.d.ts.map +1 -1
- package/dist/cli/commands/devices.js +58 -33
- package/dist/cli/commands/devices.js.map +1 -1
- package/dist/cli/commands/export.d.ts.map +1 -1
- package/dist/cli/commands/export.js +6 -9
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/import.js +1 -1
- package/dist/cli/commands/import.js.map +1 -1
- package/dist/cli/commands/projects.d.ts.map +1 -1
- package/dist/cli/commands/projects.js +33 -10
- package/dist/cli/commands/projects.js.map +1 -1
- package/dist/cli/commands/rotate-key.d.ts +3 -3
- package/dist/cli/commands/rotate-key.d.ts.map +1 -1
- package/dist/cli/commands/rotate-key.js +88 -35
- package/dist/cli/commands/rotate-key.js.map +1 -1
- package/dist/cli/commands/setup-sync.d.ts.map +1 -1
- package/dist/cli/commands/setup-sync.js +22 -4
- package/dist/cli/commands/setup-sync.js.map +1 -1
- package/dist/cli/index.js +20 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-server.d.ts +6 -0
- package/dist/cli/mcp-server.d.ts.map +1 -1
- package/dist/cli/mcp-server.js +93 -42
- package/dist/cli/mcp-server.js.map +1 -1
- package/dist/crypto/aad.d.ts +2 -5
- package/dist/crypto/aad.d.ts.map +1 -1
- package/dist/crypto/aad.js +2 -8
- package/dist/crypto/aad.js.map +1 -1
- package/dist/crypto/aead.d.ts +8 -16
- package/dist/crypto/aead.d.ts.map +1 -1
- package/dist/crypto/aead.js +10 -36
- package/dist/crypto/aead.js.map +1 -1
- package/dist/crypto/blob-id.d.ts +2 -3
- package/dist/crypto/blob-id.d.ts.map +1 -1
- package/dist/crypto/blob-id.js +2 -30
- package/dist/crypto/blob-id.js.map +1 -1
- package/dist/crypto/canonical-json.d.ts +5 -3
- package/dist/crypto/canonical-json.d.ts.map +1 -1
- package/dist/crypto/canonical-json.js +5 -85
- package/dist/crypto/canonical-json.js.map +1 -1
- package/dist/crypto/commitment.d.ts +3 -9
- package/dist/crypto/commitment.d.ts.map +1 -1
- package/dist/crypto/commitment.js +3 -27
- package/dist/crypto/commitment.js.map +1 -1
- package/dist/crypto/encryption-service.d.ts +3 -0
- package/dist/crypto/encryption-service.d.ts.map +1 -1
- package/dist/crypto/encryption-service.js +10 -6
- package/dist/crypto/encryption-service.js.map +1 -1
- package/dist/crypto/envelope-cbor.d.ts +4 -34
- package/dist/crypto/envelope-cbor.d.ts.map +1 -1
- package/dist/crypto/envelope-cbor.js +4 -121
- package/dist/crypto/envelope-cbor.js.map +1 -1
- package/dist/crypto/envelope.d.ts +1 -31
- package/dist/crypto/envelope.d.ts.map +1 -1
- package/dist/crypto/envelope.js +31 -137
- package/dist/crypto/envelope.js.map +1 -1
- package/dist/crypto/hkdf.d.ts +7 -11
- package/dist/crypto/hkdf.d.ts.map +1 -1
- package/dist/crypto/hkdf.js +9 -18
- package/dist/crypto/hkdf.js.map +1 -1
- package/dist/crypto/index.d.ts +9 -4
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js +9 -4
- package/dist/crypto/index.js.map +1 -1
- package/dist/crypto/ssh-keys.d.ts +17 -10
- package/dist/crypto/ssh-keys.d.ts.map +1 -1
- package/dist/crypto/ssh-keys.js +28 -108
- package/dist/crypto/ssh-keys.js.map +1 -1
- package/dist/crypto/types.d.ts +18 -88
- package/dist/crypto/types.d.ts.map +1 -1
- package/dist/crypto/types.js +3 -0
- package/dist/crypto/types.js.map +1 -1
- package/dist/pipeline/content-pipeline.d.ts.map +1 -1
- package/dist/pipeline/content-pipeline.js +21 -5
- package/dist/pipeline/content-pipeline.js.map +1 -1
- package/dist/pipeline/extract.d.ts +8 -0
- package/dist/pipeline/extract.d.ts.map +1 -1
- package/dist/pipeline/extract.js +15 -4
- package/dist/pipeline/extract.js.map +1 -1
- package/dist/pipeline/fetch-browser.d.ts +29 -0
- package/dist/pipeline/fetch-browser.d.ts.map +1 -0
- package/dist/pipeline/fetch-browser.js +98 -0
- package/dist/pipeline/fetch-browser.js.map +1 -0
- package/dist/pipeline/safety.d.ts +43 -6
- package/dist/pipeline/safety.d.ts.map +1 -1
- package/dist/pipeline/safety.js +52 -15
- package/dist/pipeline/safety.js.map +1 -1
- package/dist/pipeline/validate.js +35 -23
- package/dist/pipeline/validate.js.map +1 -1
- package/package.json +4 -1
- package/dist/crypto/argon2.d.ts +0 -11
- package/dist/crypto/argon2.d.ts.map +0 -1
- package/dist/crypto/argon2.js +0 -33
- package/dist/crypto/argon2.js.map +0 -1
- package/dist/crypto/invite.d.ts +0 -31
- package/dist/crypto/invite.d.ts.map +0 -1
- package/dist/crypto/invite.js +0 -139
- package/dist/crypto/invite.js.map +0 -1
- package/dist/crypto/keyring.d.ts +0 -37
- package/dist/crypto/keyring.d.ts.map +0 -1
- package/dist/crypto/keyring.js +0 -219
- package/dist/crypto/keyring.js.map +0 -1
- package/dist/crypto/known-keys.d.ts +0 -34
- package/dist/crypto/known-keys.d.ts.map +0 -1
- package/dist/crypto/known-keys.js +0 -114
- package/dist/crypto/known-keys.js.map +0 -1
- package/dist/crypto/project-keys.d.ts +0 -26
- package/dist/crypto/project-keys.d.ts.map +0 -1
- package/dist/crypto/project-keys.js +0 -69
- package/dist/crypto/project-keys.js.map +0 -1
- package/dist/crypto/secure-buffer.d.ts +0 -31
- package/dist/crypto/secure-buffer.d.ts.map +0 -1
- package/dist/crypto/secure-buffer.js +0 -61
- package/dist/crypto/secure-buffer.js.map +0 -1
- package/dist/crypto/tiers/enhanced.d.ts +0 -25
- package/dist/crypto/tiers/enhanced.d.ts.map +0 -1
- package/dist/crypto/tiers/enhanced.js +0 -56
- package/dist/crypto/tiers/enhanced.js.map +0 -1
- package/dist/crypto/tiers/maximum.d.ts +0 -19
- package/dist/crypto/tiers/maximum.d.ts.map +0 -1
- package/dist/crypto/tiers/maximum.js +0 -25
- package/dist/crypto/tiers/maximum.js.map +0 -1
- package/dist/crypto/tiers/standard.d.ts +0 -27
- package/dist/crypto/tiers/standard.d.ts.map +0 -1
- package/dist/crypto/tiers/standard.js +0 -155
- package/dist/crypto/tiers/standard.js.map +0 -1
package/dist/crypto/keyring.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import { SecureBuffer } from './secure-buffer.js';
|
|
4
|
-
const execFileAsync = promisify(execFile);
|
|
5
|
-
/**
|
|
6
|
-
* OS keyring integration via shell commands.
|
|
7
|
-
* Uses execFile (not exec) to prevent shell injection.
|
|
8
|
-
*
|
|
9
|
-
* macOS: security add-generic-password / find-generic-password / delete-generic-password
|
|
10
|
-
* Linux: secret-tool store / lookup / clear
|
|
11
|
-
* Windows: cmdkey /add: / /list: / /delete:
|
|
12
|
-
*/
|
|
13
|
-
export class KeyringService {
|
|
14
|
-
platform;
|
|
15
|
-
constructor(platform) {
|
|
16
|
-
this.platform = platform ?? process.platform;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Store a secret in the OS keyring.
|
|
20
|
-
*/
|
|
21
|
-
async store(service, account, secret) {
|
|
22
|
-
const secretHex = secret.buffer.toString('hex');
|
|
23
|
-
try {
|
|
24
|
-
switch (this.platform) {
|
|
25
|
-
case 'darwin':
|
|
26
|
-
await this.storeMacOS(service, account, secretHex);
|
|
27
|
-
break;
|
|
28
|
-
case 'linux':
|
|
29
|
-
await this.storeLinux(service, account, secretHex);
|
|
30
|
-
break;
|
|
31
|
-
case 'win32':
|
|
32
|
-
await this.storeWindows(service, account, secretHex);
|
|
33
|
-
break;
|
|
34
|
-
default:
|
|
35
|
-
throw new Error(`Unsupported platform: ${this.platform}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
// We can't zero the hex string (JS strings are immutable),
|
|
40
|
-
// but we at least don't hold onto it.
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Retrieve a secret from the OS keyring.
|
|
45
|
-
* Returns null if not found.
|
|
46
|
-
*/
|
|
47
|
-
async retrieve(service, account) {
|
|
48
|
-
try {
|
|
49
|
-
let secretHex;
|
|
50
|
-
switch (this.platform) {
|
|
51
|
-
case 'darwin':
|
|
52
|
-
secretHex = await this.retrieveMacOS(service, account);
|
|
53
|
-
break;
|
|
54
|
-
case 'linux':
|
|
55
|
-
secretHex = await this.retrieveLinux(service, account);
|
|
56
|
-
break;
|
|
57
|
-
case 'win32':
|
|
58
|
-
secretHex = await this.retrieveWindows(service, account);
|
|
59
|
-
break;
|
|
60
|
-
default:
|
|
61
|
-
throw new Error(`Unsupported platform: ${this.platform}`);
|
|
62
|
-
}
|
|
63
|
-
const bytes = Buffer.from(secretHex.trim(), 'hex');
|
|
64
|
-
return SecureBuffer.from(bytes);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Delete a secret from the OS keyring.
|
|
72
|
-
* Returns true if deleted, false if not found.
|
|
73
|
-
*/
|
|
74
|
-
async delete(service, account) {
|
|
75
|
-
try {
|
|
76
|
-
switch (this.platform) {
|
|
77
|
-
case 'darwin':
|
|
78
|
-
await this.deleteMacOS(service, account);
|
|
79
|
-
break;
|
|
80
|
-
case 'linux':
|
|
81
|
-
await this.deleteLinux(service, account);
|
|
82
|
-
break;
|
|
83
|
-
case 'win32':
|
|
84
|
-
await this.deleteWindows(service, account);
|
|
85
|
-
break;
|
|
86
|
-
default:
|
|
87
|
-
throw new Error(`Unsupported platform: ${this.platform}`);
|
|
88
|
-
}
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// --- macOS ---
|
|
96
|
-
async storeMacOS(service, account, secret) {
|
|
97
|
-
// Delete existing entry first (update = delete + add)
|
|
98
|
-
try {
|
|
99
|
-
await execFileAsync('security', [
|
|
100
|
-
'delete-generic-password',
|
|
101
|
-
'-s', service,
|
|
102
|
-
'-a', account,
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// Ignore if not found
|
|
107
|
-
}
|
|
108
|
-
await execFileAsync('security', [
|
|
109
|
-
'add-generic-password',
|
|
110
|
-
'-s', service,
|
|
111
|
-
'-a', account,
|
|
112
|
-
'-w', secret,
|
|
113
|
-
'-U', // update if exists
|
|
114
|
-
]);
|
|
115
|
-
}
|
|
116
|
-
async retrieveMacOS(service, account) {
|
|
117
|
-
const { stdout } = await execFileAsync('security', [
|
|
118
|
-
'find-generic-password',
|
|
119
|
-
'-s', service,
|
|
120
|
-
'-a', account,
|
|
121
|
-
'-w', // output password only
|
|
122
|
-
]);
|
|
123
|
-
return stdout.trim();
|
|
124
|
-
}
|
|
125
|
-
async deleteMacOS(service, account) {
|
|
126
|
-
await execFileAsync('security', [
|
|
127
|
-
'delete-generic-password',
|
|
128
|
-
'-s', service,
|
|
129
|
-
'-a', account,
|
|
130
|
-
]);
|
|
131
|
-
}
|
|
132
|
-
// --- Linux ---
|
|
133
|
-
async storeLinux(service, account, secret) {
|
|
134
|
-
// secret-tool reads from stdin
|
|
135
|
-
const child = execFileAsync('secret-tool', [
|
|
136
|
-
'store',
|
|
137
|
-
'--label', `${service}/${account}`,
|
|
138
|
-
'service', service,
|
|
139
|
-
'account', account,
|
|
140
|
-
]);
|
|
141
|
-
// Write secret to stdin
|
|
142
|
-
if (child.child.stdin) {
|
|
143
|
-
child.child.stdin.write(secret);
|
|
144
|
-
child.child.stdin.end();
|
|
145
|
-
}
|
|
146
|
-
await child;
|
|
147
|
-
}
|
|
148
|
-
async retrieveLinux(service, account) {
|
|
149
|
-
const { stdout } = await execFileAsync('secret-tool', [
|
|
150
|
-
'lookup',
|
|
151
|
-
'service', service,
|
|
152
|
-
'account', account,
|
|
153
|
-
]);
|
|
154
|
-
return stdout;
|
|
155
|
-
}
|
|
156
|
-
async deleteLinux(service, account) {
|
|
157
|
-
await execFileAsync('secret-tool', [
|
|
158
|
-
'clear',
|
|
159
|
-
'service', service,
|
|
160
|
-
'account', account,
|
|
161
|
-
]);
|
|
162
|
-
}
|
|
163
|
-
// --- Windows ---
|
|
164
|
-
async storeWindows(service, account, secret) {
|
|
165
|
-
const target = `${service}/${account}`;
|
|
166
|
-
// Use PowerShell with .NET CredentialManager API (no external modules required)
|
|
167
|
-
const script = `
|
|
168
|
-
Add-Type -AssemblyName System.Runtime.InteropServices
|
|
169
|
-
$cred = New-Object System.Management.Automation.PSCredential('${account.replace(/'/g, "''")}', (ConvertTo-SecureString '${secret.replace(/'/g, "''")}' -AsPlainText -Force))
|
|
170
|
-
cmdkey /add:${target} /user:${account} /pass:${secret}
|
|
171
|
-
`.trim();
|
|
172
|
-
await execFileAsync('powershell', ['-NoProfile', '-Command', script]);
|
|
173
|
-
}
|
|
174
|
-
async retrieveWindows(service, account) {
|
|
175
|
-
const target = `${service}/${account}`;
|
|
176
|
-
// Use PowerShell with native .NET Credential API (no external modules needed)
|
|
177
|
-
const script = `
|
|
178
|
-
Add-Type @"
|
|
179
|
-
using System;
|
|
180
|
-
using System.Runtime.InteropServices;
|
|
181
|
-
public class CredentialHelper {
|
|
182
|
-
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
|
183
|
-
public static extern bool CredReadW(string target, int type, int flags, out IntPtr credential);
|
|
184
|
-
[DllImport("advapi32.dll")]
|
|
185
|
-
public static extern void CredFree(IntPtr credential);
|
|
186
|
-
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
187
|
-
public struct CREDENTIAL {
|
|
188
|
-
public int Flags; public int Type;
|
|
189
|
-
public string TargetName; public string Comment;
|
|
190
|
-
public long LastWritten; public int CredentialBlobSize;
|
|
191
|
-
public IntPtr CredentialBlob; public int Persist;
|
|
192
|
-
public int AttributeCount; public IntPtr Attributes;
|
|
193
|
-
public string TargetAlias; public string UserName;
|
|
194
|
-
}
|
|
195
|
-
public static string Read(string target) {
|
|
196
|
-
IntPtr ptr;
|
|
197
|
-
if (!CredReadW(target, 1, 0, out ptr)) return "";
|
|
198
|
-
var cred = (CREDENTIAL)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL));
|
|
199
|
-
var secret = Marshal.PtrToStringUni(cred.CredentialBlob, cred.CredentialBlobSize / 2);
|
|
200
|
-
CredFree(ptr);
|
|
201
|
-
return secret;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
"@
|
|
205
|
-
[CredentialHelper]::Read('${target.replace(/'/g, "''")}')
|
|
206
|
-
`.trim();
|
|
207
|
-
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', script]);
|
|
208
|
-
const result = stdout.trim();
|
|
209
|
-
if (!result) {
|
|
210
|
-
throw new Error(`Credential not found: ${target}`);
|
|
211
|
-
}
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
async deleteWindows(service, account) {
|
|
215
|
-
const target = `${service}/${account}`;
|
|
216
|
-
await execFileAsync('cmdkey', ['/delete:' + target]);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
//# sourceMappingURL=keyring.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"keyring.js","sourceRoot":"","sources":["../../crypto/keyring.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
interface PinnedKey {
|
|
2
|
-
fingerprint: string;
|
|
3
|
-
publicKey: string;
|
|
4
|
-
source: string;
|
|
5
|
-
firstSeen: string;
|
|
6
|
-
verifiedAt: string;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Pin a key for an identifier (e.g., "github:alice").
|
|
10
|
-
* Throws if the identifier is already pinned with a different fingerprint.
|
|
11
|
-
*/
|
|
12
|
-
export declare function pinKey(identifier: string, fingerprint: string, publicKey: string, source: string): void;
|
|
13
|
-
/**
|
|
14
|
-
* Get a pinned key by identifier.
|
|
15
|
-
*/
|
|
16
|
-
export declare function getPinnedKey(identifier: string): PinnedKey | null;
|
|
17
|
-
/**
|
|
18
|
-
* Check a key against the pin store.
|
|
19
|
-
*/
|
|
20
|
-
export declare function checkKeyPin(identifier: string, fingerprint: string): 'match' | 'mismatch' | 'new';
|
|
21
|
-
/**
|
|
22
|
-
* Update a pinned key after verified rotation (e.g., new key confirmed on GitHub).
|
|
23
|
-
*/
|
|
24
|
-
export declare function updatePinnedKey(identifier: string, fingerprint: string, publicKey: string, source: string): void;
|
|
25
|
-
export declare class KeyMismatchError extends Error {
|
|
26
|
-
readonly identifier: string;
|
|
27
|
-
readonly pinnedFingerprint: string;
|
|
28
|
-
readonly newFingerprint: string;
|
|
29
|
-
readonly pinnedSource: string;
|
|
30
|
-
readonly newSource: string;
|
|
31
|
-
constructor(identifier: string, pinnedFingerprint: string, newFingerprint: string, pinnedSource: string, newSource: string);
|
|
32
|
-
}
|
|
33
|
-
export {};
|
|
34
|
-
//# sourceMappingURL=known-keys.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"known-keys.d.ts","sourceRoot":"","sources":["../../crypto/known-keys.ts"],"names":[],"mappings":"AASA,UAAU,SAAS;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkCD;;;GAGG;AACH,wBAAgB,MAAM,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI,CA8BN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGjE;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,GAAG,UAAU,GAAG,KAAK,CAO9B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI,CAUN;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,UAAU,EAAE,MAAM;aAClB,iBAAiB,EAAE,MAAM;aACzB,cAAc,EAAE,MAAM;aACtB,YAAY,EAAE,MAAM;aACpB,SAAS,EAAE,MAAM;gBAJjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM;CAUpC"}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import * as crypto from 'node:crypto';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import * as os from 'node:os';
|
|
5
|
-
function getKnownKeysPath() {
|
|
6
|
-
return path.join(os.homedir(), '.chaoskb', 'known_keys.json');
|
|
7
|
-
}
|
|
8
|
-
function fingerprintsEqual(a, b) {
|
|
9
|
-
const aBuf = Buffer.from(a);
|
|
10
|
-
const bBuf = Buffer.from(b);
|
|
11
|
-
if (aBuf.length !== bBuf.length)
|
|
12
|
-
return false;
|
|
13
|
-
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Trust on First Use (TOFU) key pinning for invite recipients.
|
|
17
|
-
*
|
|
18
|
-
* When we first see a recipient's public key (from GitHub, GitLab, or direct),
|
|
19
|
-
* we pin it. On subsequent invites, we check if the key has changed.
|
|
20
|
-
* A key mismatch triggers a warning; a conflict with an independent source
|
|
21
|
-
* is a hard block.
|
|
22
|
-
*/
|
|
23
|
-
function loadStore() {
|
|
24
|
-
try {
|
|
25
|
-
return JSON.parse(fs.readFileSync(getKnownKeysPath(), 'utf-8'));
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function saveStore(store) {
|
|
32
|
-
const dir = path.dirname(getKnownKeysPath());
|
|
33
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
34
|
-
fs.writeFileSync(getKnownKeysPath(), JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Pin a key for an identifier (e.g., "github:alice").
|
|
38
|
-
* Throws if the identifier is already pinned with a different fingerprint.
|
|
39
|
-
*/
|
|
40
|
-
export function pinKey(identifier, fingerprint, publicKey, source) {
|
|
41
|
-
const store = loadStore();
|
|
42
|
-
const existing = store[identifier];
|
|
43
|
-
if (existing && !fingerprintsEqual(existing.fingerprint, fingerprint)) {
|
|
44
|
-
throw new KeyMismatchError(identifier, existing.fingerprint, fingerprint, existing.source, source);
|
|
45
|
-
}
|
|
46
|
-
if (existing && fingerprintsEqual(existing.fingerprint, fingerprint)) {
|
|
47
|
-
// Same key — update verifiedAt
|
|
48
|
-
existing.verifiedAt = new Date().toISOString();
|
|
49
|
-
saveStore(store);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
// New key — pin it
|
|
53
|
-
store[identifier] = {
|
|
54
|
-
fingerprint,
|
|
55
|
-
publicKey,
|
|
56
|
-
source,
|
|
57
|
-
firstSeen: new Date().toISOString(),
|
|
58
|
-
verifiedAt: new Date().toISOString(),
|
|
59
|
-
};
|
|
60
|
-
saveStore(store);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Get a pinned key by identifier.
|
|
64
|
-
*/
|
|
65
|
-
export function getPinnedKey(identifier) {
|
|
66
|
-
const store = loadStore();
|
|
67
|
-
return store[identifier] ?? null;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Check a key against the pin store.
|
|
71
|
-
*/
|
|
72
|
-
export function checkKeyPin(identifier, fingerprint) {
|
|
73
|
-
const store = loadStore();
|
|
74
|
-
const existing = store[identifier];
|
|
75
|
-
if (!existing)
|
|
76
|
-
return 'new';
|
|
77
|
-
if (fingerprintsEqual(existing.fingerprint, fingerprint))
|
|
78
|
-
return 'match';
|
|
79
|
-
return 'mismatch';
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Update a pinned key after verified rotation (e.g., new key confirmed on GitHub).
|
|
83
|
-
*/
|
|
84
|
-
export function updatePinnedKey(identifier, fingerprint, publicKey, source) {
|
|
85
|
-
const store = loadStore();
|
|
86
|
-
store[identifier] = {
|
|
87
|
-
fingerprint,
|
|
88
|
-
publicKey,
|
|
89
|
-
source,
|
|
90
|
-
firstSeen: store[identifier]?.firstSeen ?? new Date().toISOString(),
|
|
91
|
-
verifiedAt: new Date().toISOString(),
|
|
92
|
-
};
|
|
93
|
-
saveStore(store);
|
|
94
|
-
}
|
|
95
|
-
export class KeyMismatchError extends Error {
|
|
96
|
-
identifier;
|
|
97
|
-
pinnedFingerprint;
|
|
98
|
-
newFingerprint;
|
|
99
|
-
pinnedSource;
|
|
100
|
-
newSource;
|
|
101
|
-
constructor(identifier, pinnedFingerprint, newFingerprint, pinnedSource, newSource) {
|
|
102
|
-
super(`Key mismatch for ${identifier}:\n` +
|
|
103
|
-
` Pinned: ${pinnedFingerprint} (source: ${pinnedSource})\n` +
|
|
104
|
-
` Received: ${newFingerprint} (source: ${newSource})\n` +
|
|
105
|
-
`This may indicate a compromised key source. The operation was blocked.`);
|
|
106
|
-
this.identifier = identifier;
|
|
107
|
-
this.pinnedFingerprint = pinnedFingerprint;
|
|
108
|
-
this.newFingerprint = newFingerprint;
|
|
109
|
-
this.pinnedSource = pinnedSource;
|
|
110
|
-
this.newSource = newSource;
|
|
111
|
-
this.name = 'KeyMismatchError';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
//# sourceMappingURL=known-keys.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"known-keys.js","sourceRoot":"","sources":["../../crypto/known-keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ISecureBuffer } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Create a new random project key and wrap it with the personal master key.
|
|
4
|
-
*
|
|
5
|
-
* Uses HKDF to derive a wrapping key from the master key, then encrypts
|
|
6
|
-
* the project key with XChaCha20-Poly1305. AAD can include a project name
|
|
7
|
-
* for binding.
|
|
8
|
-
*
|
|
9
|
-
* @param personalMasterKey - The user's personal master key
|
|
10
|
-
* @param projectName - Optional project name for AAD binding
|
|
11
|
-
* @returns The project key (SecureBuffer) and the wrapped (encrypted) form
|
|
12
|
-
*/
|
|
13
|
-
export declare function createProjectKey(personalMasterKey: ISecureBuffer, projectName?: string): {
|
|
14
|
-
projectKey: ISecureBuffer;
|
|
15
|
-
wrappedKey: Uint8Array;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Unwrap a project key using the personal master key.
|
|
19
|
-
*
|
|
20
|
-
* @param wrappedKey - The wrapped project key (nonce || ciphertext || tag)
|
|
21
|
-
* @param personalMasterKey - The user's personal master key
|
|
22
|
-
* @param projectName - Optional project name for AAD binding (must match what was used during wrapping)
|
|
23
|
-
* @returns The unwrapped project key as a SecureBuffer
|
|
24
|
-
*/
|
|
25
|
-
export declare function unwrapProjectKey(wrappedKey: Uint8Array, personalMasterKey: ISecureBuffer, projectName?: string): ISecureBuffer;
|
|
26
|
-
//# sourceMappingURL=project-keys.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"project-keys.d.ts","sourceRoot":"","sources":["../../crypto/project-keys.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,iBAAiB,EAAE,aAAa,EAChC,WAAW,CAAC,EAAE,MAAM,GACnB;IAAE,UAAU,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CA8BvD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,aAAa,EAChC,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CA+Bf"}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import { aeadEncrypt, aeadDecrypt } from './aead.js';
|
|
3
|
-
import { deriveKey } from './hkdf.js';
|
|
4
|
-
import { SecureBuffer } from './secure-buffer.js';
|
|
5
|
-
const PROJECT_KEY_LENGTH = 32;
|
|
6
|
-
const WRAP_INFO = 'chaoskb-project-wrap';
|
|
7
|
-
/**
|
|
8
|
-
* Create a new random project key and wrap it with the personal master key.
|
|
9
|
-
*
|
|
10
|
-
* Uses HKDF to derive a wrapping key from the master key, then encrypts
|
|
11
|
-
* the project key with XChaCha20-Poly1305. AAD can include a project name
|
|
12
|
-
* for binding.
|
|
13
|
-
*
|
|
14
|
-
* @param personalMasterKey - The user's personal master key
|
|
15
|
-
* @param projectName - Optional project name for AAD binding
|
|
16
|
-
* @returns The project key (SecureBuffer) and the wrapped (encrypted) form
|
|
17
|
-
*/
|
|
18
|
-
export function createProjectKey(personalMasterKey, projectName) {
|
|
19
|
-
// Generate random 32-byte project key
|
|
20
|
-
const projectKeyBytes = randomBytes(PROJECT_KEY_LENGTH);
|
|
21
|
-
// Derive a wrapping key via HKDF
|
|
22
|
-
const wrappingKey = deriveKey(new Uint8Array(personalMasterKey.buffer), WRAP_INFO);
|
|
23
|
-
// AAD: project name if available, otherwise empty
|
|
24
|
-
const aad = projectName
|
|
25
|
-
? new TextEncoder().encode(projectName)
|
|
26
|
-
: new Uint8Array(0);
|
|
27
|
-
// Encrypt project key with XChaCha20-Poly1305
|
|
28
|
-
const { nonce, ciphertext, tag } = aeadEncrypt(wrappingKey, projectKeyBytes, aad);
|
|
29
|
-
// Zero wrapping key and plaintext project key bytes
|
|
30
|
-
wrappingKey.fill(0);
|
|
31
|
-
// Serialize: nonce(24) || ciphertext || tag(16)
|
|
32
|
-
const wrappedKey = new Uint8Array(nonce.length + ciphertext.length + tag.length);
|
|
33
|
-
wrappedKey.set(nonce, 0);
|
|
34
|
-
wrappedKey.set(ciphertext, nonce.length);
|
|
35
|
-
wrappedKey.set(tag, nonce.length + ciphertext.length);
|
|
36
|
-
const projectKey = SecureBuffer.from(projectKeyBytes);
|
|
37
|
-
return { projectKey, wrappedKey };
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Unwrap a project key using the personal master key.
|
|
41
|
-
*
|
|
42
|
-
* @param wrappedKey - The wrapped project key (nonce || ciphertext || tag)
|
|
43
|
-
* @param personalMasterKey - The user's personal master key
|
|
44
|
-
* @param projectName - Optional project name for AAD binding (must match what was used during wrapping)
|
|
45
|
-
* @returns The unwrapped project key as a SecureBuffer
|
|
46
|
-
*/
|
|
47
|
-
export function unwrapProjectKey(wrappedKey, personalMasterKey, projectName) {
|
|
48
|
-
const NONCE_SIZE = 24;
|
|
49
|
-
const TAG_SIZE = 16;
|
|
50
|
-
if (wrappedKey.length < NONCE_SIZE + TAG_SIZE + 1) {
|
|
51
|
-
throw new Error('Wrapped key is too short');
|
|
52
|
-
}
|
|
53
|
-
// Derive the same wrapping key
|
|
54
|
-
const wrappingKey = deriveKey(new Uint8Array(personalMasterKey.buffer), WRAP_INFO);
|
|
55
|
-
// Split wrapped key into nonce, ciphertext, tag
|
|
56
|
-
const nonce = wrappedKey.slice(0, NONCE_SIZE);
|
|
57
|
-
const ciphertext = wrappedKey.slice(NONCE_SIZE, wrappedKey.length - TAG_SIZE);
|
|
58
|
-
const tag = wrappedKey.slice(wrappedKey.length - TAG_SIZE);
|
|
59
|
-
// AAD: project name if available, otherwise empty
|
|
60
|
-
const aad = projectName
|
|
61
|
-
? new TextEncoder().encode(projectName)
|
|
62
|
-
: new Uint8Array(0);
|
|
63
|
-
// Decrypt
|
|
64
|
-
const projectKeyBytes = aeadDecrypt(wrappingKey, nonce, ciphertext, tag, aad);
|
|
65
|
-
// Zero wrapping key
|
|
66
|
-
wrappingKey.fill(0);
|
|
67
|
-
return SecureBuffer.from(Buffer.from(projectKeyBytes));
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=project-keys.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"project-keys.js","sourceRoot":"","sources":["../../crypto/project-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,SAAS,GAAG,sBAAsB,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,iBAAgC,EAChC,WAAoB;IAEpB,sCAAsC;IACtC,MAAM,eAAe,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAExD,iCAAiC;IACjC,MAAM,WAAW,GAAG,SAAS,CAC3B,IAAI,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,EACxC,SAAS,CACV,CAAC;IAEF,kDAAkD;IAClD,MAAM,GAAG,GAAG,WAAW;QACrB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC;QACvC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAEtB,8CAA8C;IAC9C,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;IAElF,oDAAoD;IACpD,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpB,gDAAgD;IAChD,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACjF,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACzB,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAEtD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAsB,EACtB,iBAAgC,EAChC,WAAoB;IAEpB,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,QAAQ,GAAG,EAAE,CAAC;IAEpB,IAAI,UAAU,CAAC,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,+BAA+B;IAC/B,MAAM,WAAW,GAAG,SAAS,CAC3B,IAAI,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,EACxC,SAAS,CACV,CAAC;IAEF,gDAAgD;IAChD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC;IAE3D,kDAAkD;IAClD,MAAM,GAAG,GAAG,WAAW;QACrB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC;QACvC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAEtB,UAAU;IACV,MAAM,eAAe,GAAG,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAE9E,oBAAoB;IACpB,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpB,OAAO,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ISecureBuffer } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Memory-locked buffer for sensitive key material.
|
|
4
|
-
* Uses sodium_malloc (mlock'd pages) and sodium_memzero on dispose.
|
|
5
|
-
*/
|
|
6
|
-
export declare class SecureBuffer implements ISecureBuffer {
|
|
7
|
-
private _buffer;
|
|
8
|
-
private _disposed;
|
|
9
|
-
private constructor();
|
|
10
|
-
/** Read the buffer contents. Throws if disposed. */
|
|
11
|
-
get buffer(): Buffer;
|
|
12
|
-
/** Byte length of the buffer. */
|
|
13
|
-
get length(): number;
|
|
14
|
-
/** Whether the buffer has been zeroed and disposed. */
|
|
15
|
-
get isDisposed(): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Zero the buffer contents and mark as disposed.
|
|
18
|
-
* Safe to call multiple times (idempotent).
|
|
19
|
-
*/
|
|
20
|
-
dispose(): void;
|
|
21
|
-
/** Support `using` keyword (TC39 Explicit Resource Management). */
|
|
22
|
-
[Symbol.dispose](): void;
|
|
23
|
-
/**
|
|
24
|
-
* Copy data into a new SecureBuffer and zero the source.
|
|
25
|
-
* The source buffer is zeroed after copying regardless of type.
|
|
26
|
-
*/
|
|
27
|
-
static from(data: Buffer | Uint8Array): SecureBuffer;
|
|
28
|
-
/** Allocate a new zeroed SecureBuffer of the given length. */
|
|
29
|
-
static alloc(length: number): SecureBuffer;
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=secure-buffer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"secure-buffer.d.ts","sourceRoot":"","sources":["../../crypto/secure-buffer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO;IAIP,oDAAoD;IACpD,IAAI,MAAM,IAAI,MAAM,CAKnB;IAED,iCAAiC;IACjC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,uDAAuD;IACvD,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI;IAQf,mEAAmE;IACnE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAIxB;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY;IASpD,8DAA8D;IAC9D,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;CAK3C"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import sodium from 'sodium-native';
|
|
2
|
-
/**
|
|
3
|
-
* Memory-locked buffer for sensitive key material.
|
|
4
|
-
* Uses sodium_malloc (mlock'd pages) and sodium_memzero on dispose.
|
|
5
|
-
*/
|
|
6
|
-
export class SecureBuffer {
|
|
7
|
-
_buffer;
|
|
8
|
-
_disposed = false;
|
|
9
|
-
constructor(length) {
|
|
10
|
-
this._buffer = sodium.sodium_malloc(length);
|
|
11
|
-
}
|
|
12
|
-
/** Read the buffer contents. Throws if disposed. */
|
|
13
|
-
get buffer() {
|
|
14
|
-
if (this._disposed) {
|
|
15
|
-
throw new Error('SecureBuffer has been disposed');
|
|
16
|
-
}
|
|
17
|
-
return this._buffer;
|
|
18
|
-
}
|
|
19
|
-
/** Byte length of the buffer. */
|
|
20
|
-
get length() {
|
|
21
|
-
return this._buffer.byteLength;
|
|
22
|
-
}
|
|
23
|
-
/** Whether the buffer has been zeroed and disposed. */
|
|
24
|
-
get isDisposed() {
|
|
25
|
-
return this._disposed;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Zero the buffer contents and mark as disposed.
|
|
29
|
-
* Safe to call multiple times (idempotent).
|
|
30
|
-
*/
|
|
31
|
-
dispose() {
|
|
32
|
-
if (this._disposed) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
sodium.sodium_memzero(this._buffer);
|
|
36
|
-
this._disposed = true;
|
|
37
|
-
}
|
|
38
|
-
/** Support `using` keyword (TC39 Explicit Resource Management). */
|
|
39
|
-
[Symbol.dispose]() {
|
|
40
|
-
this.dispose();
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Copy data into a new SecureBuffer and zero the source.
|
|
44
|
-
* The source buffer is zeroed after copying regardless of type.
|
|
45
|
-
*/
|
|
46
|
-
static from(data) {
|
|
47
|
-
const sb = new SecureBuffer(data.byteLength);
|
|
48
|
-
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
49
|
-
buf.copy(sb._buffer);
|
|
50
|
-
// Zero the source
|
|
51
|
-
sodium.sodium_memzero(buf);
|
|
52
|
-
return sb;
|
|
53
|
-
}
|
|
54
|
-
/** Allocate a new zeroed SecureBuffer of the given length. */
|
|
55
|
-
static alloc(length) {
|
|
56
|
-
const sb = new SecureBuffer(length);
|
|
57
|
-
sodium.sodium_memzero(sb._buffer);
|
|
58
|
-
return sb;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
//# sourceMappingURL=secure-buffer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"secure-buffer.js","sourceRoot":"","sources":["../../crypto/secure-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,eAAe,CAAC;AAInC;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,OAAO,CAAS;IAChB,SAAS,GAAG,KAAK,CAAC;IAE1B,YAAoB,MAAc;QAChC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,oDAAoD;IACpD,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAED,uDAAuD;IACvD,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QACD,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,mEAAmE;IACnE,CAAC,MAAM,CAAC,OAAO,CAAC;QACd,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,IAAyB;QACnC,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACtG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QACrB,kBAAkB;QAClB,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,8DAA8D;IAC9D,MAAM,CAAC,KAAK,CAAC,MAAc;QACzB,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,EAAE,CAAC;IACZ,CAAC;CACF"}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ISecureBuffer } from '../types.js';
|
|
2
|
-
export { wrapMasterKey, unwrapMasterKeyEd25519, unwrapMasterKeyRSA } from './standard.js';
|
|
3
|
-
/**
|
|
4
|
-
* @deprecated Enhanced tier is deprecated. New installations use Standard or Maximum only.
|
|
5
|
-
*
|
|
6
|
-
* Enhanced tier: BIP39 24-word recovery key.
|
|
7
|
-
*
|
|
8
|
-
* The master key is a 256-bit random value which maps directly to a 24-word
|
|
9
|
-
* BIP39 mnemonic. Either the mnemonic or the SSH-wrapped key can recover.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* @deprecated Use Standard tier (SSH key wrapping) instead.
|
|
13
|
-
*
|
|
14
|
-
* Encode a 256-bit master key as a 24-word BIP39 mnemonic.
|
|
15
|
-
* The master key bytes are used directly as the entropy.
|
|
16
|
-
*/
|
|
17
|
-
export declare function generateRecoveryKey(masterKey: ISecureBuffer): string;
|
|
18
|
-
/**
|
|
19
|
-
* @deprecated Use Standard tier (SSH key wrapping) instead.
|
|
20
|
-
*
|
|
21
|
-
* Decode a 24-word BIP39 mnemonic back to the 256-bit master key.
|
|
22
|
-
* Returns a SecureBuffer containing the recovered key.
|
|
23
|
-
*/
|
|
24
|
-
export declare function recoverFromMnemonic(mnemonic: string): ISecureBuffer;
|
|
25
|
-
//# sourceMappingURL=enhanced.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"enhanced.d.ts","sourceRoot":"","sources":["../../../crypto/tiers/enhanced.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE1F;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,CAQpE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAoBnE"}
|