@byearlybird/crypto 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,25 +1,47 @@
1
- //#region src/encryption.d.ts
2
- declare function encryptData(data: string, masterKey: CryptoKey): Promise<string>;
3
- declare function decryptData(encryptedData: string, masterKey: CryptoKey): Promise<string>;
1
+ import * as crypto0 from "crypto";
2
+
3
+ //#region src/eb-auth.d.ts
4
+ type AuthPayload = {
5
+ scheme: string;
6
+ nonce: string;
7
+ vaultId: string;
8
+ method: string;
9
+ pathWithQuery: string;
10
+ timestamp: number;
11
+ bodyHash?: string;
12
+ };
13
+ declare function deriveVaultId(publicKeyB64: string): Promise<string>;
14
+ declare function generateAuthPayload(args: {
15
+ vaultId: string;
16
+ method: "GET" | "PUT" | "POST" | "PATCH" | "DELETE";
17
+ pathWithQuery: string;
18
+ serializedBody?: string;
19
+ }): Promise<AuthPayload>;
20
+ declare function makeCanonicalString(payload: AuthPayload): string;
21
+ declare function makeAuthHeader(payload: AuthPayload, signature: string): string;
22
+ declare function parseAuthHeader(header: string): AuthPayload & {
23
+ signature: string;
24
+ };
4
25
  //#endregion
5
- //#region src/hash.d.ts
6
- declare function hash(data: string): Promise<string>;
7
- declare function hashObject(obj: unknown): Promise<string>;
26
+ //#region src/encryption-keys.d.ts
27
+ declare function generateEncryptionKey(extractable?: boolean): Promise<crypto0.webcrypto.CryptoKey>;
28
+ declare function exportEncryptionKey(key: CryptoKey): Promise<string>;
29
+ declare function importEncryptionKey(base64: string, extractable?: boolean): Promise<crypto0.webcrypto.CryptoKey>;
30
+ declare function encrypt(plaintext: string, key: CryptoKey): Promise<string>;
31
+ declare function decrypt(encoded: string, key: CryptoKey): Promise<string>;
8
32
  //#endregion
9
- //#region src/keys.d.ts
10
- type DerivedKeyResult = {
11
- key: CryptoKey;
12
- salt: Uint8Array;
13
- };
14
- declare function generateVaultKey(): string;
15
- declare function generateMasterKey(): Promise<CryptoKey>;
16
- declare function deriveEncryptionKey(vaultKey: string, salt?: Uint8Array): Promise<DerivedKeyResult>;
17
- declare function encryptMasterKey(masterKey: CryptoKey, vaultKey: string): Promise<string>;
18
- declare function decryptMasterKey(encryptedMasterKey: string, vaultKey: string): Promise<CryptoKey>;
19
- declare function generateKeys(): Promise<{
20
- vaultKey: string;
21
- masterKey: CryptoKey;
22
- encryptedMasterKey: string;
23
- }>;
33
+ //#region src/signing-keys.d.ts
34
+ declare function generateSigningKeyPair(extractable?: boolean): Promise<CryptoKeyPair>;
35
+ declare function exportPublicKey(key: CryptoKey): Promise<string>;
36
+ declare function importPublicKey(spkiB64: string, extractable?: boolean): Promise<CryptoKey>;
37
+ declare function exportPrivateKey(key: CryptoKey): Promise<string>;
38
+ declare function importPrivateKey(pkcs8B64: string, extractable?: boolean): Promise<CryptoKey>;
39
+ declare function sign(message: string, privateKey: CryptoKey): Promise<string>;
40
+ declare function verify(message: string, signatureB64: string, publicKey: CryptoKey): Promise<boolean>;
41
+ //#endregion
42
+ //#region src/utils.d.ts
43
+ declare function bytesToBase64(bytes: Uint8Array): string;
44
+ declare function base64ToBytes(base64: string): Uint8Array;
45
+ declare function hashString(value: string): Promise<string>;
24
46
  //#endregion
