0nmcp 1.7.0 → 2.1.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.
@@ -0,0 +1,278 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Container Crypto Primitives
3
+ // ============================================================
4
+ // AES-256-GCM encryption, Argon2id key derivation, X25519 ECDH,
5
+ // Ed25519 signing, SHA3-256 hashing for the 0nVault container.
6
+ //
7
+ // Patent Pending: US Provisional Patent Application #63/990,046
8
+ // "System and Method for Semantically-Layered Encrypted Digital
9
+ // Business Asset Transfer with Multi-Party Escrow Key Distribution
10
+ // and Public-Verifiable State Certification"
11
+ // ============================================================
12
+
13
+ import { randomBytes, createCipheriv, createDecipheriv, randomUUID, pbkdf2Sync } from "crypto";
14
+ import { createRequire } from "module";
15
+
16
+ const require = createRequire(import.meta.url);
17
+
18
+ // ── Load optional dependencies ───────────────────────────────
19
+ let naclModule = null;
20
+ let sha3Module = null;
21
+
22
+ try { naclModule = require("tweetnacl"); } catch {}
23
+ try { sha3Module = require("js-sha3"); } catch {}
24
+
25
+ function requireNacl() {
26
+ if (naclModule) return naclModule;
27
+ throw new Error("tweetnacl package required. Install: npm i tweetnacl");
28
+ }
29
+
30
+ function requireSha3() {
31
+ if (sha3Module) return sha3Module;
32
+ throw new Error("js-sha3 package required. Install: npm i js-sha3");
33
+ }
34
+
35
+ // ── Constants ────────────────────────────────────────────────
36
+ const ALGORITHM = "aes-256-gcm";
37
+ const KEY_LENGTH = 32; // 256 bits
38
+ const IV_LENGTH = 12; // 96 bits (GCM recommended)
39
+ const SALT_LENGTH = 32; // 256 bits
40
+ const TAG_LENGTH = 16; // 128 bits (GCM auth tag)
41
+
42
+ // Argon2id parameters for credentials double-encryption
43
+ const ARGON2_MEMORY = 65536; // 64MB
44
+ const ARGON2_ITERATIONS = 4;
45
+ const ARGON2_PARALLELISM = 2;
46
+ const ARGON2_HASH_LENGTH = 32; // 256 bits
47
+
48
+ // ── Key Derivation ───────────────────────────────────────────
49
+
50
+ /**
51
+ * Derive key from passphrase using PBKDF2-SHA512.
52
+ */
53
+ function deriveKeyPBKDF2(passphrase, salt) {
54
+ return pbkdf2Sync(passphrase, salt, 100000, KEY_LENGTH, "sha512");
55
+ }
56
+
57
+ /**
58
+ * Derive key using Argon2id for credentials double-encryption.
59
+ *
60
+ * @param {string} passphrase
61
+ * @param {Buffer} salt
62
+ * @returns {Promise<Buffer>} 32-byte key
63
+ */
64
+ export async function deriveKeyArgon2(passphrase, salt) {
65
+ let argon2;
66
+ try { argon2 = await import("argon2"); } catch {
67
+ throw new Error("argon2 package required for credentials layer. Install: npm i argon2");
68
+ }
69
+ const mod = argon2.default || argon2;
70
+ const hash = await mod.hash(passphrase, {
71
+ type: 2, // argon2id
72
+ memoryCost: ARGON2_MEMORY,
73
+ timeCost: ARGON2_ITERATIONS,
74
+ parallelism: ARGON2_PARALLELISM,
75
+ hashLength: ARGON2_HASH_LENGTH,
76
+ salt,
77
+ raw: true,
78
+ });
79
+ return hash;
80
+ }
81
+
82
+ // ── AES-256-GCM ──────────────────────────────────────────────
83
+
84
+ /**
85
+ * Encrypt data with AES-256-GCM using a passphrase-derived key.
86
+ * Each call generates a unique IV and salt.
87
+ *
88
+ * @param {Buffer|string} plaintext - Data to encrypt
89
+ * @param {string} passphrase - Encryption passphrase
90
+ * @returns {{ ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer }}
91
+ */
92
+ export function encryptAES(plaintext, passphrase) {
93
+ const salt = randomBytes(SALT_LENGTH);
94
+ const iv = randomBytes(IV_LENGTH);
95
+ const key = deriveKeyPBKDF2(passphrase, salt);
96
+
97
+ const cipher = createCipheriv(ALGORITHM, key, iv);
98
+ const data = typeof plaintext === "string" ? Buffer.from(plaintext, "utf8") : plaintext;
99
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
100
+ const tag = cipher.getAuthTag();
101
+
102
+ return { ciphertext: encrypted, iv, salt, tag };
103
+ }
104
+
105
+ /**
106
+ * Decrypt AES-256-GCM encrypted data.
107
+ *
108
+ * @param {Buffer} ciphertext - Encrypted data
109
+ * @param {string} passphrase - Decryption passphrase
110
+ * @param {Buffer} iv - Initialization vector
111
+ * @param {Buffer} salt - PBKDF2 salt
112
+ * @param {Buffer} tag - GCM auth tag
113
+ * @returns {Buffer} Decrypted data
114
+ */
115
+ export function decryptAES(ciphertext, passphrase, iv, salt, tag) {
116
+ const key = deriveKeyPBKDF2(passphrase, salt);
117
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
118
+ decipher.setAuthTag(tag);
119
+
120
+ try {
121
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
122
+ } catch {
123
+ throw new Error("Decryption failed — wrong passphrase or corrupted data");
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Encrypt data with a raw 256-bit key (for escrow layer keys).
129
+ *
130
+ * @param {Buffer} plaintext - Data to encrypt
131
+ * @param {Buffer} key - 32-byte AES key
132
+ * @returns {{ ciphertext: Buffer, iv: Buffer, tag: Buffer }}
133
+ */
134
+ export function encryptAESRaw(plaintext, key) {
135
+ const iv = randomBytes(IV_LENGTH);
136
+ const cipher = createCipheriv(ALGORITHM, key, iv);
137
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
138
+ const tag = cipher.getAuthTag();
139
+ return { ciphertext: encrypted, iv, tag };
140
+ }
141
+
142
+ /**
143
+ * Decrypt with a raw 256-bit key.
144
+ *
145
+ * @param {Buffer} ciphertext
146
+ * @param {Buffer} key - 32-byte AES key
147
+ * @param {Buffer} iv
148
+ * @param {Buffer} tag
149
+ * @returns {Buffer}
150
+ */
151
+ export function decryptAESRaw(ciphertext, key, iv, tag) {
152
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
153
+ decipher.setAuthTag(tag);
154
+ try {
155
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
156
+ } catch {
157
+ throw new Error("Decryption failed — wrong key or corrupted data");
158
+ }
159
+ }
160
+
161
+ // ── Ed25519 Signing ──────────────────────────────────────────
162
+
163
+ /**
164
+ * Generate Ed25519 signing keypair.
165
+ * @returns {{ publicKey: Buffer, secretKey: Buffer }}
166
+ */
167
+ export function generateSigningKeyPair() {
168
+ const nacl = requireNacl();
169
+ const pair = nacl.sign.keyPair();
170
+ return {
171
+ publicKey: Buffer.from(pair.publicKey),
172
+ secretKey: Buffer.from(pair.secretKey),
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Sign data with Ed25519.
178
+ * @param {Buffer} data - Data to sign
179
+ * @param {Buffer} secretKey - 64-byte Ed25519 secret key
180
+ * @returns {Buffer} 64-byte signature
181
+ */
182
+ export function sign(data, secretKey) {
183
+ const nacl = requireNacl();
184
+ const sig = nacl.sign.detached(new Uint8Array(data), new Uint8Array(secretKey));
185
+ return Buffer.from(sig);
186
+ }
187
+
188
+ /**
189
+ * Verify Ed25519 signature.
190
+ * @param {Buffer} data
191
+ * @param {Buffer} signature - 64-byte signature
192
+ * @param {Buffer} publicKey - 32-byte public key
193
+ * @returns {boolean}
194
+ */
195
+ export function verifySignature(data, signature, publicKey) {
196
+ const nacl = requireNacl();
197
+ return nacl.sign.detached.verify(
198
+ new Uint8Array(data),
199
+ new Uint8Array(signature),
200
+ new Uint8Array(publicKey)
201
+ );
202
+ }
203
+
204
+ // ── X25519 ECDH ──────────────────────────────────────────────
205
+
206
+ /**
207
+ * Generate X25519 keypair for escrow key exchange.
208
+ * @returns {{ publicKey: Buffer, secretKey: Buffer }}
209
+ */
210
+ export function generateEscrowKeyPair() {
211
+ const nacl = requireNacl();
212
+ const pair = nacl.box.keyPair();
213
+ return {
214
+ publicKey: Buffer.from(pair.publicKey),
215
+ secretKey: Buffer.from(pair.secretKey),
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Compute shared secret via X25519 ECDH.
221
+ * @param {Buffer} mySecretKey - 32-byte secret key
222
+ * @param {Buffer} theirPublicKey - 32-byte public key
223
+ * @returns {Buffer} 32-byte shared secret
224
+ */
225
+ export function computeSharedSecret(mySecretKey, theirPublicKey) {
226
+ const nacl = requireNacl();
227
+ const shared = nacl.box.before(
228
+ new Uint8Array(theirPublicKey),
229
+ new Uint8Array(mySecretKey)
230
+ );
231
+ return Buffer.from(shared);
232
+ }
233
+
234
+ // ── SHA3-256 ─────────────────────────────────────────────────
235
+
236
+ /**
237
+ * Compute SHA3-256 hash.
238
+ * @param {Buffer|string} data
239
+ * @returns {Buffer} 32-byte hash
240
+ */
241
+ export function sha3(data) {
242
+ const sha3Lib = requireSha3();
243
+ const input = typeof data === "string" ? data : new Uint8Array(data);
244
+ const hash = sha3Lib.sha3_256.create().update(input).digest();
245
+ return Buffer.from(hash);
246
+ }
247
+
248
+ /**
249
+ * Compute SHA3-256 hash and return hex string.
250
+ * @param {Buffer|string} data
251
+ * @returns {string} 64-char hex hash
252
+ */
253
+ export function sha3Hex(data) {
254
+ return sha3(data).toString("hex");
255
+ }
256
+
257
+ // ── UUID ─────────────────────────────────────────────────────
258
+
259
+ /**
260
+ * Generate a UUID v4 transfer ID.
261
+ * @returns {string}
262
+ */
263
+ export function generateTransferId() {
264
+ return randomUUID();
265
+ }
266
+
267
+ // ── Exports ──────────────────────────────────────────────────
268
+ export {
269
+ ALGORITHM,
270
+ KEY_LENGTH,
271
+ IV_LENGTH,
272
+ SALT_LENGTH,
273
+ TAG_LENGTH,
274
+ ARGON2_MEMORY,
275
+ ARGON2_ITERATIONS,
276
+ ARGON2_PARALLELISM,
277
+ ARGON2_HASH_LENGTH,
278
+ };
@@ -0,0 +1,286 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Deed Credential Collector
3
+ // ============================================================
4
+ // Collects credentials from multiple sources (files, dirs,
5
+ // manual entry) and auto-detects services using the engine
6
+ // mapper. Feeds into BusinessDeed.create().
7
+ //
8
+ // Patent Pending: US Provisional Patent Application #63/990,046
9
+ // ============================================================
10
+
11
+ import { existsSync, readdirSync, readFileSync } from "fs";
12
+ import { join, extname } from "path";
13
+ import { homedir } from "os";
14
+ import { parseFile, parseEnvString, parseJsonString, parseCsvString } from "../engine/parser.js";
15
+ import { mapEnvVars, groupByService } from "../engine/mapper.js";
16
+ import { verifyCredentials } from "../engine/validator.js";
17
+
18
+ const CONNECTIONS_DIR = join(homedir(), ".0n", "connections");
19
+
20
+ // ── Collect from .0n connection files ───────────────────────
21
+
22
+ /**
23
+ * Load credentials from ~/.0n/connections/*.0n files.
24
+ *
25
+ * @param {string} [dir] - Connections directory (default: ~/.0n/connections/)
26
+ * @returns {{ credentials: Object, services: string[] }}
27
+ */
28
+ export function collectFromConnections(dir = CONNECTIONS_DIR) {
29
+ const credentials = {};
30
+ const services = [];
31
+
32
+ if (!existsSync(dir)) return { credentials, services };
33
+
34
+ const files = readdirSync(dir).filter(f => f.endsWith(".0n"));
35
+
36
+ for (const file of files) {
37
+ try {
38
+ const content = readFileSync(join(dir, file), "utf-8");
39
+ const data = JSON.parse(content);
40
+
41
+ // .0n connection file format: { $0n: { type, service }, credentials: { ... } }
42
+ const service = data?.$0n?.service || file.replace(".0n", "");
43
+ if (data.credentials && typeof data.credentials === "object") {
44
+ credentials[service] = data.credentials;
45
+ services.push(service);
46
+ }
47
+ } catch {
48
+ // Skip invalid files
49
+ }
50
+ }
51
+
52
+ return { credentials, services };
53
+ }
54
+
55
+ // ── Collect from file (auto-detect format) ──────────────────
56
+
57
+ /**
58
+ * Collect credentials from a file (.env, .json, .csv).
59
+ *
60
+ * @param {string} filePath
61
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
62
+ */
63
+ export function collectFromFile(filePath) {
64
+ const { entries } = parseFile(filePath);
65
+ return processEntries(entries);
66
+ }
67
+
68
+ // ── Collect from string content ─────────────────────────────
69
+
70
+ /**
71
+ * Collect from raw .env string content.
72
+ * @param {string} content
73
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
74
+ */
75
+ export function collectFromEnvString(content) {
76
+ const entries = parseEnvString(content);
77
+ return processEntries(entries);
78
+ }
79
+
80
+ /**
81
+ * Collect from raw JSON string content.
82
+ * @param {string} content
83
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
84
+ */
85
+ export function collectFromJsonString(content) {
86
+ const entries = parseJsonString(content);
87
+ return processEntries(entries);
88
+ }
89
+
90
+ /**
91
+ * Collect from raw CSV string content.
92
+ * @param {string} content
93
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
94
+ */
95
+ export function collectFromCsvString(content) {
96
+ const entries = parseCsvString(content);
97
+ return processEntries(entries);
98
+ }
99
+
100
+ // ── Collect from manual key-value pairs ─────────────────────
101
+
102
+ /**
103
+ * Collect from manual key-value pairs.
104
+ *
105
+ * @param {Array<{ key: string, value: string }>} pairs
106
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
107
+ */
108
+ export function collectFromManual(pairs) {
109
+ return processEntries(pairs);
110
+ }
111
+
112
+ // ── Collect from pre-structured service credentials ─────────
113
+
114
+ /**
115
+ * Collect from already-structured credentials object.
116
+ * Format: { stripe: { apiKey: "sk_..." }, github: { token: "ghp_..." } }
117
+ *
118
+ * @param {Object} structured
119
+ * @returns {{ credentials: Object, services: string[] }}
120
+ */
121
+ export function collectFromStructured(structured) {
122
+ const services = Object.keys(structured);
123
+ return { credentials: structured, services };
124
+ }
125
+
126
+ // ── Process entries through mapper ──────────────────────────
127
+
128
+ /**
129
+ * Process raw key-value entries through the mapper pipeline.
130
+ *
131
+ * @param {Array<{ key: string, value: string }>} entries
132
+ * @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
133
+ */
134
+ function processEntries(entries) {
135
+ const { mapped, unmapped } = mapEnvVars(entries);
136
+ const grouped = groupByService(mapped);
137
+
138
+ // Build credentials and envVars
139
+ const credentials = {};
140
+ const envVars = {};
141
+ const services = [];
142
+
143
+ for (const [service, group] of Object.entries(grouped)) {
144
+ credentials[service] = group.credentials;
145
+ services.push(service);
146
+ for (const envVar of group.envVars) {
147
+ const entry = entries.find(e => e.key === envVar);
148
+ if (entry) envVars[entry.key] = entry.value;
149
+ }
150
+ }
151
+
152
+ // Unmapped entries go to envVars
153
+ for (const entry of unmapped) {
154
+ envVars[entry.key] = entry.value;
155
+ }
156
+
157
+ return { credentials, envVars, services, unmapped };
158
+ }
159
+
160
+ // ── Validate credentials live ───────────────────────────────
161
+
162
+ /**
163
+ * Validate collected credentials against live API endpoints.
164
+ *
165
+ * @param {Object} credentials - { service: { field: value } }
166
+ * @returns {Promise<Object>} Validation results per service
167
+ */
168
+ export async function validateCollected(credentials) {
169
+ const results = {};
170
+ const summary = { total: 0, valid: 0, invalid: 0, skipped: 0 };
171
+
172
+ const entries = Object.entries(credentials);
173
+ summary.total = entries.length;
174
+
175
+ // Validate in parallel batches of 5
176
+ const batchSize = 5;
177
+ for (let i = 0; i < entries.length; i += batchSize) {
178
+ const batch = entries.slice(i, i + batchSize);
179
+ const promises = batch.map(([service, creds]) =>
180
+ verifyCredentials(service, creds).then(r => ({ service, ...r }))
181
+ );
182
+ const batchResults = await Promise.allSettled(promises);
183
+
184
+ for (const result of batchResults) {
185
+ if (result.status === "fulfilled") {
186
+ const r = result.value;
187
+ results[r.service] = r;
188
+ if (r.skipped) summary.skipped++;
189
+ else if (r.valid) summary.valid++;
190
+ else summary.invalid++;
191
+ }
192
+ }
193
+ }
194
+
195
+ return { results, summary };
196
+ }
197
+
198
+ // ── Master collection function ──────────────────────────────
199
+
200
+ /**
201
+ * Collect credentials from multiple sources at once.
202
+ *
203
+ * @param {Object} sources
204
+ * @param {string} [sources.envFile] - Path to .env file
205
+ * @param {string} [sources.jsonFile] - Path to JSON file
206
+ * @param {string} [sources.csvFile] - Path to CSV file
207
+ * @param {string} [sources.connectionsDir] - Path to connections dir
208
+ * @param {Array} [sources.manual] - Manual key-value pairs
209
+ * @param {Object} [sources.structured] - Pre-structured credentials
210
+ * @param {boolean} [sources.validate] - Validate credentials live
211
+ * @returns {Promise<Object>}
212
+ */
213
+ export async function collectCredentials(sources) {
214
+ const allCredentials = {};
215
+ const allEnvVars = {};
216
+ const allServices = new Set();
217
+ const allUnmapped = [];
218
+
219
+ // Collect from each source
220
+ if (sources.envFile && existsSync(sources.envFile)) {
221
+ const result = collectFromFile(sources.envFile);
222
+ mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
223
+ }
224
+
225
+ if (sources.jsonFile && existsSync(sources.jsonFile)) {
226
+ const result = collectFromFile(sources.jsonFile);
227
+ mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
228
+ }
229
+
230
+ if (sources.csvFile && existsSync(sources.csvFile)) {
231
+ const result = collectFromFile(sources.csvFile);
232
+ mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
233
+ }
234
+
235
+ if (sources.connectionsDir || existsSync(CONNECTIONS_DIR)) {
236
+ const dir = sources.connectionsDir || CONNECTIONS_DIR;
237
+ const result = collectFromConnections(dir);
238
+ for (const [svc, creds] of Object.entries(result.credentials)) {
239
+ allCredentials[svc] = { ...allCredentials[svc], ...creds };
240
+ allServices.add(svc);
241
+ }
242
+ }
243
+
244
+ if (sources.manual && Array.isArray(sources.manual)) {
245
+ const result = collectFromManual(sources.manual);
246
+ mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
247
+ }
248
+
249
+ if (sources.structured && typeof sources.structured === "object") {
250
+ const result = collectFromStructured(sources.structured);
251
+ for (const [svc, creds] of Object.entries(result.credentials)) {
252
+ allCredentials[svc] = { ...allCredentials[svc], ...creds };
253
+ allServices.add(svc);
254
+ }
255
+ }
256
+
257
+ const output = {
258
+ credentials: allCredentials,
259
+ envVars: allEnvVars,
260
+ services: Array.from(allServices),
261
+ unmapped: allUnmapped,
262
+ credentialCount: Object.values(allCredentials).reduce(
263
+ (sum, svc) => sum + Object.keys(svc).length, 0
264
+ ),
265
+ };
266
+
267
+ // Optional live validation
268
+ if (sources.validate) {
269
+ output.validation = await validateCollected(allCredentials);
270
+ }
271
+
272
+ return output;
273
+ }
274
+
275
+ // ── Merge helper ────────────────────────────────────────────
276
+
277
+ function mergeResults(allCreds, allEnv, allServices, allUnmapped, result) {
278
+ for (const [svc, creds] of Object.entries(result.credentials || {})) {
279
+ allCreds[svc] = { ...allCreds[svc], ...creds };
280
+ allServices.add(svc);
281
+ }
282
+ for (const [k, v] of Object.entries(result.envVars || {})) {
283
+ allEnv[k] = v;
284
+ }
285
+ if (result.unmapped) allUnmapped.push(...result.unmapped);
286
+ }