mdenc 0.1.0 → 0.1.2
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/README.md +47 -21
- package/dist/{chunk-FFRUPAVV.js → chunk-KNJGGFI4.js} +5 -2
- package/dist/{chunk-FFRUPAVV.js.map → chunk-KNJGGFI4.js.map} +1 -1
- package/dist/cli.js +120 -9
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-ZO2DIE5U.js → hooks-FL46SI4A.js} +2 -2
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- /package/dist/{hooks-ZO2DIE5U.js.map → hooks-FL46SI4A.js.map} +0 -0
package/README.md
CHANGED
|
@@ -27,15 +27,15 @@ Total allocated: $450k for the quarter.
|
|
|
27
27
|
`mdenc encrypt notes.md -o notes.mdenc` turns it into:
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
mdenc:v1 salt_b64=
|
|
31
|
-
hdrauth_b64=
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
seal_b64=
|
|
30
|
+
mdenc:v1 salt_b64=1z94qTI8... file_id_b64=eLP+c5cP... scrypt=N=16384,r=8,p=1
|
|
31
|
+
hdrauth_b64=griicznFYhBTVCeq1lpvB+J73wsJJbheGghxNOIJlu0=
|
|
32
|
+
Qnp4sPf/aN1z/VSkZ8yjGWwk0ZLqwpFAJBzbLOcoKyafqUbMp4Y7WMqF... <- # Meeting Notes
|
|
33
|
+
nD1KIHOMX5VhlSU4USUxWHTrl2Qi6cev/b6J5YJR9C78XHqwnNHVxHgW... <- Discussed the Q3...
|
|
34
|
+
Hes/oW+FeONHytgUa7c9ZzdF4d/w7Ei0tnGiJmqPX0DniJaiV0g0yMhc... <- ## Action Items
|
|
35
|
+
yT7vkHbaXHR390bWz1d/qcK6yVeF3p5/quvW7BOx4hfoU0F2P0/oNAkR... <- Alice will draft...
|
|
36
|
+
dkM7awElU/pfUYs1goxQFlgcyUq8FNHcnZrU76tPaygh7bdgYjdrC7Wx... <- ## Budget
|
|
37
|
+
ZBRV9kdXm7gNiF4BvI9eklrtTjhkI9tLHu001eQUevoZbeKQ8Y70basB... <- Total allocated...
|
|
38
|
+
seal_b64=29ylXnDTWQ09nzZjvoYtYUpfyr4X4NLONxpT/HOC9TU=
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Each paragraph becomes one line of base64. A seal HMAC at the end protects the file's integrity. The file is plain UTF-8 text that git tracks normally.
|
|
@@ -43,17 +43,17 @@ Each paragraph becomes one line of base64. A seal HMAC at the end protects the f
|
|
|
43
43
|
Now you edit the "Action Items" paragraph and re-encrypt. Here's what `git diff` shows:
|
|
44
44
|
|
|
45
45
|
```diff
|
|
46
|
-
mdenc:v1 salt_b64=
|
|
47
|
-
hdrauth_b64=
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
+
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-seal_b64=
|
|
56
|
-
+seal_b64=
|
|
46
|
+
mdenc:v1 salt_b64=1z94qTI8... file_id_b64=eLP+c5cP... scrypt=N=16384,r=8,p=1
|
|
47
|
+
hdrauth_b64=griicznFYhBTVCeq1lpvB+J73wsJJbheGghxNOIJlu0=
|
|
48
|
+
Qnp4sPf/aN1z/VSkZ8yjGWwk0ZLqwpFAJBzbLOcoKyafqUbMp4Y7WMqF...
|
|
49
|
+
nD1KIHOMX5VhlSU4USUxWHTrl2Qi6cev/b6J5YJR9C78XHqwnNHVxHgW...
|
|
50
|
+
Hes/oW+FeONHytgUa7c9ZzdF4d/w7Ei0tnGiJmqPX0DniJaiV0g0yMhc...
|
|
51
|
+
-yT7vkHbaXHR390bWz1d/qcK6yVeF3p5/quvW7BOx4hfoU0F2P0/oNAkR...
|
|
52
|
+
+1RgyC3rXcjykvoL0GgsQsHBmxy5axdD/tqMnicJGjit66+o5bjP1vSbG...
|
|
53
|
+
dkM7awElU/pfUYs1goxQFlgcyUq8FNHcnZrU76tPaygh7bdgYjdrC7Wx...
|
|
54
|
+
ZBRV9kdXm7gNiF4BvI9eklrtTjhkI9tLHu001eQUevoZbeKQ8Y70basB...
|
|
55
|
+
-seal_b64=29ylXnDTWQ09nzZjvoYtYUpfyr4X4NLONxpT/HOC9TU=
|
|
56
|
+
+seal_b64=iNhYjNp69tyv4tkzgDJK5Fh2h1WLgIs3Y1IPRKcpQsE=
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
One paragraph changed, one line in the diff (plus the seal updates). Even inserting a new paragraph between existing ones only adds one line -- surrounding chunks stay unchanged. Compare that to GPG, where the entire file would show as changed.
|
|
@@ -90,6 +90,32 @@ mdenc verify notes.mdenc
|
|
|
90
90
|
|
|
91
91
|
Password is read from `MDENC_PASSWORD` env var or prompted interactively (no echo).
|
|
92
92
|
|
|
93
|
+
## Git Integration
|
|
94
|
+
|
|
95
|
+
mdenc can automatically encrypt and decrypt files as part of your git workflow.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Set up git hooks (pre-commit, post-checkout, post-merge, post-rewrite)
|
|
99
|
+
mdenc init
|
|
100
|
+
|
|
101
|
+
# Generate a random password into .mdenc-password
|
|
102
|
+
mdenc genpass
|
|
103
|
+
|
|
104
|
+
# Mark a directory -- .md files inside will be encrypted on commit
|
|
105
|
+
mdenc mark docs/private
|
|
106
|
+
|
|
107
|
+
# See which files need encryption/decryption
|
|
108
|
+
mdenc status
|
|
109
|
+
|
|
110
|
+
# Watch for changes and encrypt on save
|
|
111
|
+
mdenc watch
|
|
112
|
+
|
|
113
|
+
# Remove mdenc hooks from the repository
|
|
114
|
+
mdenc remove-hooks
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
After `mdenc init` and `mdenc mark`, the workflow is automatic: edit `.md` files normally, and the pre-commit hook encrypts them to `.mdenc` before each commit. Post-checkout and post-merge hooks decrypt them back after switching branches or pulling.
|
|
118
|
+
|
|
93
119
|
## Library
|
|
94
120
|
|
|
95
121
|
```typescript
|
|
@@ -118,7 +144,7 @@ const ok = await verifySeal(encrypted, password);
|
|
|
118
144
|
4. Same content + same keys = same ciphertext, so unchanged chunks produce identical output and minimal diffs
|
|
119
145
|
5. The seal HMAC covers all lines, detecting reordering, truncation, and rollback on decrypt
|
|
120
146
|
|
|
121
|
-
The password is stretched with
|
|
147
|
+
The password is stretched with scrypt (N=16384, r=8, p=1). Keys are derived via HKDF-SHA256 with separate keys for encryption, header authentication, and nonce derivation.
|
|
122
148
|
|
|
123
149
|
## What leaks
|
|
124
150
|
|
|
@@ -236,6 +236,9 @@ function validateScryptParams(params) {
|
|
|
236
236
|
if (params.N < N.min || params.N > N.max) {
|
|
237
237
|
throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}\u2013${N.max})`);
|
|
238
238
|
}
|
|
239
|
+
if ((params.N & params.N - 1) !== 0) {
|
|
240
|
+
throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);
|
|
241
|
+
}
|
|
239
242
|
if (params.r < r.min || params.r > r.max) {
|
|
240
243
|
throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}\u2013${r.max})`);
|
|
241
244
|
}
|
|
@@ -474,7 +477,7 @@ function gitAdd(repoRoot, files) {
|
|
|
474
477
|
}
|
|
475
478
|
function gitRmCached(repoRoot, files) {
|
|
476
479
|
if (files.length === 0) return;
|
|
477
|
-
execFileSync("git", ["rm", "--cached", "--", ...files], {
|
|
480
|
+
execFileSync("git", ["rm", "--cached", "--ignore-unmatch", "--", ...files], {
|
|
478
481
|
cwd: repoRoot,
|
|
479
482
|
stdio: ["pipe", "pipe", "pipe"]
|
|
480
483
|
});
|
|
@@ -666,4 +669,4 @@ export {
|
|
|
666
669
|
postMergeHook,
|
|
667
670
|
postRewriteHook
|
|
668
671
|
};
|
|
669
|
-
//# sourceMappingURL=chunk-
|
|
672
|
+
//# sourceMappingURL=chunk-KNJGGFI4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/git/hooks.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/types.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/git/password.ts","../src/git/utils.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { encrypt, decrypt } from '../encrypt.js';\nimport { resolvePassword } from './password.js';\nimport {\n findGitRoot,\n findMarkedDirs,\n getMdFilesInDir,\n getMdencFilesInDir,\n gitAdd,\n gitRmCached,\n isFileStaged,\n} from './utils.js';\n\nfunction needsReEncryption(mdPath: string, mdencPath: string): boolean {\n if (!existsSync(mdencPath)) return true;\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n return mdMtime > mdencMtime;\n}\n\nexport async function preCommitHook(): Promise<void> {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n\n if (!password) {\n process.stderr.write(\n 'mdenc: no password available (set MDENC_PASSWORD or create .mdenc-password). Skipping encryption.\\n',\n );\n process.exit(0);\n }\n\n const markedDirs = findMarkedDirs(repoRoot);\n if (markedDirs.length === 0) process.exit(0);\n\n let encryptedCount = 0;\n let skippedCount = 0;\n let errorCount = 0;\n\n for (const dir of markedDirs) {\n const mdFiles = getMdFilesInDir(dir);\n\n for (const mdFile of mdFiles) {\n const mdPath = join(dir, mdFile);\n const mdencPath = mdPath.replace(/\\.md$/, '.mdenc');\n const relMdPath = relative(repoRoot, mdPath);\n const relMdencPath = relative(repoRoot, mdencPath);\n\n // Skip if .md hasn't changed since last encryption\n if (!needsReEncryption(mdPath, mdencPath)) {\n skippedCount++;\n // Still ensure the .mdenc is staged if it exists\n if (existsSync(mdencPath)) {\n gitAdd(repoRoot, [relMdencPath]);\n }\n continue;\n }\n\n try {\n const plaintext = readFileSync(mdPath, 'utf-8');\n\n // Read existing .mdenc for previousFile optimization\n let previousFile: string | undefined;\n if (existsSync(mdencPath)) {\n previousFile = readFileSync(mdencPath, 'utf-8');\n }\n\n const encrypted = await encrypt(plaintext, password, { previousFile });\n writeFileSync(mdencPath, encrypted);\n gitAdd(repoRoot, [relMdencPath]);\n\n // Belt-and-suspenders: unstage .md if accidentally staged\n if (isFileStaged(repoRoot, relMdPath)) {\n gitRmCached(repoRoot, [relMdPath]);\n }\n\n encryptedCount++;\n } catch (err) {\n process.stderr.write(\n `mdenc: failed to encrypt ${relMdPath}: ${err instanceof Error ? err.message : err}\\n`,\n );\n errorCount++;\n }\n }\n }\n\n if (encryptedCount > 0) {\n process.stderr.write(`mdenc: encrypted ${encryptedCount} file(s)\\n`);\n }\n\n if (errorCount > 0) {\n process.stderr.write(\n `mdenc: ${errorCount} file(s) failed to encrypt. Aborting commit.\\n`,\n );\n process.exit(1);\n }\n\n process.exit(0);\n}\n\n/**\n * Decrypt all .mdenc files in marked directories.\n * Shared logic for post-checkout, post-merge, and post-rewrite hooks.\n * Returns the number of files decrypted.\n */\nexport async function decryptAll(): Promise<number> {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n\n if (!password) {\n process.stderr.write(\n 'mdenc: no password available. Skipping decryption.\\n',\n );\n return 0;\n }\n\n const markedDirs = findMarkedDirs(repoRoot);\n let count = 0;\n\n for (const dir of markedDirs) {\n const mdencFiles = getMdencFilesInDir(dir);\n\n for (const mdencFile of mdencFiles) {\n const mdencPath = join(dir, mdencFile);\n const mdPath = mdencPath.replace(/\\.mdenc$/, '.md');\n const relMdPath = relative(repoRoot, mdPath);\n\n // Overwrite protection: skip if .md is newer than .mdenc\n if (existsSync(mdPath)) {\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n if (mdMtime > mdencMtime) {\n process.stderr.write(\n `mdenc: skipping ${relMdPath} (local .md is newer than .mdenc)\\n`,\n );\n continue;\n }\n }\n\n try {\n const encrypted = readFileSync(mdencPath, 'utf-8');\n const plaintext = await decrypt(encrypted, password);\n writeFileSync(mdPath, plaintext);\n count++;\n } catch (err) {\n process.stderr.write(\n `mdenc: failed to decrypt ${relative(repoRoot, mdencPath)}: ${err instanceof Error ? err.message : err}\\n`,\n );\n }\n }\n }\n\n return count;\n}\n\nexport async function postCheckoutHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n\nexport async function postMergeHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n\nexport async function postRewriteHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst PASSWORD_FILE = '.mdenc-password';\n\n/**\n * Resolve password for non-interactive use (git hooks).\n * Checks MDENC_PASSWORD env var, then .mdenc-password file.\n * Returns null if neither is available — never prompts on TTY.\n */\nexport function resolvePassword(repoRoot: string): string | null {\n const envPassword = process.env['MDENC_PASSWORD'];\n if (envPassword) return envPassword;\n\n try {\n const content = readFileSync(join(repoRoot, PASSWORD_FILE), 'utf-8').trim();\n if (content.length > 0) return content;\n } catch {\n // File doesn't exist or unreadable\n }\n\n return null;\n}\n","import { execFileSync } from 'node:child_process';\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', '.hg', '.svn']);\nconst MARKER_FILE = '.mdenc.conf';\n\nexport function findGitRoot(): string {\n try {\n return execFileSync('git', ['rev-parse', '--show-toplevel'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n throw new Error('Not a git repository');\n }\n}\n\nexport function getHooksDir(): string {\n try {\n const gitDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return resolve(gitDir);\n } catch {\n throw new Error('Could not determine git hooks directory');\n }\n}\n\nexport function findMarkedDirs(repoRoot: string): string[] {\n const results: string[] = [];\n walkForMarker(repoRoot, results);\n return results;\n}\n\nfunction walkForMarker(dir: string, results: string[]): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n if (entries.includes(MARKER_FILE)) {\n results.push(dir);\n }\n\n for (const entry of entries) {\n if (SKIP_DIRS.has(entry) || entry.startsWith('.')) continue;\n const full = join(dir, entry);\n try {\n if (statSync(full).isDirectory()) {\n walkForMarker(full, results);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n}\n\nexport function getMdFilesInDir(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n f => f.endsWith('.md') && statSync(join(dir, f)).isFile(),\n );\n } catch {\n return [];\n }\n}\n\nexport function getMdencFilesInDir(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n f => f.endsWith('.mdenc') && statSync(join(dir, f)).isFile(),\n );\n } catch {\n return [];\n }\n}\n\nexport function gitAdd(repoRoot: string, files: string[]): void {\n if (files.length === 0) return;\n execFileSync('git', ['add', '--', ...files], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n}\n\nexport function gitRmCached(repoRoot: string, files: string[]): void {\n if (files.length === 0) return;\n execFileSync('git', ['rm', '--cached', '--', ...files], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n}\n\nexport function isFileStaged(repoRoot: string, file: string): boolean {\n try {\n const output = execFileSync(\n 'git',\n ['diff', '--cached', '--name-only', '--', file],\n { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n return output.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nexport function isFileTracked(repoRoot: string, file: string): boolean {\n try {\n execFileSync('git', ['ls-files', '--error-unmatch', '--', file], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["readFileSync","statSync","join","hmac","sha256","sha256","hmac","sha256","hmac","sha256","hmac","sha256","join","statSync","join","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/git/hooks.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/types.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/git/password.ts","../src/git/utils.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { encrypt, decrypt } from '../encrypt.js';\nimport { resolvePassword } from './password.js';\nimport {\n findGitRoot,\n findMarkedDirs,\n getMdFilesInDir,\n getMdencFilesInDir,\n gitAdd,\n gitRmCached,\n isFileStaged,\n} from './utils.js';\n\nfunction needsReEncryption(mdPath: string, mdencPath: string): boolean {\n if (!existsSync(mdencPath)) return true;\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n return mdMtime > mdencMtime;\n}\n\nexport async function preCommitHook(): Promise<void> {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n\n if (!password) {\n process.stderr.write(\n 'mdenc: no password available (set MDENC_PASSWORD or create .mdenc-password). Skipping encryption.\\n',\n );\n process.exit(0);\n }\n\n const markedDirs = findMarkedDirs(repoRoot);\n if (markedDirs.length === 0) process.exit(0);\n\n let encryptedCount = 0;\n let skippedCount = 0;\n let errorCount = 0;\n\n for (const dir of markedDirs) {\n const mdFiles = getMdFilesInDir(dir);\n\n for (const mdFile of mdFiles) {\n const mdPath = join(dir, mdFile);\n const mdencPath = mdPath.replace(/\\.md$/, '.mdenc');\n const relMdPath = relative(repoRoot, mdPath);\n const relMdencPath = relative(repoRoot, mdencPath);\n\n // Skip if .md hasn't changed since last encryption\n if (!needsReEncryption(mdPath, mdencPath)) {\n skippedCount++;\n // Still ensure the .mdenc is staged if it exists\n if (existsSync(mdencPath)) {\n gitAdd(repoRoot, [relMdencPath]);\n }\n continue;\n }\n\n try {\n const plaintext = readFileSync(mdPath, 'utf-8');\n\n // Read existing .mdenc for previousFile optimization\n let previousFile: string | undefined;\n if (existsSync(mdencPath)) {\n previousFile = readFileSync(mdencPath, 'utf-8');\n }\n\n const encrypted = await encrypt(plaintext, password, { previousFile });\n writeFileSync(mdencPath, encrypted);\n gitAdd(repoRoot, [relMdencPath]);\n\n // Belt-and-suspenders: unstage .md if accidentally staged\n if (isFileStaged(repoRoot, relMdPath)) {\n gitRmCached(repoRoot, [relMdPath]);\n }\n\n encryptedCount++;\n } catch (err) {\n process.stderr.write(\n `mdenc: failed to encrypt ${relMdPath}: ${err instanceof Error ? err.message : err}\\n`,\n );\n errorCount++;\n }\n }\n }\n\n if (encryptedCount > 0) {\n process.stderr.write(`mdenc: encrypted ${encryptedCount} file(s)\\n`);\n }\n\n if (errorCount > 0) {\n process.stderr.write(\n `mdenc: ${errorCount} file(s) failed to encrypt. Aborting commit.\\n`,\n );\n process.exit(1);\n }\n\n process.exit(0);\n}\n\n/**\n * Decrypt all .mdenc files in marked directories.\n * Shared logic for post-checkout, post-merge, and post-rewrite hooks.\n * Returns the number of files decrypted.\n */\nexport async function decryptAll(): Promise<number> {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n\n if (!password) {\n process.stderr.write(\n 'mdenc: no password available. Skipping decryption.\\n',\n );\n return 0;\n }\n\n const markedDirs = findMarkedDirs(repoRoot);\n let count = 0;\n\n for (const dir of markedDirs) {\n const mdencFiles = getMdencFilesInDir(dir);\n\n for (const mdencFile of mdencFiles) {\n const mdencPath = join(dir, mdencFile);\n const mdPath = mdencPath.replace(/\\.mdenc$/, '.md');\n const relMdPath = relative(repoRoot, mdPath);\n\n // Overwrite protection: skip if .md is newer than .mdenc\n if (existsSync(mdPath)) {\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n if (mdMtime > mdencMtime) {\n process.stderr.write(\n `mdenc: skipping ${relMdPath} (local .md is newer than .mdenc)\\n`,\n );\n continue;\n }\n }\n\n try {\n const encrypted = readFileSync(mdencPath, 'utf-8');\n const plaintext = await decrypt(encrypted, password);\n writeFileSync(mdPath, plaintext);\n count++;\n } catch (err) {\n process.stderr.write(\n `mdenc: failed to decrypt ${relative(repoRoot, mdencPath)}: ${err instanceof Error ? err.message : err}\\n`,\n );\n }\n }\n }\n\n return count;\n}\n\nexport async function postCheckoutHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n\nexport async function postMergeHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n\nexport async function postRewriteHook(): Promise<void> {\n const count = await decryptAll();\n if (count > 0) {\n process.stderr.write(`mdenc: decrypted ${count} file(s)\\n`);\n }\n process.exit(0);\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if ((params.N & (params.N - 1)) !== 0) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst PASSWORD_FILE = '.mdenc-password';\n\n/**\n * Resolve password for non-interactive use (git hooks).\n * Checks MDENC_PASSWORD env var, then .mdenc-password file.\n * Returns null if neither is available — never prompts on TTY.\n */\nexport function resolvePassword(repoRoot: string): string | null {\n const envPassword = process.env['MDENC_PASSWORD'];\n if (envPassword) return envPassword;\n\n try {\n const content = readFileSync(join(repoRoot, PASSWORD_FILE), 'utf-8').trim();\n if (content.length > 0) return content;\n } catch {\n // File doesn't exist or unreadable\n }\n\n return null;\n}\n","import { execFileSync } from 'node:child_process';\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', '.hg', '.svn']);\nconst MARKER_FILE = '.mdenc.conf';\n\nexport function findGitRoot(): string {\n try {\n return execFileSync('git', ['rev-parse', '--show-toplevel'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n throw new Error('Not a git repository');\n }\n}\n\nexport function getHooksDir(): string {\n try {\n const gitDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return resolve(gitDir);\n } catch {\n throw new Error('Could not determine git hooks directory');\n }\n}\n\nexport function findMarkedDirs(repoRoot: string): string[] {\n const results: string[] = [];\n walkForMarker(repoRoot, results);\n return results;\n}\n\nfunction walkForMarker(dir: string, results: string[]): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n if (entries.includes(MARKER_FILE)) {\n results.push(dir);\n }\n\n for (const entry of entries) {\n if (SKIP_DIRS.has(entry) || entry.startsWith('.')) continue;\n const full = join(dir, entry);\n try {\n if (statSync(full).isDirectory()) {\n walkForMarker(full, results);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n}\n\nexport function getMdFilesInDir(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n f => f.endsWith('.md') && statSync(join(dir, f)).isFile(),\n );\n } catch {\n return [];\n }\n}\n\nexport function getMdencFilesInDir(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n f => f.endsWith('.mdenc') && statSync(join(dir, f)).isFile(),\n );\n } catch {\n return [];\n }\n}\n\nexport function gitAdd(repoRoot: string, files: string[]): void {\n if (files.length === 0) return;\n execFileSync('git', ['add', '--', ...files], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n}\n\nexport function gitRmCached(repoRoot: string, files: string[]): void {\n if (files.length === 0) return;\n execFileSync('git', ['rm', '--cached', '--ignore-unmatch', '--', ...files], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n}\n\nexport function isFileStaged(repoRoot: string, file: string): boolean {\n try {\n const output = execFileSync(\n 'git',\n ['diff', '--cached', '--name-only', '--', file],\n { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n return output.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nexport function isFileTracked(repoRoot: string, file: string): boolean {\n try {\n execFileSync('git', ['ls-files', '--error-unmatch', '--', file], {\n cwd: repoRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["readFileSync","statSync","join","hmac","sha256","sha256","hmac","sha256","hmac","sha256","hmac","sha256","join","statSync","join","readFileSync"]}
|
package/dist/cli.js
CHANGED
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
resolvePassword,
|
|
23
23
|
verifyHeader,
|
|
24
24
|
zeroize
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-KNJGGFI4.js";
|
|
26
26
|
|
|
27
27
|
// src/cli.ts
|
|
28
|
-
import { readFileSync as
|
|
28
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
29
29
|
|
|
30
30
|
// src/seal.ts
|
|
31
31
|
import { hmac } from "@noble/hashes/hmac";
|
|
@@ -177,7 +177,7 @@ async function initCommand() {
|
|
|
177
177
|
writeFileSync(gitignorePath, entry + "\n");
|
|
178
178
|
console.log("Created .gitignore with .mdenc-password");
|
|
179
179
|
}
|
|
180
|
-
const { decryptAll } = await import("./hooks-
|
|
180
|
+
const { decryptAll } = await import("./hooks-FL46SI4A.js");
|
|
181
181
|
const count = await decryptAll();
|
|
182
182
|
if (count > 0) {
|
|
183
183
|
console.log(`Decrypted ${count} existing file(s)`);
|
|
@@ -372,6 +372,109 @@ function statusCommand() {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// src/git/watch.ts
|
|
376
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
|
|
377
|
+
import { join as join4, relative as relative3 } from "path";
|
|
378
|
+
import { watch } from "chokidar";
|
|
379
|
+
async function watchCommand() {
|
|
380
|
+
const repoRoot = findGitRoot();
|
|
381
|
+
const password = resolvePassword(repoRoot);
|
|
382
|
+
if (!password) {
|
|
383
|
+
console.error(
|
|
384
|
+
"mdenc: no password available (set MDENC_PASSWORD or create .mdenc-password)"
|
|
385
|
+
);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
const markedDirs = findMarkedDirs(repoRoot);
|
|
389
|
+
if (markedDirs.length === 0) {
|
|
390
|
+
console.error("mdenc: no marked directories found");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
for (const dir of markedDirs) {
|
|
394
|
+
const mdFiles = getMdFilesInDir(dir);
|
|
395
|
+
for (const mdFile of mdFiles) {
|
|
396
|
+
await encryptFile(dir, mdFile, repoRoot, password);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const pending = /* @__PURE__ */ new Set();
|
|
400
|
+
const watcher = watch(
|
|
401
|
+
markedDirs.map((dir) => join4(dir, "*.md")),
|
|
402
|
+
{ ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 100 } }
|
|
403
|
+
);
|
|
404
|
+
watcher.on("change", (filePath) => {
|
|
405
|
+
handleFileEvent(filePath, repoRoot, password, pending);
|
|
406
|
+
});
|
|
407
|
+
watcher.on("add", (filePath) => {
|
|
408
|
+
handleFileEvent(filePath, repoRoot, password, pending);
|
|
409
|
+
});
|
|
410
|
+
for (const dir of markedDirs) {
|
|
411
|
+
const relDir = relative3(repoRoot, dir) || ".";
|
|
412
|
+
console.error(`mdenc: watching ${relDir}/`);
|
|
413
|
+
}
|
|
414
|
+
console.error("mdenc: watching for changes (Ctrl+C to stop)");
|
|
415
|
+
}
|
|
416
|
+
function handleFileEvent(filePath, repoRoot, password, pending) {
|
|
417
|
+
if (pending.has(filePath)) return;
|
|
418
|
+
pending.add(filePath);
|
|
419
|
+
const dir = join4(filePath, "..");
|
|
420
|
+
const filename = filePath.slice(dir.length + 1);
|
|
421
|
+
encryptFile(dir, filename, repoRoot, password).finally(() => {
|
|
422
|
+
pending.delete(filePath);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
async function encryptFile(dir, mdFile, repoRoot, password) {
|
|
426
|
+
const mdPath = join4(dir, mdFile);
|
|
427
|
+
const mdencPath = mdPath.replace(/\.md$/, ".mdenc");
|
|
428
|
+
const relMdPath = relative3(repoRoot, mdPath);
|
|
429
|
+
if (!existsSync4(mdPath)) return;
|
|
430
|
+
try {
|
|
431
|
+
const plaintext = readFileSync4(mdPath, "utf-8");
|
|
432
|
+
let previousFile;
|
|
433
|
+
if (existsSync4(mdencPath)) {
|
|
434
|
+
previousFile = readFileSync4(mdencPath, "utf-8");
|
|
435
|
+
}
|
|
436
|
+
const encrypted = await encrypt(plaintext, password, { previousFile });
|
|
437
|
+
if (encrypted === previousFile) return;
|
|
438
|
+
writeFileSync3(mdencPath, encrypted);
|
|
439
|
+
console.error(`mdenc: encrypted ${relMdPath}`);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
console.error(
|
|
442
|
+
`mdenc: failed to encrypt ${relMdPath}: ${err instanceof Error ? err.message : err}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/git/genpass.ts
|
|
448
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
449
|
+
import { join as join5 } from "path";
|
|
450
|
+
import { randomBytes } from "@noble/ciphers/webcrypto";
|
|
451
|
+
var PASSWORD_FILE = ".mdenc-password";
|
|
452
|
+
function genpassCommand(force) {
|
|
453
|
+
const repoRoot = findGitRoot();
|
|
454
|
+
const passwordPath = join5(repoRoot, PASSWORD_FILE);
|
|
455
|
+
if (existsSync5(passwordPath) && !force) {
|
|
456
|
+
console.error(`${PASSWORD_FILE} already exists. Use --force to overwrite.`);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
const password = Buffer.from(randomBytes(32)).toString("base64url");
|
|
460
|
+
writeFileSync4(passwordPath, password + "\n", { mode: 384 });
|
|
461
|
+
console.error(`Generated password and wrote to ${PASSWORD_FILE}`);
|
|
462
|
+
console.error(password);
|
|
463
|
+
const gitignorePath = join5(repoRoot, ".gitignore");
|
|
464
|
+
const entry = PASSWORD_FILE;
|
|
465
|
+
if (existsSync5(gitignorePath)) {
|
|
466
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
467
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
468
|
+
if (!lines.includes(entry)) {
|
|
469
|
+
writeFileSync4(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
470
|
+
console.error("Added .mdenc-password to .gitignore");
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
writeFileSync4(gitignorePath, entry + "\n");
|
|
474
|
+
console.error("Created .gitignore with .mdenc-password");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
375
478
|
// src/cli.ts
|
|
376
479
|
function readPasswordFromTTY(prompt) {
|
|
377
480
|
return new Promise((resolve2) => {
|
|
@@ -451,7 +554,9 @@ Git integration:
|
|
|
451
554
|
mdenc init Set up git hooks for automatic encryption
|
|
452
555
|
mdenc mark <directory> Mark a directory for encryption
|
|
453
556
|
mdenc status Show encryption status
|
|
454
|
-
mdenc
|
|
557
|
+
mdenc watch Watch and encrypt on file changes
|
|
558
|
+
mdenc remove-hooks Remove mdenc git hooks
|
|
559
|
+
mdenc genpass [--force] Generate a random password into .mdenc-password`);
|
|
455
560
|
process.exit(1);
|
|
456
561
|
}
|
|
457
562
|
async function main() {
|
|
@@ -466,10 +571,10 @@ async function main() {
|
|
|
466
571
|
const outputIdx = args.indexOf("-o");
|
|
467
572
|
const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : void 0;
|
|
468
573
|
const password = await getPasswordWithConfirmation();
|
|
469
|
-
const plaintext =
|
|
574
|
+
const plaintext = readFileSync6(inputFile, "utf-8");
|
|
470
575
|
const encrypted = await encrypt(plaintext, password);
|
|
471
576
|
if (outputFile) {
|
|
472
|
-
|
|
577
|
+
writeFileSync5(outputFile, encrypted);
|
|
473
578
|
} else {
|
|
474
579
|
process.stdout.write(encrypted);
|
|
475
580
|
}
|
|
@@ -481,10 +586,10 @@ async function main() {
|
|
|
481
586
|
const outputIdx = args.indexOf("-o");
|
|
482
587
|
const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : void 0;
|
|
483
588
|
const password = await getPassword();
|
|
484
|
-
const fileContent =
|
|
589
|
+
const fileContent = readFileSync6(inputFile, "utf-8");
|
|
485
590
|
const decrypted = await decrypt(fileContent, password);
|
|
486
591
|
if (outputFile) {
|
|
487
|
-
|
|
592
|
+
writeFileSync5(outputFile, decrypted);
|
|
488
593
|
} else {
|
|
489
594
|
process.stdout.write(decrypted);
|
|
490
595
|
}
|
|
@@ -494,7 +599,7 @@ async function main() {
|
|
|
494
599
|
if (!args[1]) usage();
|
|
495
600
|
const inputFile = args[1];
|
|
496
601
|
const password = await getPassword();
|
|
497
|
-
const fileContent =
|
|
602
|
+
const fileContent = readFileSync6(inputFile, "utf-8");
|
|
498
603
|
const valid = await verifySeal(fileContent, password);
|
|
499
604
|
if (valid) {
|
|
500
605
|
console.error("Seal verified: OK");
|
|
@@ -519,9 +624,15 @@ async function main() {
|
|
|
519
624
|
case "status":
|
|
520
625
|
statusCommand();
|
|
521
626
|
break;
|
|
627
|
+
case "watch":
|
|
628
|
+
await watchCommand();
|
|
629
|
+
break;
|
|
522
630
|
case "remove-hooks":
|
|
523
631
|
removeHooksCommand();
|
|
524
632
|
break;
|
|
633
|
+
case "genpass":
|
|
634
|
+
genpassCommand(args.includes("--force"));
|
|
635
|
+
break;
|
|
525
636
|
// Git hook handlers (called by hook scripts, not directly by user)
|
|
526
637
|
case "pre-commit":
|
|
527
638
|
await preCommitHook();
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/seal.ts","../src/git/init.ts","../src/git/mark.ts","../src/git/status.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { encrypt, decrypt } from './encrypt.js';\nimport { verifySeal } from './seal.js';\nimport { initCommand, removeHooksCommand } from './git/init.js';\nimport { markCommand } from './git/mark.js';\nimport { statusCommand } from './git/status.js';\nimport {\n preCommitHook,\n postCheckoutHook,\n postMergeHook,\n postRewriteHook,\n} from './git/hooks.js';\n\nfunction readPasswordFromTTY(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n if (!process.stdin.isTTY) {\n // Non-TTY fallback: read a line from stdin\n process.stderr.write(prompt);\n let data = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n const nlIdx = chunk.indexOf('\\n');\n if (nlIdx >= 0) {\n data += chunk.slice(0, nlIdx);\n process.stdin.pause();\n resolve(data);\n } else {\n data += chunk;\n }\n });\n process.stdin.on('end', () => resolve(data));\n process.stdin.resume();\n return;\n }\n\n process.stderr.write(prompt);\n const buf: string[] = [];\n\n process.stdin.setRawMode(true);\n process.stdin.setEncoding('utf-8');\n process.stdin.resume();\n\n const onData = (ch: string) => {\n if (ch === '\\u0003') {\n // Ctrl+C\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n process.exit(130);\n } else if (ch === '\\r' || ch === '\\n') {\n // Enter\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n resolve(buf.join(''));\n } else if (ch === '\\u007F' || ch === '\\b') {\n // Backspace\n buf.pop();\n } else if (ch === '\\u0004') {\n // Ctrl+D (EOF)\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n resolve(buf.join(''));\n } else if (ch >= ' ') {\n buf.push(ch);\n }\n };\n\n process.stdin.on('data', onData);\n });\n}\n\nasync function getPassword(prompt = 'Password: '): Promise<string> {\n const envPassword = process.env['MDENC_PASSWORD'];\n if (envPassword) return envPassword;\n\n return readPasswordFromTTY(prompt);\n}\n\nasync function getPasswordWithConfirmation(): Promise<string> {\n if (process.env['MDENC_PASSWORD']) return process.env['MDENC_PASSWORD'];\n\n const password = await readPasswordFromTTY('Password: ');\n const confirm = await readPasswordFromTTY('Confirm password: ');\n if (password !== confirm) {\n console.error('Error: passwords do not match');\n process.exit(1);\n }\n return password;\n}\n\nfunction usage(): never {\n console.error(`Usage:\n mdenc encrypt <file> [-o output] Encrypt a markdown file\n mdenc decrypt <file> [-o output] Decrypt an mdenc file\n mdenc verify <file> Verify file integrity\n\nGit integration:\n mdenc init Set up git hooks for automatic encryption\n mdenc mark <directory> Mark a directory for encryption\n mdenc status Show encryption status\n mdenc remove-hooks Remove mdenc git hooks`);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n\n if (args.length === 0) usage();\n\n const command = args[0];\n\n try {\n switch (command) {\n case 'encrypt': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const outputIdx = args.indexOf('-o');\n const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : undefined;\n const password = await getPasswordWithConfirmation();\n const plaintext = readFileSync(inputFile, 'utf-8');\n const encrypted = await encrypt(plaintext, password);\n if (outputFile) {\n writeFileSync(outputFile, encrypted);\n } else {\n process.stdout.write(encrypted);\n }\n break;\n }\n\n case 'decrypt': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const outputIdx = args.indexOf('-o');\n const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : undefined;\n const password = await getPassword();\n const fileContent = readFileSync(inputFile, 'utf-8');\n const decrypted = await decrypt(fileContent, password);\n if (outputFile) {\n writeFileSync(outputFile, decrypted);\n } else {\n process.stdout.write(decrypted);\n }\n break;\n }\n\n case 'verify': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const password = await getPassword();\n const fileContent = readFileSync(inputFile, 'utf-8');\n const valid = await verifySeal(fileContent, password);\n if (valid) {\n console.error('Seal verified: OK');\n process.exit(0);\n } else {\n console.error('Seal verification FAILED');\n process.exit(1);\n }\n break;\n }\n\n case 'init':\n await initCommand();\n break;\n\n case 'mark': {\n if (!args[1]) {\n console.error('Usage: mdenc mark <directory>');\n process.exit(1);\n }\n markCommand(args[1]);\n break;\n }\n\n case 'status':\n statusCommand();\n break;\n\n case 'remove-hooks':\n removeHooksCommand();\n break;\n\n // Git hook handlers (called by hook scripts, not directly by user)\n case 'pre-commit':\n await preCommitHook();\n break;\n\n case 'post-checkout':\n await postCheckoutHook();\n break;\n\n case 'post-merge':\n await postMergeHook();\n break;\n\n case 'post-rewrite':\n await postRewriteHook();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n usage();\n }\n } catch (err) {\n console.error(`Error: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\nmain();\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n","import { readFileSync, writeFileSync, existsSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { findGitRoot, getHooksDir } from './utils.js';\n\nconst MARKER = '# mdenc-hook-marker';\n\nconst HOOK_NAMES = [\n 'pre-commit',\n 'post-checkout',\n 'post-merge',\n 'post-rewrite',\n] as const;\n\nfunction hookBlock(hookName: string): string {\n return `\n${MARKER}\nif command -v mdenc >/dev/null 2>&1; then\n mdenc ${hookName}\nelif [ -x \"./node_modules/.bin/mdenc\" ]; then\n ./node_modules/.bin/mdenc ${hookName}\nelse\n echo \"mdenc: not found, skipping ${hookName} hook\" >&2\nfi`;\n}\n\nfunction newHookScript(hookName: string): string {\n return `#!/bin/sh${hookBlock(hookName)}\n`;\n}\n\nfunction isBinary(content: string): boolean {\n return content.slice(0, 512).includes('\\0');\n}\n\nfunction hasShellShebang(content: string): boolean {\n const firstLine = content.split('\\n')[0];\n return /^#!.*\\b(sh|bash|zsh|dash)\\b/.test(firstLine);\n}\n\nfunction looksLikeFrameworkHook(content: string): boolean {\n // Detect husky-style hooks that source/exec another script as their main logic\n const lines = content.split('\\n').filter(l => l.trim() && !l.startsWith('#'));\n if (lines.length <= 2) {\n // Very short hook that sources or execs something else\n return lines.some(l => /^\\.\\s+\"/.test(l.trim()) || /^exec\\s+/.test(l.trim()));\n }\n return false;\n}\n\nfunction printManualInstructions(hookName: string): void {\n process.stderr.write(\n `mdenc: ${hookName} hook exists but has an unrecognized format.\\n` +\n ` Add the following to your hook manually:\\n\\n` +\n ` ${MARKER}\\n` +\n ` if command -v mdenc >/dev/null 2>&1; then\\n` +\n ` mdenc ${hookName}\\n` +\n ` elif [ -x \"./node_modules/.bin/mdenc\" ]; then\\n` +\n ` ./node_modules/.bin/mdenc ${hookName}\\n` +\n ` fi\\n\\n`,\n );\n}\n\nexport async function initCommand(): Promise<void> {\n const repoRoot = findGitRoot();\n const hooksDir = getHooksDir();\n\n for (const hookName of HOOK_NAMES) {\n const hookPath = join(hooksDir, hookName);\n\n if (!existsSync(hookPath)) {\n writeFileSync(hookPath, newHookScript(hookName));\n chmodSync(hookPath, 0o755);\n console.log(`Installed ${hookName} hook`);\n continue;\n }\n\n const content = readFileSync(hookPath, 'utf-8');\n\n if (content.includes(MARKER)) {\n console.log(`${hookName} hook already installed (skipped)`);\n continue;\n }\n\n // Safety checks\n if (isBinary(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook appears to be a binary file. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n if (!hasShellShebang(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook has no shell shebang. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n if (looksLikeFrameworkHook(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook appears to be managed by a framework. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n // Safe to append\n writeFileSync(hookPath, content.trimEnd() + '\\n' + hookBlock(hookName) + '\\n');\n chmodSync(hookPath, 0o755);\n console.log(`Appended mdenc to existing ${hookName} hook`);\n }\n\n // Add .mdenc-password to root .gitignore\n const gitignorePath = join(repoRoot, '.gitignore');\n const entry = '.mdenc-password';\n\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim());\n if (!lines.includes(entry)) {\n writeFileSync(gitignorePath, content.trimEnd() + '\\n' + entry + '\\n');\n console.log('Added .mdenc-password to .gitignore');\n } else {\n console.log('.mdenc-password already in .gitignore (skipped)');\n }\n } else {\n writeFileSync(gitignorePath, entry + '\\n');\n console.log('Created .gitignore with .mdenc-password');\n }\n\n // Decrypt existing .mdenc files\n const { decryptAll } = await import('./hooks.js');\n const count = await decryptAll();\n if (count > 0) {\n console.log(`Decrypted ${count} existing file(s)`);\n }\n\n console.log('mdenc git integration initialized.');\n}\n\nexport function removeHooksCommand(): void {\n const hooksDir = getHooksDir();\n let removedCount = 0;\n\n for (const hookName of HOOK_NAMES) {\n const hookPath = join(hooksDir, hookName);\n\n if (!existsSync(hookPath)) continue;\n\n const content = readFileSync(hookPath, 'utf-8');\n if (!content.includes(MARKER)) {\n console.log(`${hookName}: no mdenc block found (skipped)`);\n continue;\n }\n\n // Remove the mdenc block: from the marker line through the matching fi\n const lines = content.split('\\n');\n const filtered: string[] = [];\n let inBlock = false;\n\n for (const line of lines) {\n if (line.trim() === MARKER) {\n inBlock = true;\n continue;\n }\n if (inBlock) {\n if (line.trim() === 'fi') {\n inBlock = false;\n continue;\n }\n continue;\n }\n filtered.push(line);\n }\n\n const result = filtered.join('\\n');\n const isEmpty = result.split('\\n').every(l => l.trim() === '' || l.startsWith('#!'));\n\n if (isEmpty) {\n unlinkSync(hookPath);\n console.log(`Removed ${hookName} hook (was mdenc-only)`);\n } else {\n writeFileSync(hookPath, result);\n console.log(`Removed mdenc block from ${hookName} hook`);\n }\n\n removedCount++;\n }\n\n if (removedCount === 0) {\n console.log('No mdenc hooks found to remove.');\n } else {\n console.log('mdenc hooks removed.');\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport {\n findGitRoot,\n getMdFilesInDir,\n gitAdd,\n gitRmCached,\n isFileTracked,\n} from './utils.js';\n\nconst MARKER_FILE = '.mdenc.conf';\nconst MARKER_CONTENT = '# mdenc: .md files in this directory are automatically encrypted\\n';\nconst GITIGNORE_PATTERN = '*.md';\n\nexport function markCommand(dirArg: string): void {\n const repoRoot = findGitRoot();\n const dir = resolve(dirArg);\n\n if (!existsSync(dir)) {\n console.error(`Error: directory \"${dirArg}\" does not exist`);\n process.exit(1);\n }\n\n const rel = relative(repoRoot, dir);\n if (rel.startsWith('..')) {\n console.error(`Error: directory \"${dirArg}\" is outside the git repository`);\n process.exit(1);\n }\n\n const relDir = rel || '.';\n\n // Create .mdenc.conf\n const confPath = join(dir, MARKER_FILE);\n if (!existsSync(confPath)) {\n writeFileSync(confPath, MARKER_CONTENT);\n console.log(`Created ${relDir}/${MARKER_FILE}`);\n } else {\n console.log(`${relDir}/${MARKER_FILE} already exists (skipped)`);\n }\n\n // Create/update .gitignore\n const gitignorePath = join(dir, '.gitignore');\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim());\n if (!lines.includes(GITIGNORE_PATTERN)) {\n writeFileSync(gitignorePath, content.trimEnd() + '\\n' + GITIGNORE_PATTERN + '\\n');\n console.log(`Updated ${relDir}/.gitignore (added ${GITIGNORE_PATTERN})`);\n } else {\n console.log(`${relDir}/.gitignore already has ${GITIGNORE_PATTERN} (skipped)`);\n }\n } else {\n writeFileSync(gitignorePath, GITIGNORE_PATTERN + '\\n');\n console.log(`Created ${relDir}/.gitignore with ${GITIGNORE_PATTERN}`);\n }\n\n // Untrack any currently-tracked .md files\n const mdFiles = getMdFilesInDir(dir);\n const trackedMd: string[] = [];\n for (const f of mdFiles) {\n const relPath = relative(repoRoot, join(dir, f));\n if (isFileTracked(repoRoot, relPath)) {\n trackedMd.push(relPath);\n }\n }\n\n if (trackedMd.length > 0) {\n gitRmCached(repoRoot, trackedMd);\n for (const f of trackedMd) {\n console.log(`Untracked ${f} from git (still exists locally)`);\n }\n }\n\n // Stage .mdenc.conf and .gitignore\n const toStage = [\n relative(repoRoot, confPath),\n relative(repoRoot, gitignorePath),\n ];\n gitAdd(repoRoot, toStage);\n\n console.log(`Marked ${relDir}/ for mdenc encryption`);\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { resolvePassword } from './password.js';\nimport {\n findGitRoot,\n findMarkedDirs,\n getHooksDir,\n getMdFilesInDir,\n getMdencFilesInDir,\n} from './utils.js';\n\nconst HOOK_NAMES = ['pre-commit', 'post-checkout', 'post-merge', 'post-rewrite'];\nconst MARKER = '# mdenc-hook-marker';\n\nexport function statusCommand(): void {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n const markedDirs = findMarkedDirs(repoRoot);\n\n if (markedDirs.length === 0) {\n console.log('No directories marked for mdenc encryption.');\n console.log('Use \"mdenc mark <directory>\" to designate a directory.');\n } else {\n console.log('Marked directories:\\n');\n\n for (const dir of markedDirs) {\n const relDir = relative(repoRoot, dir) || '.';\n console.log(` ${relDir}/`);\n\n const mdFiles = getMdFilesInDir(dir);\n const mdencFiles = getMdencFilesInDir(dir);\n\n const mdBases = new Set(mdFiles.map(f => f.replace(/\\.md$/, '')));\n const mdencBases = new Set(mdencFiles.map(f => f.replace(/\\.mdenc$/, '')));\n\n // Paired files (both .md and .mdenc exist)\n for (const base of mdBases) {\n if (mdencBases.has(base)) {\n const mdPath = join(dir, `${base}.md`);\n const mdencPath = join(dir, `${base}.mdenc`);\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n\n if (mdMtime > mdencMtime) {\n console.log(` ${base}.md [needs re-encryption]`);\n } else {\n console.log(` ${base}.md [up to date]`);\n }\n } else {\n console.log(` ${base}.md [not yet encrypted]`);\n }\n }\n\n // Orphaned .mdenc files (no corresponding .md)\n for (const base of mdencBases) {\n if (!mdBases.has(base)) {\n console.log(` ${base}.mdenc [needs decryption]`);\n }\n }\n\n // Check .gitignore health\n const gitignorePath = join(dir, '.gitignore');\n if (!existsSync(gitignorePath)) {\n console.log(` WARNING: no .gitignore in this directory`);\n } else {\n const content = readFileSync(gitignorePath, 'utf-8');\n if (!content.split('\\n').some(l => l.trim() === '*.md')) {\n console.log(` WARNING: .gitignore missing *.md pattern`);\n }\n }\n\n console.log();\n }\n }\n\n // Password status\n if (!password) {\n console.log('Password: NOT AVAILABLE');\n console.log(' Set MDENC_PASSWORD env var or create .mdenc-password file');\n } else {\n console.log('Password: available');\n }\n\n // Hook status\n const hooksDir = getHooksDir();\n const missing: string[] = [];\n for (const name of HOOK_NAMES) {\n const hookPath = join(hooksDir, name);\n if (!existsSync(hookPath)) {\n missing.push(name);\n } else {\n const content = readFileSync(hookPath, 'utf-8');\n if (!content.includes(MARKER)) {\n missing.push(name);\n }\n }\n }\n\n if (missing.length > 0) {\n console.log(`Hooks: MISSING (${missing.join(', ')})`);\n console.log(' Run \"mdenc init\" to install hooks');\n } else {\n console.log('Hooks: all installed');\n }\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","readFileSync","writeFileSync","join","existsSync","join","writeFileSync","readFileSync","existsSync","readFileSync","join","relative","HOOK_NAMES","MARKER","relative","join","existsSync","readFileSync","resolve","readFileSync","writeFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/seal.ts","../src/git/init.ts","../src/git/mark.ts","../src/git/status.ts","../src/git/watch.ts","../src/git/genpass.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { encrypt, decrypt } from './encrypt.js';\nimport { verifySeal } from './seal.js';\nimport { initCommand, removeHooksCommand } from './git/init.js';\nimport { markCommand } from './git/mark.js';\nimport { statusCommand } from './git/status.js';\nimport { watchCommand } from './git/watch.js';\nimport { genpassCommand } from './git/genpass.js';\nimport {\n preCommitHook,\n postCheckoutHook,\n postMergeHook,\n postRewriteHook,\n} from './git/hooks.js';\n\nfunction readPasswordFromTTY(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n if (!process.stdin.isTTY) {\n // Non-TTY fallback: read a line from stdin\n process.stderr.write(prompt);\n let data = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n const nlIdx = chunk.indexOf('\\n');\n if (nlIdx >= 0) {\n data += chunk.slice(0, nlIdx);\n process.stdin.pause();\n resolve(data);\n } else {\n data += chunk;\n }\n });\n process.stdin.on('end', () => resolve(data));\n process.stdin.resume();\n return;\n }\n\n process.stderr.write(prompt);\n const buf: string[] = [];\n\n process.stdin.setRawMode(true);\n process.stdin.setEncoding('utf-8');\n process.stdin.resume();\n\n const onData = (ch: string) => {\n if (ch === '\\u0003') {\n // Ctrl+C\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n process.exit(130);\n } else if (ch === '\\r' || ch === '\\n') {\n // Enter\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n resolve(buf.join(''));\n } else if (ch === '\\u007F' || ch === '\\b') {\n // Backspace\n buf.pop();\n } else if (ch === '\\u0004') {\n // Ctrl+D (EOF)\n process.stderr.write('\\n');\n process.stdin.setRawMode(false);\n process.stdin.pause();\n process.stdin.removeListener('data', onData);\n resolve(buf.join(''));\n } else if (ch >= ' ') {\n buf.push(ch);\n }\n };\n\n process.stdin.on('data', onData);\n });\n}\n\nasync function getPassword(prompt = 'Password: '): Promise<string> {\n const envPassword = process.env['MDENC_PASSWORD'];\n if (envPassword) return envPassword;\n\n return readPasswordFromTTY(prompt);\n}\n\nasync function getPasswordWithConfirmation(): Promise<string> {\n if (process.env['MDENC_PASSWORD']) return process.env['MDENC_PASSWORD'];\n\n const password = await readPasswordFromTTY('Password: ');\n const confirm = await readPasswordFromTTY('Confirm password: ');\n if (password !== confirm) {\n console.error('Error: passwords do not match');\n process.exit(1);\n }\n return password;\n}\n\nfunction usage(): never {\n console.error(`Usage:\n mdenc encrypt <file> [-o output] Encrypt a markdown file\n mdenc decrypt <file> [-o output] Decrypt an mdenc file\n mdenc verify <file> Verify file integrity\n\nGit integration:\n mdenc init Set up git hooks for automatic encryption\n mdenc mark <directory> Mark a directory for encryption\n mdenc status Show encryption status\n mdenc watch Watch and encrypt on file changes\n mdenc remove-hooks Remove mdenc git hooks\n mdenc genpass [--force] Generate a random password into .mdenc-password`);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n\n if (args.length === 0) usage();\n\n const command = args[0];\n\n try {\n switch (command) {\n case 'encrypt': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const outputIdx = args.indexOf('-o');\n const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : undefined;\n const password = await getPasswordWithConfirmation();\n const plaintext = readFileSync(inputFile, 'utf-8');\n const encrypted = await encrypt(plaintext, password);\n if (outputFile) {\n writeFileSync(outputFile, encrypted);\n } else {\n process.stdout.write(encrypted);\n }\n break;\n }\n\n case 'decrypt': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const outputIdx = args.indexOf('-o');\n const outputFile = outputIdx >= 0 ? args[outputIdx + 1] : undefined;\n const password = await getPassword();\n const fileContent = readFileSync(inputFile, 'utf-8');\n const decrypted = await decrypt(fileContent, password);\n if (outputFile) {\n writeFileSync(outputFile, decrypted);\n } else {\n process.stdout.write(decrypted);\n }\n break;\n }\n\n case 'verify': {\n if (!args[1]) usage();\n const inputFile = args[1];\n const password = await getPassword();\n const fileContent = readFileSync(inputFile, 'utf-8');\n const valid = await verifySeal(fileContent, password);\n if (valid) {\n console.error('Seal verified: OK');\n process.exit(0);\n } else {\n console.error('Seal verification FAILED');\n process.exit(1);\n }\n break;\n }\n\n case 'init':\n await initCommand();\n break;\n\n case 'mark': {\n if (!args[1]) {\n console.error('Usage: mdenc mark <directory>');\n process.exit(1);\n }\n markCommand(args[1]);\n break;\n }\n\n case 'status':\n statusCommand();\n break;\n\n case 'watch':\n await watchCommand();\n break;\n\n case 'remove-hooks':\n removeHooksCommand();\n break;\n\n case 'genpass':\n genpassCommand(args.includes('--force'));\n break;\n\n // Git hook handlers (called by hook scripts, not directly by user)\n case 'pre-commit':\n await preCommitHook();\n break;\n\n case 'post-checkout':\n await postCheckoutHook();\n break;\n\n case 'post-merge':\n await postMergeHook();\n break;\n\n case 'post-rewrite':\n await postRewriteHook();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n usage();\n }\n } catch (err) {\n console.error(`Error: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\nmain();\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n","import { readFileSync, writeFileSync, existsSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { findGitRoot, getHooksDir } from './utils.js';\n\nconst MARKER = '# mdenc-hook-marker';\n\nconst HOOK_NAMES = [\n 'pre-commit',\n 'post-checkout',\n 'post-merge',\n 'post-rewrite',\n] as const;\n\nfunction hookBlock(hookName: string): string {\n return `\n${MARKER}\nif command -v mdenc >/dev/null 2>&1; then\n mdenc ${hookName}\nelif [ -x \"./node_modules/.bin/mdenc\" ]; then\n ./node_modules/.bin/mdenc ${hookName}\nelse\n echo \"mdenc: not found, skipping ${hookName} hook\" >&2\nfi`;\n}\n\nfunction newHookScript(hookName: string): string {\n return `#!/bin/sh${hookBlock(hookName)}\n`;\n}\n\nfunction isBinary(content: string): boolean {\n return content.slice(0, 512).includes('\\0');\n}\n\nfunction hasShellShebang(content: string): boolean {\n const firstLine = content.split('\\n')[0];\n return /^#!.*\\b(sh|bash|zsh|dash)\\b/.test(firstLine);\n}\n\nfunction looksLikeFrameworkHook(content: string): boolean {\n // Detect husky-style hooks that source/exec another script as their main logic\n const lines = content.split('\\n').filter(l => l.trim() && !l.startsWith('#'));\n if (lines.length <= 2) {\n // Very short hook that sources or execs something else\n return lines.some(l => /^\\.\\s+\"/.test(l.trim()) || /^exec\\s+/.test(l.trim()));\n }\n return false;\n}\n\nfunction printManualInstructions(hookName: string): void {\n process.stderr.write(\n `mdenc: ${hookName} hook exists but has an unrecognized format.\\n` +\n ` Add the following to your hook manually:\\n\\n` +\n ` ${MARKER}\\n` +\n ` if command -v mdenc >/dev/null 2>&1; then\\n` +\n ` mdenc ${hookName}\\n` +\n ` elif [ -x \"./node_modules/.bin/mdenc\" ]; then\\n` +\n ` ./node_modules/.bin/mdenc ${hookName}\\n` +\n ` fi\\n\\n`,\n );\n}\n\nexport async function initCommand(): Promise<void> {\n const repoRoot = findGitRoot();\n const hooksDir = getHooksDir();\n\n for (const hookName of HOOK_NAMES) {\n const hookPath = join(hooksDir, hookName);\n\n if (!existsSync(hookPath)) {\n writeFileSync(hookPath, newHookScript(hookName));\n chmodSync(hookPath, 0o755);\n console.log(`Installed ${hookName} hook`);\n continue;\n }\n\n const content = readFileSync(hookPath, 'utf-8');\n\n if (content.includes(MARKER)) {\n console.log(`${hookName} hook already installed (skipped)`);\n continue;\n }\n\n // Safety checks\n if (isBinary(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook appears to be a binary file. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n if (!hasShellShebang(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook has no shell shebang. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n if (looksLikeFrameworkHook(content)) {\n process.stderr.write(\n `mdenc: ${hookName} hook appears to be managed by a framework. Skipping.\\n`,\n );\n printManualInstructions(hookName);\n continue;\n }\n\n // Safe to append\n writeFileSync(hookPath, content.trimEnd() + '\\n' + hookBlock(hookName) + '\\n');\n chmodSync(hookPath, 0o755);\n console.log(`Appended mdenc to existing ${hookName} hook`);\n }\n\n // Add .mdenc-password to root .gitignore\n const gitignorePath = join(repoRoot, '.gitignore');\n const entry = '.mdenc-password';\n\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim());\n if (!lines.includes(entry)) {\n writeFileSync(gitignorePath, content.trimEnd() + '\\n' + entry + '\\n');\n console.log('Added .mdenc-password to .gitignore');\n } else {\n console.log('.mdenc-password already in .gitignore (skipped)');\n }\n } else {\n writeFileSync(gitignorePath, entry + '\\n');\n console.log('Created .gitignore with .mdenc-password');\n }\n\n // Decrypt existing .mdenc files\n const { decryptAll } = await import('./hooks.js');\n const count = await decryptAll();\n if (count > 0) {\n console.log(`Decrypted ${count} existing file(s)`);\n }\n\n console.log('mdenc git integration initialized.');\n}\n\nexport function removeHooksCommand(): void {\n const hooksDir = getHooksDir();\n let removedCount = 0;\n\n for (const hookName of HOOK_NAMES) {\n const hookPath = join(hooksDir, hookName);\n\n if (!existsSync(hookPath)) continue;\n\n const content = readFileSync(hookPath, 'utf-8');\n if (!content.includes(MARKER)) {\n console.log(`${hookName}: no mdenc block found (skipped)`);\n continue;\n }\n\n // Remove the mdenc block: from the marker line through the matching fi\n const lines = content.split('\\n');\n const filtered: string[] = [];\n let inBlock = false;\n\n for (const line of lines) {\n if (line.trim() === MARKER) {\n inBlock = true;\n continue;\n }\n if (inBlock) {\n if (line.trim() === 'fi') {\n inBlock = false;\n continue;\n }\n continue;\n }\n filtered.push(line);\n }\n\n const result = filtered.join('\\n');\n const isEmpty = result.split('\\n').every(l => l.trim() === '' || l.startsWith('#!'));\n\n if (isEmpty) {\n unlinkSync(hookPath);\n console.log(`Removed ${hookName} hook (was mdenc-only)`);\n } else {\n writeFileSync(hookPath, result);\n console.log(`Removed mdenc block from ${hookName} hook`);\n }\n\n removedCount++;\n }\n\n if (removedCount === 0) {\n console.log('No mdenc hooks found to remove.');\n } else {\n console.log('mdenc hooks removed.');\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport {\n findGitRoot,\n getMdFilesInDir,\n gitAdd,\n gitRmCached,\n isFileTracked,\n} from './utils.js';\n\nconst MARKER_FILE = '.mdenc.conf';\nconst MARKER_CONTENT = '# mdenc: .md files in this directory are automatically encrypted\\n';\nconst GITIGNORE_PATTERN = '*.md';\n\nexport function markCommand(dirArg: string): void {\n const repoRoot = findGitRoot();\n const dir = resolve(dirArg);\n\n if (!existsSync(dir)) {\n console.error(`Error: directory \"${dirArg}\" does not exist`);\n process.exit(1);\n }\n\n const rel = relative(repoRoot, dir);\n if (rel.startsWith('..')) {\n console.error(`Error: directory \"${dirArg}\" is outside the git repository`);\n process.exit(1);\n }\n\n const relDir = rel || '.';\n\n // Create .mdenc.conf\n const confPath = join(dir, MARKER_FILE);\n if (!existsSync(confPath)) {\n writeFileSync(confPath, MARKER_CONTENT);\n console.log(`Created ${relDir}/${MARKER_FILE}`);\n } else {\n console.log(`${relDir}/${MARKER_FILE} already exists (skipped)`);\n }\n\n // Create/update .gitignore\n const gitignorePath = join(dir, '.gitignore');\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim());\n if (!lines.includes(GITIGNORE_PATTERN)) {\n writeFileSync(gitignorePath, content.trimEnd() + '\\n' + GITIGNORE_PATTERN + '\\n');\n console.log(`Updated ${relDir}/.gitignore (added ${GITIGNORE_PATTERN})`);\n } else {\n console.log(`${relDir}/.gitignore already has ${GITIGNORE_PATTERN} (skipped)`);\n }\n } else {\n writeFileSync(gitignorePath, GITIGNORE_PATTERN + '\\n');\n console.log(`Created ${relDir}/.gitignore with ${GITIGNORE_PATTERN}`);\n }\n\n // Untrack any currently-tracked .md files\n const mdFiles = getMdFilesInDir(dir);\n const trackedMd: string[] = [];\n for (const f of mdFiles) {\n const relPath = relative(repoRoot, join(dir, f));\n if (isFileTracked(repoRoot, relPath)) {\n trackedMd.push(relPath);\n }\n }\n\n if (trackedMd.length > 0) {\n gitRmCached(repoRoot, trackedMd);\n for (const f of trackedMd) {\n console.log(`Untracked ${f} from git (still exists locally)`);\n }\n }\n\n // Stage .mdenc.conf and .gitignore\n const toStage = [\n relative(repoRoot, confPath),\n relative(repoRoot, gitignorePath),\n ];\n gitAdd(repoRoot, toStage);\n\n console.log(`Marked ${relDir}/ for mdenc encryption`);\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { resolvePassword } from './password.js';\nimport {\n findGitRoot,\n findMarkedDirs,\n getHooksDir,\n getMdFilesInDir,\n getMdencFilesInDir,\n} from './utils.js';\n\nconst HOOK_NAMES = ['pre-commit', 'post-checkout', 'post-merge', 'post-rewrite'];\nconst MARKER = '# mdenc-hook-marker';\n\nexport function statusCommand(): void {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n const markedDirs = findMarkedDirs(repoRoot);\n\n if (markedDirs.length === 0) {\n console.log('No directories marked for mdenc encryption.');\n console.log('Use \"mdenc mark <directory>\" to designate a directory.');\n } else {\n console.log('Marked directories:\\n');\n\n for (const dir of markedDirs) {\n const relDir = relative(repoRoot, dir) || '.';\n console.log(` ${relDir}/`);\n\n const mdFiles = getMdFilesInDir(dir);\n const mdencFiles = getMdencFilesInDir(dir);\n\n const mdBases = new Set(mdFiles.map(f => f.replace(/\\.md$/, '')));\n const mdencBases = new Set(mdencFiles.map(f => f.replace(/\\.mdenc$/, '')));\n\n // Paired files (both .md and .mdenc exist)\n for (const base of mdBases) {\n if (mdencBases.has(base)) {\n const mdPath = join(dir, `${base}.md`);\n const mdencPath = join(dir, `${base}.mdenc`);\n const mdMtime = statSync(mdPath).mtimeMs;\n const mdencMtime = statSync(mdencPath).mtimeMs;\n\n if (mdMtime > mdencMtime) {\n console.log(` ${base}.md [needs re-encryption]`);\n } else {\n console.log(` ${base}.md [up to date]`);\n }\n } else {\n console.log(` ${base}.md [not yet encrypted]`);\n }\n }\n\n // Orphaned .mdenc files (no corresponding .md)\n for (const base of mdencBases) {\n if (!mdBases.has(base)) {\n console.log(` ${base}.mdenc [needs decryption]`);\n }\n }\n\n // Check .gitignore health\n const gitignorePath = join(dir, '.gitignore');\n if (!existsSync(gitignorePath)) {\n console.log(` WARNING: no .gitignore in this directory`);\n } else {\n const content = readFileSync(gitignorePath, 'utf-8');\n if (!content.split('\\n').some(l => l.trim() === '*.md')) {\n console.log(` WARNING: .gitignore missing *.md pattern`);\n }\n }\n\n console.log();\n }\n }\n\n // Password status\n if (!password) {\n console.log('Password: NOT AVAILABLE');\n console.log(' Set MDENC_PASSWORD env var or create .mdenc-password file');\n } else {\n console.log('Password: available');\n }\n\n // Hook status\n const hooksDir = getHooksDir();\n const missing: string[] = [];\n for (const name of HOOK_NAMES) {\n const hookPath = join(hooksDir, name);\n if (!existsSync(hookPath)) {\n missing.push(name);\n } else {\n const content = readFileSync(hookPath, 'utf-8');\n if (!content.includes(MARKER)) {\n missing.push(name);\n }\n }\n }\n\n if (missing.length > 0) {\n console.log(`Hooks: MISSING (${missing.join(', ')})`);\n console.log(' Run \"mdenc init\" to install hooks');\n } else {\n console.log('Hooks: all installed');\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport { watch } from 'chokidar';\nimport { encrypt } from '../encrypt.js';\nimport { resolvePassword } from './password.js';\nimport { findGitRoot, findMarkedDirs, getMdFilesInDir } from './utils.js';\n\nexport async function watchCommand(): Promise<void> {\n const repoRoot = findGitRoot();\n const password = resolvePassword(repoRoot);\n\n if (!password) {\n console.error(\n 'mdenc: no password available (set MDENC_PASSWORD or create .mdenc-password)',\n );\n process.exit(1);\n }\n\n const markedDirs = findMarkedDirs(repoRoot);\n if (markedDirs.length === 0) {\n console.error('mdenc: no marked directories found');\n process.exit(1);\n }\n\n // Initial encryption pass\n for (const dir of markedDirs) {\n const mdFiles = getMdFilesInDir(dir);\n for (const mdFile of mdFiles) {\n await encryptFile(dir, mdFile, repoRoot, password);\n }\n }\n\n // Watch each marked directory for .md file changes\n const pending = new Set<string>();\n\n const watcher = watch(\n markedDirs.map(dir => join(dir, '*.md')),\n { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 100 } },\n );\n\n watcher.on('change', (filePath) => {\n handleFileEvent(filePath, repoRoot, password, pending);\n });\n watcher.on('add', (filePath) => {\n handleFileEvent(filePath, repoRoot, password, pending);\n });\n\n for (const dir of markedDirs) {\n const relDir = relative(repoRoot, dir) || '.';\n console.error(`mdenc: watching ${relDir}/`);\n }\n console.error('mdenc: watching for changes (Ctrl+C to stop)');\n}\n\nfunction handleFileEvent(\n filePath: string,\n repoRoot: string,\n password: string,\n pending: Set<string>,\n): void {\n if (pending.has(filePath)) return;\n pending.add(filePath);\n\n const dir = join(filePath, '..');\n const filename = filePath.slice(dir.length + 1);\n\n encryptFile(dir, filename, repoRoot, password).finally(() => {\n pending.delete(filePath);\n });\n}\n\nasync function encryptFile(\n dir: string,\n mdFile: string,\n repoRoot: string,\n password: string,\n): Promise<void> {\n const mdPath = join(dir, mdFile);\n const mdencPath = mdPath.replace(/\\.md$/, '.mdenc');\n const relMdPath = relative(repoRoot, mdPath);\n\n if (!existsSync(mdPath)) return;\n\n try {\n const plaintext = readFileSync(mdPath, 'utf-8');\n\n let previousFile: string | undefined;\n if (existsSync(mdencPath)) {\n previousFile = readFileSync(mdencPath, 'utf-8');\n }\n\n const encrypted = await encrypt(plaintext, password, { previousFile });\n if (encrypted === previousFile) return;\n writeFileSync(mdencPath, encrypted);\n console.error(`mdenc: encrypted ${relMdPath}`);\n } catch (err) {\n console.error(\n `mdenc: failed to encrypt ${relMdPath}: ${err instanceof Error ? err.message : err}`,\n );\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport { findGitRoot } from './utils.js';\n\nconst PASSWORD_FILE = '.mdenc-password';\n\nexport function genpassCommand(force: boolean): void {\n const repoRoot = findGitRoot();\n const passwordPath = join(repoRoot, PASSWORD_FILE);\n\n if (existsSync(passwordPath) && !force) {\n console.error(`${PASSWORD_FILE} already exists. Use --force to overwrite.`);\n process.exit(1);\n }\n\n const password = Buffer.from(randomBytes(32)).toString('base64url');\n writeFileSync(passwordPath, password + '\\n', { mode: 0o600 });\n console.error(`Generated password and wrote to ${PASSWORD_FILE}`);\n console.error(password);\n\n // Ensure .mdenc-password is in .gitignore\n const gitignorePath = join(repoRoot, '.gitignore');\n const entry = PASSWORD_FILE;\n\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim());\n if (!lines.includes(entry)) {\n writeFileSync(gitignorePath, content.trimEnd() + '\\n' + entry + '\\n');\n console.error('Added .mdenc-password to .gitignore');\n }\n } else {\n writeFileSync(gitignorePath, entry + '\\n');\n console.error('Created .gitignore with .mdenc-password');\n }\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","readFileSync","writeFileSync","join","existsSync","join","writeFileSync","readFileSync","existsSync","readFileSync","join","relative","HOOK_NAMES","MARKER","relative","join","existsSync","readFileSync","readFileSync","writeFileSync","existsSync","join","relative","join","relative","existsSync","readFileSync","writeFileSync","existsSync","readFileSync","writeFileSync","join","join","existsSync","writeFileSync","readFileSync","resolve","readFileSync","writeFileSync"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
postMergeHook,
|
|
6
6
|
postRewriteHook,
|
|
7
7
|
preCommitHook
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KNJGGFI4.js";
|
|
9
9
|
export {
|
|
10
10
|
decryptAll,
|
|
11
11
|
postCheckoutHook,
|
|
@@ -13,4 +13,4 @@ export {
|
|
|
13
13
|
postRewriteHook,
|
|
14
14
|
preCommitHook
|
|
15
15
|
};
|
|
16
|
-
//# sourceMappingURL=hooks-
|
|
16
|
+
//# sourceMappingURL=hooks-FL46SI4A.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -265,6 +265,9 @@ function validateScryptParams(params) {
|
|
|
265
265
|
if (params.N < N.min || params.N > N.max) {
|
|
266
266
|
throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}\u2013${N.max})`);
|
|
267
267
|
}
|
|
268
|
+
if ((params.N & params.N - 1) !== 0) {
|
|
269
|
+
throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);
|
|
270
|
+
}
|
|
268
271
|
if (params.r < r.min || params.r > r.max) {
|
|
269
272
|
throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}\u2013${r.max})`);
|
|
270
273
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/seal.ts"],"sourcesContent":["export type {\n ScryptParams,\n MdencHeader,\n MdencChunk,\n MdencFile,\n EncryptOptions,\n DecryptOptions,\n} from './types.js';\nexport { DEFAULT_SCRYPT_PARAMS, ChunkingStrategy } from './types.js';\nexport { encrypt, decrypt } from './encrypt.js';\nexport { verifySeal } from './seal.js';\n","export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n"],"mappings":"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","names":["ChunkingStrategy","import_hmac","import_sha256","import_sha256","import_hmac","import_sha256","import_hmac","import_sha256"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/seal.ts"],"sourcesContent":["export type {\n ScryptParams,\n MdencHeader,\n MdencChunk,\n MdencFile,\n EncryptOptions,\n DecryptOptions,\n} from './types.js';\nexport { DEFAULT_SCRYPT_PARAMS, ChunkingStrategy } from './types.js';\nexport { encrypt, decrypt } from './encrypt.js';\nexport { verifySeal } from './seal.js';\n","export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if ((params.N & (params.N - 1)) !== 0) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n"],"mappings":"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","names":["ChunkingStrategy","import_hmac","import_sha256","import_sha256","import_hmac","import_sha256","import_hmac","import_sha256"]}
|
package/dist/index.js
CHANGED
|
@@ -235,6 +235,9 @@ function validateScryptParams(params) {
|
|
|
235
235
|
if (params.N < N.min || params.N > N.max) {
|
|
236
236
|
throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}\u2013${N.max})`);
|
|
237
237
|
}
|
|
238
|
+
if ((params.N & params.N - 1) !== 0) {
|
|
239
|
+
throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);
|
|
240
|
+
}
|
|
238
241
|
if (params.r < r.min || params.r > r.max) {
|
|
239
242
|
throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}\u2013${r.max})`);
|
|
240
243
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/seal.ts"],"sourcesContent":["export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n"],"mappings":"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","names":["ChunkingStrategy","hmac","sha256","sha256","hmac","sha256","hmac","sha256","hmac","sha256","hmac","sha256","hmac","sha256"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/encrypt.ts","../src/chunking.ts","../src/kdf.ts","../src/crypto-utils.ts","../src/aead.ts","../src/header.ts","../src/seal.ts"],"sourcesContent":["export interface ScryptParams {\n N: number; // CPU/memory cost (default 16384 = 2^14, ~16 MiB with r=8)\n r: number; // block size (default 8)\n p: number; // parallelism (default 1)\n}\n\nexport const DEFAULT_SCRYPT_PARAMS: ScryptParams = {\n N: 16384,\n r: 8,\n p: 1,\n};\n\nexport const SCRYPT_BOUNDS = {\n N: { min: 1024, max: 1048576 }, // 2^10 – 2^20\n r: { min: 1, max: 64 },\n p: { min: 1, max: 16 },\n} as const;\n\nexport interface MdencHeader {\n version: 'v1';\n salt: Uint8Array; // 16 bytes\n fileId: Uint8Array; // 16 bytes\n scrypt: ScryptParams;\n}\n\nexport interface MdencChunk {\n payload: Uint8Array; // nonce || ciphertext || tag\n}\n\nexport interface MdencFile {\n header: MdencHeader;\n headerLine: string;\n headerHmac: Uint8Array;\n chunks: MdencChunk[];\n sealHmac: Uint8Array; // file-level HMAC\n}\n\nexport enum ChunkingStrategy {\n Paragraph = 'paragraph',\n FixedSize = 'fixed-size',\n}\n\nexport interface EncryptOptions {\n chunking?: ChunkingStrategy;\n maxChunkSize?: number; // bytes, default 65536 (64 KiB)\n fixedChunkSize?: number; // bytes, for fixed-size chunking\n scrypt?: ScryptParams;\n previousFile?: string; // previous encrypted file content for ciphertext reuse\n}\n\nexport interface DecryptOptions {\n // Reserved for future options\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { chunkByParagraph, chunkByFixedSize } from './chunking.js';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport { encryptChunk, decryptChunk } from './aead.js';\nimport {\n serializeHeader,\n parseHeader,\n authenticateHeader,\n verifyHeader,\n generateSalt,\n generateFileId,\n toBase64,\n fromBase64,\n} from './header.js';\nimport { ChunkingStrategy, DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport type { EncryptOptions, MdencHeader } from './types.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function encrypt(\n plaintext: string,\n password: string,\n options?: EncryptOptions,\n): Promise<string> {\n const chunking = options?.chunking ?? ChunkingStrategy.Paragraph;\n const maxChunkSize = options?.maxChunkSize ?? 65536;\n const scryptParams = options?.scrypt ?? DEFAULT_SCRYPT_PARAMS;\n\n // Chunk the plaintext\n let chunks: string[];\n if (chunking === ChunkingStrategy.FixedSize) {\n const fixedSize = options?.fixedChunkSize ?? 4096;\n chunks = chunkByFixedSize(plaintext, fixedSize);\n } else {\n chunks = chunkByParagraph(plaintext, maxChunkSize);\n }\n\n // If previousFile provided, extract salt/fileId/keys from its header to avoid\n // deriving the same master key twice (same password + same salt).\n let salt: Uint8Array;\n let fileId: Uint8Array;\n let masterKey: Uint8Array;\n\n const prev = options?.previousFile\n ? parsePreviousFileHeader(options.previousFile, password)\n : undefined;\n\n if (prev) {\n salt = prev.salt;\n fileId = prev.fileId;\n masterKey = prev.masterKey;\n } else {\n salt = generateSalt();\n fileId = generateFileId();\n masterKey = deriveMasterKey(password, salt, scryptParams);\n }\n\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Build header\n const header: MdencHeader = { version: 'v1', salt, fileId, scrypt: scryptParams };\n const headerLine = serializeHeader(header);\n const headerHmac = authenticateHeader(headerKey, headerLine);\n const headerAuthLine = `hdrauth_b64=${toBase64(headerHmac)}`;\n\n // Encrypt chunks — deterministic encryption handles reuse automatically\n const chunkLines: string[] = [];\n for (const chunkText of chunks) {\n const chunkBytes = new TextEncoder().encode(chunkText);\n const payload = encryptChunk(encKey, nonceKey, chunkBytes, fileId);\n chunkLines.push(toBase64(payload));\n }\n\n // Compute seal HMAC over header + auth + chunk lines\n const sealInput = headerLine + '\\n' + headerAuthLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const sealHmac = hmac(sha256, headerKey, sealData);\n const sealLine = `seal_b64=${toBase64(sealHmac)}`;\n\n return [headerLine, headerAuthLine, ...chunkLines, sealLine, ''].join('\\n');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nexport async function decrypt(\n fileContent: string,\n password: string,\n): Promise<string> {\n const lines = fileContent.split('\\n');\n\n // Remove trailing empty line if present\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lines.length < 3) {\n throw new Error('Invalid mdenc file: too few lines');\n }\n\n // Parse header\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) {\n throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n }\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { encKey, headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header HMAC\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed (wrong password or tampered header)');\n }\n\n // Collect chunk lines and seal line\n const remaining = lines.slice(2);\n const sealIndex = remaining.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('Invalid mdenc file: missing seal');\n }\n\n const chunkLines = remaining.slice(0, sealIndex);\n if (chunkLines.length === 0) {\n throw new Error('Invalid mdenc file: no chunk lines');\n }\n\n // Verify seal HMAC\n const sealMatch = remaining[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid mdenc file: malformed seal line');\n const storedSealHmac = fromBase64(sealMatch[1]);\n\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computedSealHmac = hmac(sha256, headerKey, sealData);\n if (!constantTimeEqual(computedSealHmac, storedSealHmac)) {\n throw new Error('Seal verification failed (file tampered or chunks reordered)');\n }\n\n // Decrypt chunks\n const plaintextParts: string[] = [];\n for (const line of chunkLines) {\n const payload = fromBase64(line);\n const decrypted = decryptChunk(encKey, payload, header.fileId);\n plaintextParts.push(new TextDecoder().decode(decrypted));\n }\n\n return plaintextParts.join('');\n } finally {\n zeroize(masterKey, encKey, headerKey, nonceKey);\n }\n}\n\nfunction parsePreviousFileHeader(\n fileContent: string,\n password: string,\n): { salt: Uint8Array; fileId: Uint8Array; masterKey: Uint8Array } | undefined {\n try {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) return undefined;\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse and verify header HMAC before trusting\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) return undefined;\n const headerHmac = fromBase64(authMatch[1]);\n\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey } = deriveKeys(masterKey);\n\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n zeroize(masterKey, headerKey);\n return undefined;\n }\n\n zeroize(headerKey);\n // Return masterKey for reuse — same password + same salt produces the same key,\n // so the caller can skip a redundant scrypt derivation.\n return { salt: header.salt, fileId: header.fileId, masterKey };\n } catch {\n return undefined;\n }\n}\n","const DEFAULT_MAX_CHUNK_SIZE = 65536; // 64 KiB\n\nexport function chunkByParagraph(text: string, maxSize = DEFAULT_MAX_CHUNK_SIZE): string[] {\n // Normalize line endings\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n // Split on runs of 2+ newlines, attaching each boundary to the preceding chunk\n const chunks: string[] = [];\n const boundary = /\\n{2,}/g;\n let lastEnd = 0;\n let match: RegExpExecArray | null;\n\n while ((match = boundary.exec(normalized)) !== null) {\n // Content up to and including the boundary goes to the preceding chunk\n const chunkEnd = match.index + match[0].length;\n chunks.push(normalized.slice(lastEnd, chunkEnd));\n lastEnd = chunkEnd;\n }\n\n // Remaining content after the last boundary (or the entire string if no boundary)\n if (lastEnd < normalized.length) {\n chunks.push(normalized.slice(lastEnd));\n } else if (chunks.length === 0) {\n // No boundaries found and nothing remaining — shouldn't happen since we checked length > 0\n chunks.push(normalized);\n }\n\n // Split any oversized chunks at byte boundaries\n const result: string[] = [];\n for (const chunk of chunks) {\n if (byteLength(chunk) <= maxSize) {\n result.push(chunk);\n } else {\n result.push(...splitAtByteLimit(chunk, maxSize));\n }\n }\n\n return result;\n}\n\nexport function chunkByFixedSize(text: string, size: number): string[] {\n const normalized = text.replace(/\\r\\n/g, '\\n');\n\n if (normalized.length === 0) {\n return [''];\n }\n\n const bytes = new TextEncoder().encode(normalized);\n if (bytes.length <= size) {\n return [normalized];\n }\n\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n let offset = 0;\n while (offset < bytes.length) {\n const end = Math.min(offset + size, bytes.length);\n // Avoid splitting in the middle of a multi-byte UTF-8 character\n let adjusted = end;\n if (adjusted < bytes.length) {\n while (adjusted > offset && (bytes[adjusted] & 0xc0) === 0x80) {\n adjusted--;\n }\n }\n chunks.push(decoder.decode(bytes.slice(offset, adjusted)));\n offset = adjusted;\n }\n\n return chunks;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n\nfunction splitAtByteLimit(text: string, maxSize: number): string[] {\n const bytes = new TextEncoder().encode(text);\n const decoder = new TextDecoder();\n const parts: string[] = [];\n let offset = 0;\n while (offset < bytes.length) {\n let end = Math.min(offset + maxSize, bytes.length);\n // Avoid splitting multi-byte characters\n if (end < bytes.length) {\n while (end > offset && (bytes[end] & 0xc0) === 0x80) {\n end--;\n }\n }\n parts.push(decoder.decode(bytes.slice(offset, end)));\n offset = end;\n }\n return parts;\n}\n","import { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { scrypt } from '@noble/hashes/scrypt';\nimport type { ScryptParams } from './types.js';\nimport { DEFAULT_SCRYPT_PARAMS } from './types.js';\nimport { zeroize } from './crypto-utils.js';\n\nconst ENC_INFO = new TextEncoder().encode('mdenc-v1-enc');\nconst HDR_INFO = new TextEncoder().encode('mdenc-v1-hdr');\nconst NONCE_INFO = new TextEncoder().encode('mdenc-v1-nonce');\n\nexport function normalizePassword(password: string): Uint8Array {\n const normalized = password.normalize('NFKC');\n return new TextEncoder().encode(normalized);\n}\n\nexport function deriveMasterKey(\n password: string,\n salt: Uint8Array,\n params: ScryptParams = DEFAULT_SCRYPT_PARAMS,\n): Uint8Array {\n const passwordBytes = normalizePassword(password);\n try {\n return scrypt(passwordBytes, salt, {\n N: params.N,\n r: params.r,\n p: params.p,\n dkLen: 32,\n });\n } finally {\n zeroize(passwordBytes);\n }\n}\n\nexport function deriveKeys(masterKey: Uint8Array): { encKey: Uint8Array; headerKey: Uint8Array; nonceKey: Uint8Array } {\n const encKey = hkdf(sha256, masterKey, undefined, ENC_INFO, 32);\n const headerKey = hkdf(sha256, masterKey, undefined, HDR_INFO, 32);\n const nonceKey = hkdf(sha256, masterKey, undefined, NONCE_INFO, 32);\n return { encKey, headerKey, nonceKey };\n}\n","export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a[i] ^ b[i];\n }\n return diff === 0;\n}\n\nexport function zeroize(...arrays: Uint8Array[]): void {\n for (const arr of arrays) {\n arr.fill(0);\n }\n}\n","import { xchacha20poly1305 } from '@noble/ciphers/chacha';\nimport { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\n\nconst NONCE_LENGTH = 24;\n\nexport function buildAAD(fileId: Uint8Array): Uint8Array {\n const fileIdHex = bytesToHex(fileId);\n const aadString = `mdenc:v1\\n${fileIdHex}`;\n return new TextEncoder().encode(aadString);\n}\n\nexport function deriveNonce(nonceKey: Uint8Array, plaintext: Uint8Array): Uint8Array {\n const full = hmac(sha256, nonceKey, plaintext);\n return full.slice(0, NONCE_LENGTH);\n}\n\nexport function encryptChunk(\n encKey: Uint8Array,\n nonceKey: Uint8Array,\n plaintext: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n const nonce = deriveNonce(nonceKey, plaintext);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n const ciphertext = cipher.encrypt(plaintext);\n // Output: nonce || ciphertext || tag (tag is appended by noble)\n const result = new Uint8Array(NONCE_LENGTH + ciphertext.length);\n result.set(nonce, 0);\n result.set(ciphertext, NONCE_LENGTH);\n return result;\n}\n\nexport function decryptChunk(\n encKey: Uint8Array,\n payload: Uint8Array,\n fileId: Uint8Array,\n): Uint8Array {\n if (payload.length < NONCE_LENGTH + 16) {\n throw new Error('Chunk payload too short');\n }\n const nonce = payload.slice(0, NONCE_LENGTH);\n const ciphertext = payload.slice(NONCE_LENGTH);\n const aad = buildAAD(fileId);\n const cipher = xchacha20poly1305(encKey, nonce, aad);\n try {\n return cipher.decrypt(ciphertext);\n } catch {\n throw new Error('Chunk authentication failed');\n }\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport type { MdencHeader, ScryptParams } from './types.js';\nimport { SCRYPT_BOUNDS } from './types.js';\nimport { constantTimeEqual } from './crypto-utils.js';\n\nexport function generateSalt(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function generateFileId(): Uint8Array {\n return randomBytes(16);\n}\n\nexport function serializeHeader(header: MdencHeader): string {\n const saltB64 = toBase64(header.salt);\n const fileIdB64 = toBase64(header.fileId);\n const { N, r, p } = header.scrypt;\n return `mdenc:v1 salt_b64=${saltB64} file_id_b64=${fileIdB64} scrypt=N=${N},r=${r},p=${p}`;\n}\n\nexport function parseHeader(line: string): MdencHeader {\n if (!line.startsWith('mdenc:v1 ')) {\n throw new Error('Invalid header: missing mdenc:v1 prefix');\n }\n\n const saltMatch = line.match(/salt_b64=([A-Za-z0-9+/=]+)/);\n if (!saltMatch) throw new Error('Invalid header: missing salt_b64');\n const salt = fromBase64(saltMatch[1]);\n if (salt.length !== 16) throw new Error('Invalid header: salt must be 16 bytes');\n\n const fileIdMatch = line.match(/file_id_b64=([A-Za-z0-9+/=]+)/);\n if (!fileIdMatch) throw new Error('Invalid header: missing file_id_b64');\n const fileId = fromBase64(fileIdMatch[1]);\n if (fileId.length !== 16) throw new Error('Invalid header: file_id must be 16 bytes');\n\n const scryptMatch = line.match(/scrypt=N=(\\d+),r=(\\d+),p=(\\d+)/);\n if (!scryptMatch) throw new Error('Invalid header: missing scrypt parameters');\n const scryptParams: ScryptParams = {\n N: parseInt(scryptMatch[1], 10),\n r: parseInt(scryptMatch[2], 10),\n p: parseInt(scryptMatch[3], 10),\n };\n\n validateScryptParams(scryptParams);\n\n return { version: 'v1', salt, fileId, scrypt: scryptParams };\n}\n\nexport function validateScryptParams(params: ScryptParams): void {\n const { N, r, p } = SCRYPT_BOUNDS;\n if (params.N < N.min || params.N > N.max) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be ${N.min}–${N.max})`);\n }\n if ((params.N & (params.N - 1)) !== 0) {\n throw new Error(`Invalid scrypt N: ${params.N} (must be a power of 2)`);\n }\n if (params.r < r.min || params.r > r.max) {\n throw new Error(`Invalid scrypt r: ${params.r} (must be ${r.min}–${r.max})`);\n }\n if (params.p < p.min || params.p > p.max) {\n throw new Error(`Invalid scrypt p: ${params.p} (must be ${p.min}–${p.max})`);\n }\n}\n\nexport function authenticateHeader(headerKey: Uint8Array, headerLine: string): Uint8Array {\n const headerBytes = new TextEncoder().encode(headerLine);\n return hmac(sha256, headerKey, headerBytes);\n}\n\nexport function verifyHeader(\n headerKey: Uint8Array,\n headerLine: string,\n hmacBytes: Uint8Array,\n): boolean {\n const computed = authenticateHeader(headerKey, headerLine);\n return constantTimeEqual(computed, hmacBytes);\n}\n\nexport function toBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n}\n","import { hmac } from '@noble/hashes/hmac';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { deriveMasterKey, deriveKeys } from './kdf.js';\nimport {\n parseHeader,\n verifyHeader,\n fromBase64,\n} from './header.js';\nimport { constantTimeEqual, zeroize } from './crypto-utils.js';\n\nexport async function verifySeal(\n fileContent: string,\n password: string,\n): Promise<boolean> {\n const lines = fileContent.split('\\n');\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n if (lines.length < 3) throw new Error('Invalid mdenc file: too few lines');\n\n const headerLine = lines[0];\n const header = parseHeader(headerLine);\n\n // Parse header auth\n const authLine = lines[1];\n const authMatch = authLine.match(/^hdrauth_b64=([A-Za-z0-9+/=]+)$/);\n if (!authMatch) throw new Error('Invalid mdenc file: missing hdrauth_b64 line');\n const headerHmac = fromBase64(authMatch[1]);\n\n // Derive keys\n const masterKey = deriveMasterKey(password, header.salt, header.scrypt);\n const { headerKey, nonceKey } = deriveKeys(masterKey);\n\n try {\n // Verify header\n if (!verifyHeader(headerKey, headerLine, headerHmac)) {\n throw new Error('Header authentication failed');\n }\n\n // Find seal line\n const chunkAndSealLines = lines.slice(2);\n const sealIndex = chunkAndSealLines.findIndex(l => l.startsWith('seal_b64='));\n if (sealIndex < 0) {\n throw new Error('File is not sealed: no seal_b64 line found');\n }\n\n const chunkLines = chunkAndSealLines.slice(0, sealIndex);\n const sealMatch = chunkAndSealLines[sealIndex].match(/^seal_b64=([A-Za-z0-9+/=]+)$/);\n if (!sealMatch) throw new Error('Invalid seal line');\n const storedHmac = fromBase64(sealMatch[1]);\n\n // Verify seal HMAC (covers header + auth + chunk lines)\n const sealInput = headerLine + '\\n' + authLine + '\\n' + chunkLines.join('\\n');\n const sealData = new TextEncoder().encode(sealInput);\n const computed = hmac(sha256, headerKey, sealData);\n\n return constantTimeEqual(computed, storedHmac);\n } finally {\n zeroize(masterKey, headerKey, nonceKey);\n }\n}\n"],"mappings":"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","names":["ChunkingStrategy","hmac","sha256","sha256","hmac","sha256","hmac","sha256","hmac","sha256","hmac","sha256","hmac","sha256"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdenc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Diff-friendly encrypted Markdown format",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"license": "ISC",
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@noble/ciphers": "^1.2.1",
|
|
58
|
-
"@noble/hashes": "^1.7.1"
|
|
58
|
+
"@noble/hashes": "^1.7.1",
|
|
59
|
+
"chokidar": "^5.0.0"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@types/node": "^25.3.3",
|
|
File without changes
|