25
- export { decryptData, decryptMasterKey, deriveEncryptionKey, encryptData, encryptMasterKey, generateKeys, generateMasterKey, generateVaultKey, hash, hashObject };
47
+ export { AuthPayload, base64ToBytes, bytesToBase64, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey, generateAuthPayload, generateEncryptionKey, generateSigningKeyPair, hashString, importEncryptionKey, importPrivateKey, importPublicKey, makeAuthHeader, makeCanonicalString, parseAuthHeader, sign, verify };
package/dist/index.mjs CHANGED
@@ -1,138 +1,162 @@
1
- //#region src/crypto-utils.ts
2
- function randomBytes(length) {
3
- const bytes = new Uint8Array(length);
4
- crypto.getRandomValues(bytes);
5
- return bytes;
6
- }
7
- function concatBytes(...chunks) {
8
- const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
9
- const result = new Uint8Array(total);
10
- let offset = 0;
11
- for (const chunk of chunks) {
12
- result.set(chunk, offset);
13
- offset += chunk.length;
14
- }
15
- return result;
1
+ //#region src/utils.ts
2
+ function bytesToBase64(bytes) {
3
+ return Buffer.from(bytes).toString("base64");
16
4
  }
17
- function toBase64(bytes) {
18
- return btoa(String.fromCharCode(...bytes));
5
+ function base64ToBytes(base64) {
6
+ return new Uint8Array(Buffer.from(base64, "base64"));
19
7
  }
20
- function fromBase64(value) {
21
- return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
8
+ async function hashString(value) {
9
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
10
+ return Buffer.from(hash).toString("hex");
22
11
  }
23
12
 
24
13
  //#endregion
25
- //#region src/encryption.ts
26
- const IV_LENGTH$1 = 12;
27
- async function encryptData(data, masterKey) {
28
- const iv = randomBytes(IV_LENGTH$1);
29
- const encrypted = await crypto.subtle.encrypt({
30
- name: "AES-GCM",
31
- iv
32
- }, masterKey, new TextEncoder().encode(data));
33
- return toBase64(concatBytes(iv, new Uint8Array(encrypted)));
34
- }
35
- async function decryptData(encryptedData, masterKey) {
36
- const combined = fromBase64(encryptedData);
37
- if (combined.length <= IV_LENGTH$1) throw new Error("Invalid encrypted data payload");
38
- const iv = combined.slice(0, IV_LENGTH$1);
39
- const encrypted = combined.slice(IV_LENGTH$1);
40
- const decrypted = await crypto.subtle.decrypt({
41
- name: "AES-GCM",
42
- iv
43
- }, masterKey, encrypted);
44
- return new TextDecoder().decode(decrypted);
14
+ //#region src/eb-auth.ts
15
+ const CURRENT_SCHEME = "eb1";
16
+ async function deriveVaultId(publicKeyB64) {
17
+ const spkiBytes = Buffer.from(publicKeyB64, "base64");
18
+ const hash = await crypto.subtle.digest("SHA-256", spkiBytes);
19
+ return Buffer.from(hash).toString("hex").slice(0, 32);
45
20
  }
46
-
47
- //#endregion
48
- //#region src/hash.ts
49
- async function hash(data) {
50
- const dataBytes = new TextEncoder().encode(data);
51
- const hashBuffer = await crypto.subtle.digest("SHA-256", dataBytes);
52
- return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
53
- }
54
- async function hashObject(obj) {
55
- const canonical = JSON.stringify(normalize(obj));
56
- if (canonical === void 0) throw new TypeError("hashObject only supports JSON-serializable values");
57
- return hash(canonical);
58
- }
59
- function normalize(value) {
60
- if (Array.isArray(value)) return value.map((item) => normalize(item));
61
- if (value && typeof value === "object") {
62
- const prototype = Object.getPrototypeOf(value);
63
- if (prototype === Object.prototype || prototype === null) {
64
- const sortedKeys = Object.keys(value).sort();
65
- const result = {};
66
- for (const key of sortedKeys) result[key] = normalize(value[key]);
67
- return result;
68
- }
21
+ async function generateAuthPayload(args) {
22
+ const { serializedBody,...rest } = args;
23
+ const nonce = crypto.randomUUID();
24
+ const timestamp = Date.now();
25
+ const payload = {
26
+ ...rest,
27
+ scheme: CURRENT_SCHEME,
28
+ nonce,
29
+ timestamp
30
+ };
31
+ if (serializedBody) payload.bodyHash = await hashString(serializedBody);
32
+ return payload;
33
+ }
34
+ function makeCanonicalString(payload) {
35
+ const parts = [
36
+ payload.scheme,
37
+ payload.vaultId,
38
+ payload.method,
39
+ payload.pathWithQuery,
40
+ payload.nonce,
41
+ payload.timestamp
42
+ ];
43
+ if (payload.bodyHash) parts.push(payload.bodyHash);
44
+ return parts.join("\n");
45
+ }
46
+ function makeAuthHeader(payload, signature) {
47
+ const params = [
48
+ `vid=${payload.vaultId}`,
49
+ `n=${payload.nonce}`,
50
+ `m=${payload.method}`,
51
+ `p=${payload.pathWithQuery}`,
52
+ `t=${payload.timestamp}`,
53
+ `sig=${signature}`
54
+ ];
55
+ if (payload.bodyHash) params.push(`bh=${payload.bodyHash}`);
56
+ return `${payload.scheme} ${params.join(";")}`;
57
+ }
58
+ function parseAuthHeader(header) {
59
+ const spaceIdx = header.indexOf(" ");
60
+ if (spaceIdx === -1) throw new Error("Malformed auth header: missing scheme separator");
61
+ const scheme = header.slice(0, spaceIdx);
62
+ if (scheme !== CURRENT_SCHEME) throw new Error(`Unsupported auth scheme: ${scheme}`);
63
+ const paramStr = header.slice(spaceIdx + 1);
64
+ const params = /* @__PURE__ */ new Map();
65
+ for (const part of paramStr.split(";")) {
66
+ const eqIdx = part.indexOf("=");
67
+ if (eqIdx === -1) throw new Error(`Malformed auth header param: ${part}`);
68
+ params.set(part.slice(0, eqIdx), part.slice(eqIdx + 1));
69
69
  }
70
- return value;
70
+ const vid = params.get("vid");
71
+ const n = params.get("n");
72
+ const m = params.get("m");
73
+ const p = params.get("p");
74
+ const t = params.get("t");
75
+ const sig = params.get("sig");
76
+ if (!vid || !n || !m || !p || !t || !sig) throw new Error("Malformed auth header: missing required params");
77
+ const timestamp = Number(t);
78
+ if (Number.isNaN(timestamp)) throw new Error("Malformed auth header: timestamp is not a number");
79
+ const result = {
80
+ scheme,
81
+ vaultId: vid,
82
+ nonce: n,
83
+ method: m,
84
+ pathWithQuery: p,
85
+ timestamp,
86
+ signature: sig
87
+ };
88
+ const bh = params.get("bh");
89
+ if (bh) result.bodyHash = bh;
90
+ return result;
71
91
  }
72
92
 
73
93
  //#endregion
74
- //#region src/keys.ts
75
- const PBKDF2_ITERATIONS = 6e5;
76
- const PBKDF2_SALT_LENGTH = 16;
77
- const VAULT_KEY_LENGTH = 32;
78
- const IV_LENGTH = 12;
79
- function generateVaultKey() {
80
- return [...randomBytes(VAULT_KEY_LENGTH)].map((b) => b.toString(16).padStart(2, "0")).join("");
81
- }
82
- async function generateMasterKey() {
94
+ //#region src/encryption-keys.ts
95
+ async function generateEncryptionKey(extractable = false) {
83
96
  return crypto.subtle.generateKey({
84
97
  name: "AES-GCM",
85
98
  length: 256
86
- }, true, ["encrypt", "decrypt"]);
87
- }
88
- async function deriveEncryptionKey(vaultKey, salt) {
89
- const normalizedKey = vaultKey.toLowerCase();
90
- const encoder = new TextEncoder();
91
- const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(normalizedKey), { name: "PBKDF2" }, false, ["deriveKey"]);
92
- const saltBytes = salt ? new Uint8Array(salt) : randomBytes(PBKDF2_SALT_LENGTH);
93
- return {
94
- key: await crypto.subtle.deriveKey({
95
- name: "PBKDF2",
96
- salt: saltBytes,
97
- iterations: PBKDF2_ITERATIONS,
98
- hash: "SHA-256"
99
- }, keyMaterial, {
100
- name: "AES-GCM",
101
- length: 256
102
- }, false, ["wrapKey", "unwrapKey"]),
103
- salt: saltBytes
104
- };
99
+ }, extractable, ["encrypt", "decrypt"]);
100
+ }
101
+ async function exportEncryptionKey(key) {
102
+ return bytesToBase64(new Uint8Array(await crypto.subtle.exportKey("raw", key)));
105
103
  }
106
- async function encryptMasterKey(masterKey, vaultKey) {
107
- const { key: wrappingKey, salt } = await deriveEncryptionKey(vaultKey);
108
- const iv = randomBytes(IV_LENGTH);
109
- const wrapped = await crypto.subtle.wrapKey("raw", masterKey, wrappingKey, {
104
+ async function importEncryptionKey(base64, extractable = false) {
105
+ const raw = base64ToBytes(base64);
106
+ return crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, extractable, ["encrypt", "decrypt"]);
107
+ }
108
+ async function encrypt(plaintext, key) {
109
+ const iv = crypto.getRandomValues(new Uint8Array(12));
110
+ const ciphertext = await crypto.subtle.encrypt({
110
111
  name: "AES-GCM",
111
112
  iv
112
- });
113
- return toBase64(concatBytes(salt, iv, new Uint8Array(wrapped)));
114
- }
115
- async function decryptMasterKey(encryptedMasterKey, vaultKey) {
116
- const combined = fromBase64(encryptedMasterKey);
117
- if (combined.length <= PBKDF2_SALT_LENGTH + IV_LENGTH) throw new Error("Invalid encrypted master key payload");
118
- const salt = combined.slice(0, PBKDF2_SALT_LENGTH);
119
- const iv = combined.slice(PBKDF2_SALT_LENGTH, PBKDF2_SALT_LENGTH + IV_LENGTH);
120
- const wrapped = combined.slice(PBKDF2_SALT_LENGTH + IV_LENGTH);
121
- const { key: unwrappingKey } = await deriveEncryptionKey(vaultKey, salt);
122
- return crypto.subtle.unwrapKey("raw", wrapped, unwrappingKey, {
113
+ }, key, new TextEncoder().encode(plaintext));
114
+ const combined = new Uint8Array(iv.byteLength + ciphertext.byteLength);
115
+ combined.set(iv, 0);
116
+ combined.set(new Uint8Array(ciphertext), iv.byteLength);
117
+ return bytesToBase64(combined);
118
+ }
119
+ async function decrypt(encoded, key) {
120
+ const combined = base64ToBytes(encoded);
121
+ const iv = new Uint8Array(combined.subarray(0, 12));
122
+ const ciphertext = new Uint8Array(combined.subarray(12));
123
+ const decrypted = await crypto.subtle.decrypt({
123
124
  name: "AES-GCM",
124
125
  iv
125
- }, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
126
- }
127
- async function generateKeys() {
128
- const vaultKey = generateVaultKey();
129
- const encryptedMasterKey = await encryptMasterKey(await generateMasterKey(), vaultKey);
130
- return {
131
- vaultKey,
132
- masterKey: await decryptMasterKey(encryptedMasterKey, vaultKey),
133
- encryptedMasterKey
134
- };
126
+ }, key, ciphertext);
127
+ return new TextDecoder().decode(decrypted);
128
+ }
129
+
130
+ //#endregion
131
+ //#region src/signing-keys.ts
132
+ async function generateSigningKeyPair(extractable = false) {
133
+ return crypto.subtle.generateKey("Ed25519", extractable, ["sign", "verify"]);
134
+ }
135
+ async function exportPublicKey(key) {
136
+ const spki = await crypto.subtle.exportKey("spki", key);
137
+ return Buffer.from(spki).toString("base64");
138
+ }
139
+ async function importPublicKey(spkiB64, extractable = false) {
140
+ const spkiDer = Buffer.from(spkiB64, "base64");
141
+ return await crypto.subtle.importKey("spki", spkiDer, "Ed25519", extractable, ["verify"]);
142
+ }
143
+ async function exportPrivateKey(key) {
144
+ const pkcs8 = await crypto.subtle.exportKey("pkcs8", key);
145
+ return Buffer.from(pkcs8).toString("base64");
146
+ }
147
+ async function importPrivateKey(pkcs8B64, extractable = false) {
148
+ return await crypto.subtle.importKey("pkcs8", Buffer.from(pkcs8B64, "base64"), "Ed25519", extractable, ["sign"]);
149
+ }
150
+ async function sign(message, privateKey) {
151
+ const data = new TextEncoder().encode(message);
152
+ const signature = await crypto.subtle.sign("Ed25519", privateKey, data);
153
+ return Buffer.from(signature).toString("base64");
154
+ }
155
+ async function verify(message, signatureB64, publicKey) {
156
+ const data = new TextEncoder().encode(message);
157
+ const signature = Buffer.from(signatureB64, "base64");
158
+ return crypto.subtle.verify("Ed25519", publicKey, signature, data);
135
159
  }
136
160
 
137
161
  //#endregion
138
- export { decryptData, decryptMasterKey, deriveEncryptionKey, encryptData, encryptMasterKey, generateKeys, generateMasterKey, generateVaultKey, hash, hashObject };
162
+ export { base64ToBytes, bytesToBase64, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey, generateAuthPayload, generateEncryptionKey, generateSigningKeyPair, hashString, importEncryptionKey, importPrivateKey, importPublicKey, makeAuthHeader, makeCanonicalString, parseAuthHeader, sign, verify };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byearlybird/crypto",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "Lightweight E2EE toolkit for web apps - zero dependencies + vault key security",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,8 +19,8 @@
19
19
  "scripts": {
20
20
  "build": "bun run build.ts",
21
21
  "prepublishOnly": "bun run build.ts",
22
- "patch": "bun pm patch",
23
- "minor": "bun pm minor",
22
+ "patch": "bun pm version patch",
23
+ "minor": "bun pm version minor",
24
24
  "publish": "bun pm publish"
25
25
  },
26
26
  "devDependencies": {