@byearlybird/crypto 0.1.0 → 0.3.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/README.md +168 -113
- package/dist/index.d.mts +24 -11
- package/dist/index.mjs +121 -69
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,208 +10,263 @@ npm install @byearlybird/crypto
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Encrypt & Decrypt Data
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import {
|
|
16
|
+
import { generateEncryptionKey, encrypt, decrypt } from '@byearlybird/crypto';
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const { vaultKey, masterKey, encryptedMasterKey } = await generateKeys();
|
|
18
|
+
const key = await generateEncryptionKey();
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Encrypt user data
|
|
25
|
-
const encrypted = await encryptData(JSON.stringify({ secret: 'data' }), masterKey);
|
|
26
|
-
|
|
27
|
-
// Store on server: encryptedMasterKey + encrypted data
|
|
28
|
-
await saveToServer({ encryptedMasterKey, data: encrypted });
|
|
20
|
+
const ciphertext = await encrypt('secret data', key);
|
|
21
|
+
const plaintext = await decrypt(ciphertext, key);
|
|
29
22
|
```
|
|
30
23
|
|
|
31
|
-
###
|
|
24
|
+
### Sign & Verify Messages
|
|
32
25
|
|
|
33
26
|
```typescript
|
|
34
|
-
import {
|
|
27
|
+
import { generateSigningKeyPair, sign, verify, exportPublicKey } from '@byearlybird/crypto';
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
const vaultKey = getUserInput();
|
|
29
|
+
const { publicKey, privateKey } = await generateSigningKeyPair(true);
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
const
|
|
31
|
+
const signature = await sign('hello', privateKey);
|
|
32
|
+
const valid = await verify('hello', signature, publicKey);
|
|
33
|
+
```
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
### Signature-Based Auth (eba1)
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {
|
|
39
|
+
generateSigningKeyPair,
|
|
40
|
+
exportPublicKey,
|
|
41
|
+
deriveVaultId,
|
|
42
|
+
createAuthHeader,
|
|
43
|
+
verifyAuthHeader,
|
|
44
|
+
} from '@byearlybird/crypto';
|
|
45
|
+
|
|
46
|
+
const { publicKey, privateKey } = await generateSigningKeyPair(true);
|
|
47
|
+
const pubB64 = await exportPublicKey(publicKey);
|
|
48
|
+
const vaultId = await deriveVaultId(pubB64);
|
|
49
|
+
|
|
50
|
+
// Client: create a signed auth header
|
|
51
|
+
const header = await createAuthHeader({
|
|
52
|
+
vaultId,
|
|
53
|
+
method: 'POST',
|
|
54
|
+
pathWithQuery: '/api/data?page=1',
|
|
55
|
+
serializedBody: JSON.stringify({ hello: 'world' }),
|
|
56
|
+
privateKey,
|
|
57
|
+
});
|
|
58
|
+
// header: "eba1 vid=...;n=...;m=POST;p=/api/data?page=1;t=...;sig=...;bh=..."
|
|
59
|
+
|
|
60
|
+
// Server: verify the header
|
|
61
|
+
const valid = await verifyAuthHeader(header, publicKey);
|
|
45
62
|
```
|
|
46
63
|
|
|
47
64
|
## API Reference
|
|
48
65
|
|
|
49
|
-
###
|
|
66
|
+
### Encryption Keys
|
|
67
|
+
|
|
68
|
+
#### `generateEncryptionKey(extractable?)`
|
|
50
69
|
```typescript
|
|
51
|
-
function
|
|
52
|
-
vaultKey: string;
|
|
53
|
-
masterKey: CryptoKey;
|
|
54
|
-
encryptedMasterKey: string;
|
|
55
|
-
}>
|
|
70
|
+
function generateEncryptionKey(extractable?: boolean): Promise<CryptoKey>
|
|
56
71
|
```
|
|
72
|
+
Generates a random AES-256-GCM encryption key. `extractable` defaults to `false`.
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
#### `exportEncryptionKey(key)`
|
|
77
|
+
```typescript
|
|
78
|
+
function exportEncryptionKey(key: CryptoKey): Promise<string>
|
|
79
|
+
```
|
|
80
|
+
Exports an encryption key to a base64 string (raw format).
|
|
59
81
|
|
|
60
82
|
---
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
#### `importEncryptionKey(base64, extractable?)`
|
|
63
85
|
```typescript
|
|
64
|
-
function
|
|
86
|
+
function importEncryptionKey(base64: string, extractable?: boolean): Promise<CryptoKey>
|
|
65
87
|
```
|
|
88
|
+
Imports a base64-encoded encryption key. `extractable` defaults to `false`.
|
|
89
|
+
|
|
90
|
+
---
|
|
66
91
|
|
|
67
|
-
|
|
92
|
+
#### `encrypt(plaintext, key)`
|
|
93
|
+
```typescript
|
|
94
|
+
function encrypt(plaintext: string, key: CryptoKey): Promise<string>
|
|
95
|
+
```
|
|
96
|
+
Encrypts a string with AES-256-GCM. Returns base64 payload (12-byte IV + ciphertext). Fresh random IV per call.
|
|
68
97
|
|
|
69
98
|
---
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
#### `decrypt(encoded, key)`
|
|
72
101
|
```typescript
|
|
73
|
-
function
|
|
102
|
+
function decrypt(encoded: string, key: CryptoKey): Promise<string>
|
|
74
103
|
```
|
|
104
|
+
Decrypts an AES-256-GCM encrypted payload. Throws if the key is wrong or data is tampered with.
|
|
105
|
+
|
|
106
|
+
---
|
|
75
107
|
|
|
76
|
-
|
|
108
|
+
### Signing Keys
|
|
109
|
+
|
|
110
|
+
#### `generateSigningKeyPair(extractable?)`
|
|
111
|
+
```typescript
|
|
112
|
+
function generateSigningKeyPair(extractable?: boolean): Promise<CryptoKeyPair>
|
|
113
|
+
```
|
|
114
|
+
Generates an Ed25519 signing key pair. `extractable` defaults to `false`.
|
|
77
115
|
|
|
78
116
|
---
|
|
79
117
|
|
|
80
|
-
|
|
118
|
+
#### `exportPublicKey(key)`
|
|
81
119
|
```typescript
|
|
82
|
-
function
|
|
83
|
-
masterKey: CryptoKey,
|
|
84
|
-
vaultKey: string
|
|
85
|
-
): Promise<string>
|
|
120
|
+
function exportPublicKey(key: CryptoKey): Promise<string>
|
|
86
121
|
```
|
|
122
|
+
Exports a public key to base64 (SPKI format).
|
|
123
|
+
|
|
124
|
+
---
|
|
87
125
|
|
|
88
|
-
|
|
126
|
+
#### `importPublicKey(spkiB64, extractable?)`
|
|
127
|
+
```typescript
|
|
128
|
+
function importPublicKey(spkiB64: string, extractable?: boolean): Promise<CryptoKey>
|
|
129
|
+
```
|
|
130
|
+
Imports a base64-encoded SPKI public key. `extractable` defaults to `false`.
|
|
89
131
|
|
|
90
132
|
---
|
|
91
133
|
|
|
92
|
-
|
|
134
|
+
#### `exportPrivateKey(key)`
|
|
93
135
|
```typescript
|
|
94
|
-
function
|
|
95
|
-
encryptedMasterKey: string,
|
|
96
|
-
vaultKey: string
|
|
97
|
-
): Promise<CryptoKey>
|
|
136
|
+
function exportPrivateKey(key: CryptoKey): Promise<string>
|
|
98
137
|
```
|
|
138
|
+
Exports a private key to base64 (PKCS8 format).
|
|
139
|
+
|
|
140
|
+
---
|
|
99
141
|
|
|
100
|
-
|
|
142
|
+
#### `importPrivateKey(pkcs8B64, extractable?)`
|
|
143
|
+
```typescript
|
|
144
|
+
function importPrivateKey(pkcs8B64: string, extractable?: boolean): Promise<CryptoKey>
|
|
145
|
+
```
|
|
146
|
+
Imports a base64-encoded PKCS8 private key. `extractable` defaults to `false`.
|
|
101
147
|
|
|
102
148
|
---
|
|
103
149
|
|
|
104
|
-
|
|
150
|
+
#### `sign(message, privateKey)`
|
|
105
151
|
```typescript
|
|
106
|
-
function
|
|
107
|
-
data: string,
|
|
108
|
-
masterKey: CryptoKey
|
|
109
|
-
): Promise<string>
|
|
152
|
+
function sign(message: string, privateKey: CryptoKey): Promise<string>
|
|
110
153
|
```
|
|
154
|
+
Signs a message using Ed25519. Returns a base64-encoded signature.
|
|
155
|
+
|
|
156
|
+
---
|
|
111
157
|
|
|
112
|
-
|
|
158
|
+
#### `verify(message, signatureB64, publicKey)`
|
|
159
|
+
```typescript
|
|
160
|
+
function verify(message: string, signatureB64: string, publicKey: CryptoKey): Promise<boolean>
|
|
161
|
+
```
|
|
162
|
+
Verifies an Ed25519 signature. Returns `true` if valid, `false` otherwise.
|
|
113
163
|
|
|
114
164
|
---
|
|
115
165
|
|
|
116
|
-
###
|
|
166
|
+
### Authentication (eba1)
|
|
167
|
+
|
|
168
|
+
#### `AuthPayload`
|
|
117
169
|
```typescript
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
type AuthPayload = {
|
|
171
|
+
scheme: string;
|
|
172
|
+
nonce: string;
|
|
173
|
+
vaultId: string;
|
|
174
|
+
method: string;
|
|
175
|
+
pathWithQuery: string;
|
|
176
|
+
timestamp: number;
|
|
177
|
+
bodyHash?: string;
|
|
178
|
+
}
|
|
122
179
|
```
|
|
123
180
|
|
|
124
|
-
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
#### `deriveVaultId(publicKeyB64)`
|
|
184
|
+
```typescript
|
|
185
|
+
function deriveVaultId(publicKeyB64: string): Promise<string>
|
|
186
|
+
```
|
|
187
|
+
Derives a 32-character hex vault ID from a base64-encoded public key (SHA-256 of SPKI bytes, truncated).
|
|
125
188
|
|
|
126
189
|
---
|
|
127
190
|
|
|
128
|
-
|
|
191
|
+
#### `parseAuthHeader(header)`
|
|
129
192
|
```typescript
|
|
130
|
-
function
|
|
131
|
-
vaultKey: string,
|
|
132
|
-
salt?: Uint8Array
|
|
133
|
-
): Promise<{ key: CryptoKey; salt: Uint8Array }>
|
|
193
|
+
function parseAuthHeader(header: string): AuthPayload & { signature: string }
|
|
134
194
|
```
|
|
195
|
+
Parses an eba1 auth header string back into its components. Throws on malformed or unsupported headers.
|
|
135
196
|
|
|
136
|
-
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
#### `createAuthHeader(args)`
|
|
200
|
+
```typescript
|
|
201
|
+
function createAuthHeader(args: {
|
|
202
|
+
vaultId: string;
|
|
203
|
+
method: 'GET' | 'PUT' | 'POST' | 'PATCH' | 'DELETE';
|
|
204
|
+
pathWithQuery: string;
|
|
205
|
+
serializedBody?: string;
|
|
206
|
+
privateKey: CryptoKey;
|
|
207
|
+
}): Promise<string>
|
|
208
|
+
```
|
|
209
|
+
Creates a complete signed auth header. Generates a nonce/timestamp, hashes the body if provided, signs the canonical string, and returns the formatted header.
|
|
137
210
|
|
|
138
211
|
---
|
|
139
212
|
|
|
140
|
-
|
|
213
|
+
#### `verifyAuthHeader(header, publicKey)`
|
|
141
214
|
```typescript
|
|
142
|
-
function
|
|
215
|
+
function verifyAuthHeader(header: string, publicKey: CryptoKey): Promise<boolean>
|
|
143
216
|
```
|
|
217
|
+
Verifies an eba1 auth header against a public key. Parses the header, reconstructs the canonical string, and verifies the signature. Returns `true` if valid.
|
|
144
218
|
|
|
145
|
-
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Utilities
|
|
222
|
+
|
|
223
|
+
#### `bytesToBase64(bytes)`
|
|
224
|
+
```typescript
|
|
225
|
+
function bytesToBase64(bytes: Uint8Array): string
|
|
226
|
+
```
|
|
227
|
+
Converts a byte array to a base64 string.
|
|
146
228
|
|
|
147
229
|
---
|
|
148
230
|
|
|
149
|
-
|
|
231
|
+
#### `base64ToBytes(base64)`
|
|
150
232
|
```typescript
|
|
151
|
-
function
|
|
233
|
+
function base64ToBytes(base64: string): Uint8Array
|
|
152
234
|
```
|
|
235
|
+
Converts a base64 string to a byte array.
|
|
153
236
|
|
|
154
|
-
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
#### `hashString(value)`
|
|
240
|
+
```typescript
|
|
241
|
+
function hashString(value: string): Promise<string>
|
|
242
|
+
```
|
|
243
|
+
Computes SHA-256 hash of a string. Returns hex-encoded digest (64 chars).
|
|
155
244
|
|
|
156
245
|
## Security
|
|
157
246
|
|
|
158
247
|
**Cryptographic primitives:**
|
|
159
248
|
- AES-256-GCM (authenticated encryption)
|
|
160
|
-
-
|
|
249
|
+
- Ed25519 (digital signatures)
|
|
161
250
|
- SHA-256 (content hashing)
|
|
162
|
-
-
|
|
251
|
+
- 96-bit IVs
|
|
163
252
|
- `crypto.getRandomValues()` for all randomness
|
|
164
253
|
|
|
165
254
|
**E2EE model:**
|
|
166
255
|
- All encryption happens client-side
|
|
167
256
|
- Server only sees ciphertext
|
|
168
|
-
- Vault key never leaves client
|
|
169
257
|
- Fresh IV per encryption (no reuse)
|
|
170
|
-
- Self-contained ciphertexts (IVs
|
|
171
|
-
|
|
172
|
-
**Important:**
|
|
173
|
-
- Users must save vault keys securely (no recovery if lost)
|
|
174
|
-
- Use HTTPS to prevent code injection
|
|
258
|
+
- Self-contained ciphertexts (IVs embedded)
|
|
175
259
|
- GCM authentication detects tampering
|
|
176
|
-
- Vault keys are case-insensitive (normalized to lowercase)
|
|
177
|
-
|
|
178
|
-
## Examples
|
|
179
|
-
|
|
180
|
-
### Password Change
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// Re-encrypt master key with new vault key
|
|
184
|
-
const masterKey = await decryptMasterKey(encrypted, oldVaultKey);
|
|
185
|
-
const newEncrypted = await encryptMasterKey(masterKey, newVaultKey);
|
|
186
|
-
// Update on server (user data doesn't need re-encryption)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Multiple Items
|
|
190
260
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
// Each has different ciphertext (fresh IV)
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Hashing
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
import { hash, hashObject } from '@byearlybird/crypto';
|
|
204
|
-
|
|
205
|
-
// Hash a string
|
|
206
|
-
const digest = await hash('hello world');
|
|
207
|
-
|
|
208
|
-
// Hash an object (must be JSON serializable)
|
|
209
|
-
const objHash = await hashObject({ user: 'alice', id: 123 });
|
|
210
|
-
```
|
|
261
|
+
**Signature-based auth:**
|
|
262
|
+
- Ed25519 key pairs for request signing
|
|
263
|
+
- Nonce + timestamp prevent replay attacks
|
|
264
|
+
- Optional body hash ensures payload integrity
|
|
265
|
+
- Canonical string format for deterministic signing
|
|
211
266
|
|
|
212
267
|
## Browser Compatibility
|
|
213
268
|
|
|
214
|
-
Requires Web Crypto API (Chrome
|
|
269
|
+
Requires Web Crypto API and Ed25519 support (Chrome 113+, Firefox 130+, Safari 17+).
|
|
215
270
|
|
|
216
271
|
## License
|
|
217
272
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as crypto0 from "crypto";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/auth.d.ts
|
|
4
4
|
type AuthPayload = {
|
|
5
5
|
scheme: string;
|
|
6
6
|
nonce: string;
|
|
@@ -10,27 +10,40 @@ type AuthPayload = {
|
|
|
10
10
|
timestamp: number;
|
|
11
11
|
bodyHash?: string;
|
|
12
12
|
};
|
|
13
|
+
type ValidateSuccess = {
|
|
14
|
+
ok: true;
|
|
15
|
+
data: AuthPayload;
|
|
16
|
+
};
|
|
17
|
+
type ValidateFailure = {
|
|
18
|
+
ok: false;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
type ValidateResult = ValidateSuccess | ValidateFailure;
|
|
13
22
|
declare function deriveVaultId(publicKeyB64: string): Promise<string>;
|
|
14
|
-
declare function
|
|
23
|
+
declare function createAuthHeader(args: {
|
|
15
24
|
vaultId: string;
|
|
16
25
|
method: "GET" | "PUT" | "POST" | "PATCH" | "DELETE";
|
|
17
26
|
pathWithQuery: string;
|
|
18
27
|
serializedBody?: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
declare function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
privateKey: CryptoKey;
|
|
29
|
+
}): Promise<string>;
|
|
30
|
+
declare function validateAuthHeader(header: string, options: {
|
|
31
|
+
publicKey: CryptoKey;
|
|
32
|
+
vaultId: string;
|
|
33
|
+
method: string;
|
|
34
|
+
path: string;
|
|
35
|
+
ttl: number;
|
|
36
|
+
body?: string;
|
|
37
|
+
}): Promise<ValidateResult>;
|
|
25
38
|
//#endregion
|
|
26
|
-
//#region src/encryption
|
|
39
|
+
//#region src/encryption.d.ts
|
|
27
40
|
declare function generateEncryptionKey(extractable?: boolean): Promise<crypto0.webcrypto.CryptoKey>;
|
|
28
41
|
declare function exportEncryptionKey(key: CryptoKey): Promise<string>;
|
|
29
42
|
declare function importEncryptionKey(base64: string, extractable?: boolean): Promise<crypto0.webcrypto.CryptoKey>;
|
|
30
43
|
declare function encrypt(plaintext: string, key: CryptoKey): Promise<string>;
|
|
31
44
|
declare function decrypt(encoded: string, key: CryptoKey): Promise<string>;
|
|
32
45
|
//#endregion
|
|
33
|
-
//#region src/signing
|
|
46
|
+
//#region src/signing.d.ts
|
|
34
47
|
declare function generateSigningKeyPair(extractable?: boolean): Promise<CryptoKeyPair>;
|
|
35
48
|
declare function exportPublicKey(key: CryptoKey): Promise<string>;
|
|
36
49
|
declare function importPublicKey(spkiB64: string, extractable?: boolean): Promise<CryptoKey>;
|
|
@@ -44,4 +57,4 @@ declare function bytesToBase64(bytes: Uint8Array): string;
|
|
|
44
57
|
declare function base64ToBytes(base64: string): Uint8Array;
|
|
45
58
|
declare function hashString(value: string): Promise<string>;
|
|
46
59
|
//#endregion
|
|
47
|
-
export { AuthPayload, base64ToBytes, bytesToBase64, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey,
|
|
60
|
+
export { type AuthPayload, type ValidateFailure, type ValidateResult, type ValidateSuccess, base64ToBytes, bytesToBase64, createAuthHeader, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey, generateEncryptionKey, generateSigningKeyPair, hashString, importEncryptionKey, importPrivateKey, importPublicKey, sign, validateAuthHeader, verify };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
//#region src/signing.ts
|
|
2
|
+
async function generateSigningKeyPair(extractable = false) {
|
|
3
|
+
return await crypto.subtle.generateKey("Ed25519", extractable, ["sign", "verify"]);
|
|
4
|
+
}
|
|
5
|
+
async function exportPublicKey(key) {
|
|
6
|
+
const spki = await crypto.subtle.exportKey("spki", key);
|
|
7
|
+
return Buffer.from(spki).toString("base64");
|
|
8
|
+
}
|
|
9
|
+
async function importPublicKey(spkiB64, extractable = false) {
|
|
10
|
+
const spkiDer = Buffer.from(spkiB64, "base64");
|
|
11
|
+
return await crypto.subtle.importKey("spki", spkiDer, "Ed25519", extractable, ["verify"]);
|
|
12
|
+
}
|
|
13
|
+
async function exportPrivateKey(key) {
|
|
14
|
+
const pkcs8 = await crypto.subtle.exportKey("pkcs8", key);
|
|
15
|
+
return Buffer.from(pkcs8).toString("base64");
|
|
16
|
+
}
|
|
17
|
+
async function importPrivateKey(pkcs8B64, extractable = false) {
|
|
18
|
+
return await crypto.subtle.importKey("pkcs8", Buffer.from(pkcs8B64, "base64"), "Ed25519", extractable, ["sign"]);
|
|
19
|
+
}
|
|
20
|
+
async function sign(message, privateKey) {
|
|
21
|
+
const data = new TextEncoder().encode(message);
|
|
22
|
+
const signature = await crypto.subtle.sign("Ed25519", privateKey, data);
|
|
23
|
+
return Buffer.from(signature).toString("base64");
|
|
24
|
+
}
|
|
25
|
+
async function verify(message, signatureB64, publicKey) {
|
|
26
|
+
const data = new TextEncoder().encode(message);
|
|
27
|
+
const signature = Buffer.from(signatureB64, "base64");
|
|
28
|
+
return crypto.subtle.verify("Ed25519", publicKey, signature, data);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
1
32
|
//#region src/utils.ts
|
|
2
33
|
function bytesToBase64(bytes) {
|
|
3
34
|
return Buffer.from(bytes).toString("base64");
|
|
@@ -11,13 +42,99 @@ async function hashString(value) {
|
|
|
11
42
|
}
|
|
12
43
|
|
|
13
44
|
//#endregion
|
|
14
|
-
//#region src/
|
|
15
|
-
const CURRENT_SCHEME = "
|
|
45
|
+
//#region src/auth.ts
|
|
46
|
+
const CURRENT_SCHEME = "eba1";
|
|
16
47
|
async function deriveVaultId(publicKeyB64) {
|
|
17
48
|
const spkiBytes = Buffer.from(publicKeyB64, "base64");
|
|
18
49
|
const hash = await crypto.subtle.digest("SHA-256", spkiBytes);
|
|
19
50
|
return Buffer.from(hash).toString("hex").slice(0, 32);
|
|
20
51
|
}
|
|
52
|
+
async function createAuthHeader(args) {
|
|
53
|
+
const { privateKey,...payloadArgs } = args;
|
|
54
|
+
const payload = await generateAuthPayload(payloadArgs);
|
|
55
|
+
return makeAuthHeader(payload, await sign(makeCanonicalString(payload), privateKey));
|
|
56
|
+
}
|
|
57
|
+
async function validateAuthHeader(header, options) {
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = parseAuthHeader(header);
|
|
61
|
+
} catch {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
message: "Malformed auth header"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const { signature,...payload } = parsed;
|
|
68
|
+
if (parsed.vaultId !== options.vaultId) return {
|
|
69
|
+
ok: false,
|
|
70
|
+
message: "Vault id mismatch"
|
|
71
|
+
};
|
|
72
|
+
if (parsed.method !== options.method) return {
|
|
73
|
+
ok: false,
|
|
74
|
+
message: "Method mismatch"
|
|
75
|
+
};
|
|
76
|
+
if (parsed.pathWithQuery !== options.path) return {
|
|
77
|
+
ok: false,
|
|
78
|
+
message: "Path mismatch"
|
|
79
|
+
};
|
|
80
|
+
if (Math.abs(Date.now() - parsed.timestamp) > options.ttl) return {
|
|
81
|
+
ok: false,
|
|
82
|
+
message: "Expired"
|
|
83
|
+
};
|
|
84
|
+
if (parsed.bodyHash) {
|
|
85
|
+
if (!options.body) return {
|
|
86
|
+
ok: false,
|
|
87
|
+
message: "Body hash mismatch"
|
|
88
|
+
};
|
|
89
|
+
const serverHash = await hashString(options.body);
|
|
90
|
+
if (parsed.bodyHash !== serverHash) return {
|
|
91
|
+
ok: false,
|
|
92
|
+
message: "Body hash mismatch"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!await verify(makeCanonicalString(payload), signature, options.publicKey)) return {
|
|
96
|
+
ok: false,
|
|
97
|
+
message: "Invalid signature"
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
ok: true,
|
|
101
|
+
data: payload
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function parseAuthHeader(header) {
|
|
105
|
+
const spaceIdx = header.indexOf(" ");
|
|
106
|
+
if (spaceIdx === -1) throw new Error("Malformed auth header: missing scheme separator");
|
|
107
|
+
const scheme = header.slice(0, spaceIdx);
|
|
108
|
+
if (scheme !== CURRENT_SCHEME) throw new Error(`Unsupported auth scheme: ${scheme}`);
|
|
109
|
+
const paramStr = header.slice(spaceIdx + 1);
|
|
110
|
+
const params = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const part of paramStr.split(";")) {
|
|
112
|
+
const eqIdx = part.indexOf("=");
|
|
113
|
+
if (eqIdx === -1) throw new Error(`Malformed auth header param: ${part}`);
|
|
114
|
+
params.set(part.slice(0, eqIdx), part.slice(eqIdx + 1));
|
|
115
|
+
}
|
|
116
|
+
const vid = params.get("vid");
|
|
117
|
+
const n = params.get("n");
|
|
118
|
+
const m = params.get("m");
|
|
119
|
+
const p = params.get("p");
|
|
120
|
+
const t = params.get("t");
|
|
121
|
+
const sig = params.get("sig");
|
|
122
|
+
if (!vid || !n || !m || !p || !t || !sig) throw new Error("Malformed auth header: missing required params");
|
|
123
|
+
const timestamp = Number(t);
|
|
124
|
+
if (Number.isNaN(timestamp)) throw new Error("Malformed auth header: timestamp is not a number");
|
|
125
|
+
const result = {
|
|
126
|
+
scheme,
|
|
127
|
+
vaultId: vid,
|
|
128
|
+
nonce: n,
|
|
129
|
+
method: m,
|
|
130
|
+
pathWithQuery: p,
|
|
131
|
+
timestamp,
|
|
132
|
+
signature: sig
|
|
133
|
+
};
|
|
134
|
+
const bh = params.get("bh");
|
|
135
|
+
if (bh) result.bodyHash = bh;
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
21
138
|
async function generateAuthPayload(args) {
|
|
22
139
|
const { serializedBody,...rest } = args;
|
|
23
140
|
const nonce = crypto.randomUUID();
|
|
@@ -55,43 +172,9 @@ function makeAuthHeader(payload, signature) {
|
|
|
55
172
|
if (payload.bodyHash) params.push(`bh=${payload.bodyHash}`);
|
|
56
173
|
return `${payload.scheme} ${params.join(";")}`;
|
|
57
174
|
}
|
|
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
|
-
}
|
|
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;
|
|
91
|
-
}
|
|
92
175
|
|
|
93
176
|
//#endregion
|
|
94
|
-
//#region src/encryption
|
|
177
|
+
//#region src/encryption.ts
|
|
95
178
|
async function generateEncryptionKey(extractable = false) {
|
|
96
179
|
return crypto.subtle.generateKey({
|
|
97
180
|
name: "AES-GCM",
|
|
@@ -128,35 +211,4 @@ async function decrypt(encoded, key) {
|
|
|
128
211
|
}
|
|
129
212
|
|
|
130
213
|
//#endregion
|
|
131
|
-
|
|
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);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
//#endregion
|
|
162
|
-
export { base64ToBytes, bytesToBase64, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey, generateAuthPayload, generateEncryptionKey, generateSigningKeyPair, hashString, importEncryptionKey, importPrivateKey, importPublicKey, makeAuthHeader, makeCanonicalString, parseAuthHeader, sign, verify };
|
|
214
|
+
export { base64ToBytes, bytesToBase64, createAuthHeader, decrypt, deriveVaultId, encrypt, exportEncryptionKey, exportPrivateKey, exportPublicKey, generateEncryptionKey, generateSigningKeyPair, hashString, importEncryptionKey, importPrivateKey, importPublicKey, sign, validateAuthHeader, verify };
|