@coder/mux-md-client 0.1.0-main.13 → 0.1.0-main.15
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.cjs +146 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -79
- package/dist/index.d.ts +61 -79
- package/dist/index.js +146 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -26,7 +26,6 @@ interface SignatureEnvelope {
|
|
|
26
26
|
sig: string;
|
|
27
27
|
publicKey: string;
|
|
28
28
|
githubUser?: string;
|
|
29
|
-
email?: string;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
31
|
* Signed content payload (decrypted).
|
|
@@ -45,13 +44,25 @@ interface SignedPayload {
|
|
|
45
44
|
* Works in both Node.js (with webcrypto) and browser environments.
|
|
46
45
|
*/
|
|
47
46
|
|
|
47
|
+
/** Options for signing content during upload */
|
|
48
|
+
interface SignOptions {
|
|
49
|
+
/** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
|
|
50
|
+
privateKey: Uint8Array;
|
|
51
|
+
/** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
|
|
52
|
+
publicKey: string;
|
|
53
|
+
/** Optional GitHub username for attribution */
|
|
54
|
+
githubUser?: string;
|
|
55
|
+
}
|
|
48
56
|
interface UploadOptions {
|
|
49
57
|
/** Base URL of the mux.md service */
|
|
50
58
|
baseUrl?: string;
|
|
51
59
|
/** Expiration time (unix timestamp ms, ISO date string, or Date object) */
|
|
52
60
|
expiresAt?: number | string | Date;
|
|
53
|
-
/**
|
|
54
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Sign the content with the provided credentials.
|
|
63
|
+
* The library handles creating the signature envelope internally.
|
|
64
|
+
*/
|
|
65
|
+
sign?: SignOptions;
|
|
55
66
|
}
|
|
56
67
|
interface UploadResult {
|
|
57
68
|
/** Full URL with encryption key in fragment */
|
|
@@ -151,81 +162,6 @@ interface SetExpirationResult {
|
|
|
151
162
|
*/
|
|
152
163
|
declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
|
|
153
164
|
|
|
154
|
-
/**
|
|
155
|
-
* Signing and verification utilities for mux.md
|
|
156
|
-
*
|
|
157
|
-
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
/** Supported key types */
|
|
161
|
-
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
162
|
-
/** Parsed public key with type and raw bytes */
|
|
163
|
-
interface ParsedPublicKey {
|
|
164
|
-
type: KeyType;
|
|
165
|
-
keyBytes: Uint8Array;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Parse an SSH public key and extract the key bytes and type.
|
|
169
|
-
* Supports formats:
|
|
170
|
-
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
171
|
-
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
172
|
-
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
173
|
-
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
174
|
-
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
175
|
-
*/
|
|
176
|
-
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
177
|
-
/**
|
|
178
|
-
* Sign a message with Ed25519 private key.
|
|
179
|
-
* @param message - The message bytes to sign
|
|
180
|
-
* @param privateKey - 32-byte Ed25519 private key
|
|
181
|
-
* @returns Base64-encoded signature (64 bytes)
|
|
182
|
-
*/
|
|
183
|
-
declare function signEd25519(message: Uint8Array, privateKey: Uint8Array): Promise<string>;
|
|
184
|
-
/**
|
|
185
|
-
* Sign a message with ECDSA private key (P-256/384/521).
|
|
186
|
-
* @param message - The message bytes to sign (will be hashed)
|
|
187
|
-
* @param privateKey - ECDSA private key bytes
|
|
188
|
-
* @param curve - Which curve to use
|
|
189
|
-
* @returns Base64-encoded signature
|
|
190
|
-
*/
|
|
191
|
-
declare function signECDSA(message: Uint8Array, privateKey: Uint8Array, curve: 'p256' | 'p384' | 'p521'): string;
|
|
192
|
-
/**
|
|
193
|
-
* Helper: Create a SignatureEnvelope from content + private key.
|
|
194
|
-
* This is the high-level API for signing before upload.
|
|
195
|
-
*
|
|
196
|
-
* @param content - The content bytes to sign
|
|
197
|
-
* @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)
|
|
198
|
-
* @param publicKey - SSH format public key string (e.g., "ssh-ed25519 AAAA...")
|
|
199
|
-
* @param options - Optional email or GitHub username for attribution
|
|
200
|
-
* @returns SignatureEnvelope ready for upload
|
|
201
|
-
*/
|
|
202
|
-
declare function createSignatureEnvelope(content: Uint8Array, privateKey: Uint8Array, publicKey: string, options?: {
|
|
203
|
-
email?: string;
|
|
204
|
-
githubUser?: string;
|
|
205
|
-
}): Promise<SignatureEnvelope>;
|
|
206
|
-
/**
|
|
207
|
-
* Verify a signature using the appropriate algorithm based on key type.
|
|
208
|
-
* For Ed25519: signature is raw 64 bytes
|
|
209
|
-
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
210
|
-
*
|
|
211
|
-
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
212
|
-
* @param message - Original message bytes
|
|
213
|
-
* @param signature - Signature bytes (not base64)
|
|
214
|
-
* @returns true if signature is valid
|
|
215
|
-
*/
|
|
216
|
-
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
217
|
-
/**
|
|
218
|
-
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
219
|
-
* @param publicKey - Raw public key bytes
|
|
220
|
-
* @returns Fingerprint string like "SHA256:abc123..."
|
|
221
|
-
*/
|
|
222
|
-
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
223
|
-
/**
|
|
224
|
-
* Format a fingerprint for nice display.
|
|
225
|
-
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
226
|
-
*/
|
|
227
|
-
declare function formatFingerprint(fingerprint: string): string;
|
|
228
|
-
|
|
229
165
|
/**
|
|
230
166
|
* Generate a random file ID (30 bits, 5 chars base62)
|
|
231
167
|
*/
|
|
@@ -280,4 +216,50 @@ declare function base64Encode(data: Uint8Array): string;
|
|
|
280
216
|
*/
|
|
281
217
|
declare function base64Decode(str: string): Uint8Array;
|
|
282
218
|
|
|
283
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Signing and verification utilities for mux.md
|
|
221
|
+
*
|
|
222
|
+
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
/** Supported key types */
|
|
226
|
+
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
227
|
+
/** Parsed public key with type and raw bytes */
|
|
228
|
+
interface ParsedPublicKey {
|
|
229
|
+
type: KeyType;
|
|
230
|
+
keyBytes: Uint8Array;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Parse an SSH public key and extract the key bytes and type.
|
|
234
|
+
* Supports formats:
|
|
235
|
+
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
236
|
+
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
237
|
+
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
238
|
+
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
239
|
+
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
240
|
+
*/
|
|
241
|
+
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
242
|
+
/**
|
|
243
|
+
* Verify a signature using the appropriate algorithm based on key type.
|
|
244
|
+
* For Ed25519: signature is raw 64 bytes
|
|
245
|
+
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
246
|
+
*
|
|
247
|
+
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
248
|
+
* @param message - Original message bytes
|
|
249
|
+
* @param signature - Signature bytes (not base64)
|
|
250
|
+
* @returns true if signature is valid
|
|
251
|
+
*/
|
|
252
|
+
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
253
|
+
/**
|
|
254
|
+
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
255
|
+
* @param publicKey - Raw public key bytes
|
|
256
|
+
* @returns Fingerprint string like "SHA256:abc123..."
|
|
257
|
+
*/
|
|
258
|
+
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
259
|
+
/**
|
|
260
|
+
* Format a fingerprint for nice display.
|
|
261
|
+
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
262
|
+
*/
|
|
263
|
+
declare function formatFingerprint(fingerprint: string): string;
|
|
264
|
+
|
|
265
|
+
export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignOptions, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, upload, verifySignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ interface SignatureEnvelope {
|
|
|
26
26
|
sig: string;
|
|
27
27
|
publicKey: string;
|
|
28
28
|
githubUser?: string;
|
|
29
|
-
email?: string;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
31
|
* Signed content payload (decrypted).
|
|
@@ -45,13 +44,25 @@ interface SignedPayload {
|
|
|
45
44
|
* Works in both Node.js (with webcrypto) and browser environments.
|
|
46
45
|
*/
|
|
47
46
|
|
|
47
|
+
/** Options for signing content during upload */
|
|
48
|
+
interface SignOptions {
|
|
49
|
+
/** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
|
|
50
|
+
privateKey: Uint8Array;
|
|
51
|
+
/** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
|
|
52
|
+
publicKey: string;
|
|
53
|
+
/** Optional GitHub username for attribution */
|
|
54
|
+
githubUser?: string;
|
|
55
|
+
}
|
|
48
56
|
interface UploadOptions {
|
|
49
57
|
/** Base URL of the mux.md service */
|
|
50
58
|
baseUrl?: string;
|
|
51
59
|
/** Expiration time (unix timestamp ms, ISO date string, or Date object) */
|
|
52
60
|
expiresAt?: number | string | Date;
|
|
53
|
-
/**
|
|
54
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Sign the content with the provided credentials.
|
|
63
|
+
* The library handles creating the signature envelope internally.
|
|
64
|
+
*/
|
|
65
|
+
sign?: SignOptions;
|
|
55
66
|
}
|
|
56
67
|
interface UploadResult {
|
|
57
68
|
/** Full URL with encryption key in fragment */
|
|
@@ -151,81 +162,6 @@ interface SetExpirationResult {
|
|
|
151
162
|
*/
|
|
152
163
|
declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
|
|
153
164
|
|
|
154
|
-
/**
|
|
155
|
-
* Signing and verification utilities for mux.md
|
|
156
|
-
*
|
|
157
|
-
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
/** Supported key types */
|
|
161
|
-
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
162
|
-
/** Parsed public key with type and raw bytes */
|
|
163
|
-
interface ParsedPublicKey {
|
|
164
|
-
type: KeyType;
|
|
165
|
-
keyBytes: Uint8Array;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Parse an SSH public key and extract the key bytes and type.
|
|
169
|
-
* Supports formats:
|
|
170
|
-
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
171
|
-
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
172
|
-
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
173
|
-
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
174
|
-
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
175
|
-
*/
|
|
176
|
-
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
177
|
-
/**
|
|
178
|
-
* Sign a message with Ed25519 private key.
|
|
179
|
-
* @param message - The message bytes to sign
|
|
180
|
-
* @param privateKey - 32-byte Ed25519 private key
|
|
181
|
-
* @returns Base64-encoded signature (64 bytes)
|
|
182
|
-
*/
|
|
183
|
-
declare function signEd25519(message: Uint8Array, privateKey: Uint8Array): Promise<string>;
|
|
184
|
-
/**
|
|
185
|
-
* Sign a message with ECDSA private key (P-256/384/521).
|
|
186
|
-
* @param message - The message bytes to sign (will be hashed)
|
|
187
|
-
* @param privateKey - ECDSA private key bytes
|
|
188
|
-
* @param curve - Which curve to use
|
|
189
|
-
* @returns Base64-encoded signature
|
|
190
|
-
*/
|
|
191
|
-
declare function signECDSA(message: Uint8Array, privateKey: Uint8Array, curve: 'p256' | 'p384' | 'p521'): string;
|
|
192
|
-
/**
|
|
193
|
-
* Helper: Create a SignatureEnvelope from content + private key.
|
|
194
|
-
* This is the high-level API for signing before upload.
|
|
195
|
-
*
|
|
196
|
-
* @param content - The content bytes to sign
|
|
197
|
-
* @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)
|
|
198
|
-
* @param publicKey - SSH format public key string (e.g., "ssh-ed25519 AAAA...")
|
|
199
|
-
* @param options - Optional email or GitHub username for attribution
|
|
200
|
-
* @returns SignatureEnvelope ready for upload
|
|
201
|
-
*/
|
|
202
|
-
declare function createSignatureEnvelope(content: Uint8Array, privateKey: Uint8Array, publicKey: string, options?: {
|
|
203
|
-
email?: string;
|
|
204
|
-
githubUser?: string;
|
|
205
|
-
}): Promise<SignatureEnvelope>;
|
|
206
|
-
/**
|
|
207
|
-
* Verify a signature using the appropriate algorithm based on key type.
|
|
208
|
-
* For Ed25519: signature is raw 64 bytes
|
|
209
|
-
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
210
|
-
*
|
|
211
|
-
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
212
|
-
* @param message - Original message bytes
|
|
213
|
-
* @param signature - Signature bytes (not base64)
|
|
214
|
-
* @returns true if signature is valid
|
|
215
|
-
*/
|
|
216
|
-
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
217
|
-
/**
|
|
218
|
-
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
219
|
-
* @param publicKey - Raw public key bytes
|
|
220
|
-
* @returns Fingerprint string like "SHA256:abc123..."
|
|
221
|
-
*/
|
|
222
|
-
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
223
|
-
/**
|
|
224
|
-
* Format a fingerprint for nice display.
|
|
225
|
-
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
226
|
-
*/
|
|
227
|
-
declare function formatFingerprint(fingerprint: string): string;
|
|
228
|
-
|
|
229
165
|
/**
|
|
230
166
|
* Generate a random file ID (30 bits, 5 chars base62)
|
|
231
167
|
*/
|
|
@@ -280,4 +216,50 @@ declare function base64Encode(data: Uint8Array): string;
|
|
|
280
216
|
*/
|
|
281
217
|
declare function base64Decode(str: string): Uint8Array;
|
|
282
218
|
|
|
283
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Signing and verification utilities for mux.md
|
|
221
|
+
*
|
|
222
|
+
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
/** Supported key types */
|
|
226
|
+
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
227
|
+
/** Parsed public key with type and raw bytes */
|
|
228
|
+
interface ParsedPublicKey {
|
|
229
|
+
type: KeyType;
|
|
230
|
+
keyBytes: Uint8Array;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Parse an SSH public key and extract the key bytes and type.
|
|
234
|
+
* Supports formats:
|
|
235
|
+
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
236
|
+
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
237
|
+
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
238
|
+
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
239
|
+
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
240
|
+
*/
|
|
241
|
+
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
242
|
+
/**
|
|
243
|
+
* Verify a signature using the appropriate algorithm based on key type.
|
|
244
|
+
* For Ed25519: signature is raw 64 bytes
|
|
245
|
+
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
246
|
+
*
|
|
247
|
+
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
248
|
+
* @param message - Original message bytes
|
|
249
|
+
* @param signature - Signature bytes (not base64)
|
|
250
|
+
* @returns true if signature is valid
|
|
251
|
+
*/
|
|
252
|
+
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
253
|
+
/**
|
|
254
|
+
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
255
|
+
* @param publicKey - Raw public key bytes
|
|
256
|
+
* @returns Fingerprint string like "SHA256:abc123..."
|
|
257
|
+
*/
|
|
258
|
+
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
259
|
+
/**
|
|
260
|
+
* Format a fingerprint for nice display.
|
|
261
|
+
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
262
|
+
*/
|
|
263
|
+
declare function formatFingerprint(fingerprint: string): string;
|
|
264
|
+
|
|
265
|
+
export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignOptions, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, upload, verifySignature };
|
package/dist/index.js
CHANGED
|
@@ -104,6 +104,141 @@ function base64Decode(str) {
|
|
|
104
104
|
return bytes;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// src/signing.ts
|
|
108
|
+
import { p256, p384, p521 } from "@noble/curves/nist.js";
|
|
109
|
+
import * as ed from "@noble/ed25519";
|
|
110
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
111
|
+
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
112
|
+
var SSH_KEY_TYPES = {
|
|
113
|
+
"ssh-ed25519": "ed25519",
|
|
114
|
+
"ecdsa-sha2-nistp256": "ecdsa-p256",
|
|
115
|
+
"ecdsa-sha2-nistp384": "ecdsa-p384",
|
|
116
|
+
"ecdsa-sha2-nistp521": "ecdsa-p521"
|
|
117
|
+
};
|
|
118
|
+
function readSSHString(data, offset) {
|
|
119
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
120
|
+
const len = view.getUint32(offset);
|
|
121
|
+
const value = data.slice(offset + 4, offset + 4 + len);
|
|
122
|
+
return { value, nextOffset: offset + 4 + len };
|
|
123
|
+
}
|
|
124
|
+
function base64Decode2(str) {
|
|
125
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
126
|
+
while (base64.length % 4) {
|
|
127
|
+
base64 += "=";
|
|
128
|
+
}
|
|
129
|
+
const binary = atob(base64);
|
|
130
|
+
const bytes = new Uint8Array(binary.length);
|
|
131
|
+
for (let i = 0; i < binary.length; i++) {
|
|
132
|
+
bytes[i] = binary.charCodeAt(i);
|
|
133
|
+
}
|
|
134
|
+
return bytes;
|
|
135
|
+
}
|
|
136
|
+
function parsePublicKey(keyString) {
|
|
137
|
+
const trimmed = keyString.trim();
|
|
138
|
+
for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {
|
|
139
|
+
if (trimmed.startsWith(`${sshType} `)) {
|
|
140
|
+
const parts = trimmed.split(" ");
|
|
141
|
+
if (parts.length < 2) {
|
|
142
|
+
throw new Error("Invalid SSH key format");
|
|
143
|
+
}
|
|
144
|
+
const keyData = base64Decode2(parts[1]);
|
|
145
|
+
const { value: typeBytes, nextOffset: afterType } = readSSHString(
|
|
146
|
+
keyData,
|
|
147
|
+
0
|
|
148
|
+
);
|
|
149
|
+
const typeStr = new TextDecoder().decode(typeBytes);
|
|
150
|
+
if (typeStr !== sshType) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Key type mismatch: expected ${sshType}, got ${typeStr}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (keyType === "ed25519") {
|
|
156
|
+
const { value: rawKey } = readSSHString(keyData, afterType);
|
|
157
|
+
if (rawKey.length !== 32) {
|
|
158
|
+
throw new Error("Invalid Ed25519 key length");
|
|
159
|
+
}
|
|
160
|
+
return { type: "ed25519", keyBytes: rawKey };
|
|
161
|
+
}
|
|
162
|
+
const { nextOffset: afterCurve } = readSSHString(keyData, afterType);
|
|
163
|
+
const { value: point } = readSSHString(keyData, afterCurve);
|
|
164
|
+
return { type: keyType, keyBytes: point };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const decoded = base64Decode2(trimmed);
|
|
168
|
+
if (decoded.length === 32) {
|
|
169
|
+
return { type: "ed25519", keyBytes: decoded };
|
|
170
|
+
}
|
|
171
|
+
throw new Error("Unsupported public key format");
|
|
172
|
+
}
|
|
173
|
+
async function signEd25519(message, privateKey) {
|
|
174
|
+
const sig = await ed.signAsync(message, privateKey);
|
|
175
|
+
return btoa(String.fromCharCode(...sig));
|
|
176
|
+
}
|
|
177
|
+
function signECDSA(message, privateKey, curve) {
|
|
178
|
+
const curves = { p256, p384, p521 };
|
|
179
|
+
const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });
|
|
180
|
+
return btoa(String.fromCharCode(...sigBytes));
|
|
181
|
+
}
|
|
182
|
+
async function createSignatureEnvelope(content, privateKey, publicKey, options) {
|
|
183
|
+
const parsed = parsePublicKey(publicKey);
|
|
184
|
+
let sig;
|
|
185
|
+
if (parsed.type === "ed25519") {
|
|
186
|
+
sig = await signEd25519(content, privateKey);
|
|
187
|
+
} else {
|
|
188
|
+
const curve = parsed.type.replace("ecdsa-", "");
|
|
189
|
+
sig = signECDSA(content, privateKey, curve);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
sig,
|
|
193
|
+
publicKey,
|
|
194
|
+
githubUser: options?.githubUser
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function verifySignature(parsedKey, message, signature) {
|
|
198
|
+
try {
|
|
199
|
+
switch (parsedKey.type) {
|
|
200
|
+
case "ed25519":
|
|
201
|
+
return await ed.verifyAsync(signature, message, parsedKey.keyBytes);
|
|
202
|
+
case "ecdsa-p256":
|
|
203
|
+
return p256.verify(signature, message, parsedKey.keyBytes, {
|
|
204
|
+
prehash: true
|
|
205
|
+
});
|
|
206
|
+
case "ecdsa-p384":
|
|
207
|
+
return p384.verify(signature, message, parsedKey.keyBytes, {
|
|
208
|
+
prehash: true
|
|
209
|
+
});
|
|
210
|
+
case "ecdsa-p521":
|
|
211
|
+
return p521.verify(signature, message, parsedKey.keyBytes, {
|
|
212
|
+
prehash: true
|
|
213
|
+
});
|
|
214
|
+
default:
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function computeFingerprint(publicKey) {
|
|
222
|
+
const hash = await crypto.subtle.digest(
|
|
223
|
+
"SHA-256",
|
|
224
|
+
publicKey.buffer
|
|
225
|
+
);
|
|
226
|
+
const hashArray = new Uint8Array(hash);
|
|
227
|
+
const base64 = btoa(String.fromCharCode(...hashArray));
|
|
228
|
+
return `SHA256:${base64.replace(/=+$/, "")}`;
|
|
229
|
+
}
|
|
230
|
+
function formatFingerprint(fingerprint) {
|
|
231
|
+
const base64Part = fingerprint.startsWith("SHA256:") ? fingerprint.slice(7) : fingerprint;
|
|
232
|
+
try {
|
|
233
|
+
const binary = atob(base64Part);
|
|
234
|
+
const hex = Array.from(binary).map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
235
|
+
const short = hex.slice(0, 16);
|
|
236
|
+
return short.match(/.{4}/g)?.join(" ") || short;
|
|
237
|
+
} catch {
|
|
238
|
+
return fingerprint.slice(0, 16).toUpperCase();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
107
242
|
// src/client.ts
|
|
108
243
|
var DEFAULT_BASE_URL = "https://mux.md";
|
|
109
244
|
async function upload(data, fileInfo, options = {}) {
|
|
@@ -112,11 +247,20 @@ async function upload(data, fileInfo, options = {}) {
|
|
|
112
247
|
const salt = generateSalt();
|
|
113
248
|
const iv = generateIV();
|
|
114
249
|
const cryptoKey = await deriveKey(keyMaterial, salt);
|
|
250
|
+
let signatureEnvelope;
|
|
251
|
+
if (options.sign) {
|
|
252
|
+
signatureEnvelope = await createSignatureEnvelope(
|
|
253
|
+
data,
|
|
254
|
+
options.sign.privateKey,
|
|
255
|
+
options.sign.publicKey,
|
|
256
|
+
{ githubUser: options.sign.githubUser }
|
|
257
|
+
);
|
|
258
|
+
}
|
|
115
259
|
let plaintext;
|
|
116
|
-
if (
|
|
260
|
+
if (signatureEnvelope) {
|
|
117
261
|
const signed = {
|
|
118
262
|
content: new TextDecoder().decode(data),
|
|
119
|
-
sig:
|
|
263
|
+
sig: signatureEnvelope
|
|
120
264
|
};
|
|
121
265
|
plaintext = new TextEncoder().encode(JSON.stringify(signed));
|
|
122
266
|
} else {
|
|
@@ -308,143 +452,6 @@ async function setExpiration(id, mutateKey, expiresAt, options = {}) {
|
|
|
308
452
|
}
|
|
309
453
|
return response.json();
|
|
310
454
|
}
|
|
311
|
-
|
|
312
|
-
// src/signing.ts
|
|
313
|
-
import * as ed from "@noble/ed25519";
|
|
314
|
-
import { p256, p384, p521 } from "@noble/curves/nist.js";
|
|
315
|
-
import { sha512 } from "@noble/hashes/sha2.js";
|
|
316
|
-
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
317
|
-
var SSH_KEY_TYPES = {
|
|
318
|
-
"ssh-ed25519": "ed25519",
|
|
319
|
-
"ecdsa-sha2-nistp256": "ecdsa-p256",
|
|
320
|
-
"ecdsa-sha2-nistp384": "ecdsa-p384",
|
|
321
|
-
"ecdsa-sha2-nistp521": "ecdsa-p521"
|
|
322
|
-
};
|
|
323
|
-
function readSSHString(data, offset) {
|
|
324
|
-
const view = new DataView(data.buffer, data.byteOffset);
|
|
325
|
-
const len = view.getUint32(offset);
|
|
326
|
-
const value = data.slice(offset + 4, offset + 4 + len);
|
|
327
|
-
return { value, nextOffset: offset + 4 + len };
|
|
328
|
-
}
|
|
329
|
-
function base64Decode2(str) {
|
|
330
|
-
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
331
|
-
while (base64.length % 4) {
|
|
332
|
-
base64 += "=";
|
|
333
|
-
}
|
|
334
|
-
const binary = atob(base64);
|
|
335
|
-
const bytes = new Uint8Array(binary.length);
|
|
336
|
-
for (let i = 0; i < binary.length; i++) {
|
|
337
|
-
bytes[i] = binary.charCodeAt(i);
|
|
338
|
-
}
|
|
339
|
-
return bytes;
|
|
340
|
-
}
|
|
341
|
-
function parsePublicKey(keyString) {
|
|
342
|
-
const trimmed = keyString.trim();
|
|
343
|
-
for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {
|
|
344
|
-
if (trimmed.startsWith(`${sshType} `)) {
|
|
345
|
-
const parts = trimmed.split(" ");
|
|
346
|
-
if (parts.length < 2) {
|
|
347
|
-
throw new Error("Invalid SSH key format");
|
|
348
|
-
}
|
|
349
|
-
const keyData = base64Decode2(parts[1]);
|
|
350
|
-
const { value: typeBytes, nextOffset: afterType } = readSSHString(
|
|
351
|
-
keyData,
|
|
352
|
-
0
|
|
353
|
-
);
|
|
354
|
-
const typeStr = new TextDecoder().decode(typeBytes);
|
|
355
|
-
if (typeStr !== sshType) {
|
|
356
|
-
throw new Error(
|
|
357
|
-
`Key type mismatch: expected ${sshType}, got ${typeStr}`
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
if (keyType === "ed25519") {
|
|
361
|
-
const { value: rawKey } = readSSHString(keyData, afterType);
|
|
362
|
-
if (rawKey.length !== 32) {
|
|
363
|
-
throw new Error("Invalid Ed25519 key length");
|
|
364
|
-
}
|
|
365
|
-
return { type: "ed25519", keyBytes: rawKey };
|
|
366
|
-
}
|
|
367
|
-
const { nextOffset: afterCurve } = readSSHString(keyData, afterType);
|
|
368
|
-
const { value: point } = readSSHString(keyData, afterCurve);
|
|
369
|
-
return { type: keyType, keyBytes: point };
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const decoded = base64Decode2(trimmed);
|
|
373
|
-
if (decoded.length === 32) {
|
|
374
|
-
return { type: "ed25519", keyBytes: decoded };
|
|
375
|
-
}
|
|
376
|
-
throw new Error("Unsupported public key format");
|
|
377
|
-
}
|
|
378
|
-
async function signEd25519(message, privateKey) {
|
|
379
|
-
const sig = await ed.signAsync(message, privateKey);
|
|
380
|
-
return btoa(String.fromCharCode(...sig));
|
|
381
|
-
}
|
|
382
|
-
function signECDSA(message, privateKey, curve) {
|
|
383
|
-
const curves = { p256, p384, p521 };
|
|
384
|
-
const sig = curves[curve].sign(message, privateKey, { prehash: true });
|
|
385
|
-
const sigBytes = sig.toCompactRawBytes();
|
|
386
|
-
return btoa(String.fromCharCode(...sigBytes));
|
|
387
|
-
}
|
|
388
|
-
async function createSignatureEnvelope(content, privateKey, publicKey, options) {
|
|
389
|
-
const parsed = parsePublicKey(publicKey);
|
|
390
|
-
let sig;
|
|
391
|
-
if (parsed.type === "ed25519") {
|
|
392
|
-
sig = await signEd25519(content, privateKey);
|
|
393
|
-
} else {
|
|
394
|
-
const curve = parsed.type.replace("ecdsa-", "");
|
|
395
|
-
sig = signECDSA(content, privateKey, curve);
|
|
396
|
-
}
|
|
397
|
-
return {
|
|
398
|
-
sig,
|
|
399
|
-
publicKey,
|
|
400
|
-
email: options?.email,
|
|
401
|
-
githubUser: options?.githubUser
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
async function verifySignature(parsedKey, message, signature) {
|
|
405
|
-
try {
|
|
406
|
-
switch (parsedKey.type) {
|
|
407
|
-
case "ed25519":
|
|
408
|
-
return await ed.verifyAsync(signature, message, parsedKey.keyBytes);
|
|
409
|
-
case "ecdsa-p256":
|
|
410
|
-
return p256.verify(signature, message, parsedKey.keyBytes, {
|
|
411
|
-
prehash: true
|
|
412
|
-
});
|
|
413
|
-
case "ecdsa-p384":
|
|
414
|
-
return p384.verify(signature, message, parsedKey.keyBytes, {
|
|
415
|
-
prehash: true
|
|
416
|
-
});
|
|
417
|
-
case "ecdsa-p521":
|
|
418
|
-
return p521.verify(signature, message, parsedKey.keyBytes, {
|
|
419
|
-
prehash: true
|
|
420
|
-
});
|
|
421
|
-
default:
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
} catch {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
async function computeFingerprint(publicKey) {
|
|
429
|
-
const hash = await crypto.subtle.digest(
|
|
430
|
-
"SHA-256",
|
|
431
|
-
publicKey.buffer
|
|
432
|
-
);
|
|
433
|
-
const hashArray = new Uint8Array(hash);
|
|
434
|
-
const base64 = btoa(String.fromCharCode(...hashArray));
|
|
435
|
-
return `SHA256:${base64.replace(/=+$/, "")}`;
|
|
436
|
-
}
|
|
437
|
-
function formatFingerprint(fingerprint) {
|
|
438
|
-
const base64Part = fingerprint.startsWith("SHA256:") ? fingerprint.slice(7) : fingerprint;
|
|
439
|
-
try {
|
|
440
|
-
const binary = atob(base64Part);
|
|
441
|
-
const hex = Array.from(binary).map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
442
|
-
const short = hex.slice(0, 16);
|
|
443
|
-
return short.match(/.{4}/g)?.join(" ") || short;
|
|
444
|
-
} catch {
|
|
445
|
-
return fingerprint.slice(0, 16).toUpperCase();
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
455
|
export {
|
|
449
456
|
base64Decode,
|
|
450
457
|
base64Encode,
|
|
@@ -452,7 +459,6 @@ export {
|
|
|
452
459
|
base64UrlEncode,
|
|
453
460
|
buildUrl,
|
|
454
461
|
computeFingerprint,
|
|
455
|
-
createSignatureEnvelope,
|
|
456
462
|
decrypt,
|
|
457
463
|
deleteFile,
|
|
458
464
|
deriveKey,
|
|
@@ -468,8 +474,6 @@ export {
|
|
|
468
474
|
parsePublicKey,
|
|
469
475
|
parseUrl,
|
|
470
476
|
setExpiration,
|
|
471
|
-
signECDSA,
|
|
472
|
-
signEd25519,
|
|
473
477
|
upload,
|
|
474
478
|
verifySignature
|
|
475
479
|
};
|