@coder/mux-md-client 0.1.0-main.13 → 0.1.0-main.14
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 +147 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -78
- package/dist/index.d.ts +63 -78
- package/dist/index.js +147 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -45,13 +45,27 @@ interface SignedPayload {
|
|
|
45
45
|
* Works in both Node.js (with webcrypto) and browser environments.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
+
/** Options for signing content during upload */
|
|
49
|
+
interface SignOptions {
|
|
50
|
+
/** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
|
|
51
|
+
privateKey: Uint8Array;
|
|
52
|
+
/** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
|
|
53
|
+
publicKey: string;
|
|
54
|
+
/** Optional email for attribution */
|
|
55
|
+
email?: string;
|
|
56
|
+
/** Optional GitHub username for attribution */
|
|
57
|
+
githubUser?: string;
|
|
58
|
+
}
|
|
48
59
|
interface UploadOptions {
|
|
49
60
|
/** Base URL of the mux.md service */
|
|
50
61
|
baseUrl?: string;
|
|
51
62
|
/** Expiration time (unix timestamp ms, ISO date string, or Date object) */
|
|
52
63
|
expiresAt?: number | string | Date;
|
|
53
|
-
/**
|
|
54
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Sign the content with the provided credentials.
|
|
66
|
+
* The library handles creating the signature envelope internally.
|
|
67
|
+
*/
|
|
68
|
+
sign?: SignOptions;
|
|
55
69
|
}
|
|
56
70
|
interface UploadResult {
|
|
57
71
|
/** Full URL with encryption key in fragment */
|
|
@@ -151,81 +165,6 @@ interface SetExpirationResult {
|
|
|
151
165
|
*/
|
|
152
166
|
declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
|
|
153
167
|
|
|
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
168
|
/**
|
|
230
169
|
* Generate a random file ID (30 bits, 5 chars base62)
|
|
231
170
|
*/
|
|
@@ -280,4 +219,50 @@ declare function base64Encode(data: Uint8Array): string;
|
|
|
280
219
|
*/
|
|
281
220
|
declare function base64Decode(str: string): Uint8Array;
|
|
282
221
|
|
|
283
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Signing and verification utilities for mux.md
|
|
224
|
+
*
|
|
225
|
+
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/** Supported key types */
|
|
229
|
+
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
230
|
+
/** Parsed public key with type and raw bytes */
|
|
231
|
+
interface ParsedPublicKey {
|
|
232
|
+
type: KeyType;
|
|
233
|
+
keyBytes: Uint8Array;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Parse an SSH public key and extract the key bytes and type.
|
|
237
|
+
* Supports formats:
|
|
238
|
+
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
239
|
+
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
240
|
+
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
241
|
+
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
242
|
+
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
243
|
+
*/
|
|
244
|
+
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
245
|
+
/**
|
|
246
|
+
* Verify a signature using the appropriate algorithm based on key type.
|
|
247
|
+
* For Ed25519: signature is raw 64 bytes
|
|
248
|
+
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
249
|
+
*
|
|
250
|
+
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
251
|
+
* @param message - Original message bytes
|
|
252
|
+
* @param signature - Signature bytes (not base64)
|
|
253
|
+
* @returns true if signature is valid
|
|
254
|
+
*/
|
|
255
|
+
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
256
|
+
/**
|
|
257
|
+
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
258
|
+
* @param publicKey - Raw public key bytes
|
|
259
|
+
* @returns Fingerprint string like "SHA256:abc123..."
|
|
260
|
+
*/
|
|
261
|
+
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
262
|
+
/**
|
|
263
|
+
* Format a fingerprint for nice display.
|
|
264
|
+
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
265
|
+
*/
|
|
266
|
+
declare function formatFingerprint(fingerprint: string): string;
|
|
267
|
+
|
|
268
|
+
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
|
@@ -45,13 +45,27 @@ interface SignedPayload {
|
|
|
45
45
|
* Works in both Node.js (with webcrypto) and browser environments.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
+
/** Options for signing content during upload */
|
|
49
|
+
interface SignOptions {
|
|
50
|
+
/** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
|
|
51
|
+
privateKey: Uint8Array;
|
|
52
|
+
/** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
|
|
53
|
+
publicKey: string;
|
|
54
|
+
/** Optional email for attribution */
|
|
55
|
+
email?: string;
|
|
56
|
+
/** Optional GitHub username for attribution */
|
|
57
|
+
githubUser?: string;
|
|
58
|
+
}
|
|
48
59
|
interface UploadOptions {
|
|
49
60
|
/** Base URL of the mux.md service */
|
|
50
61
|
baseUrl?: string;
|
|
51
62
|
/** Expiration time (unix timestamp ms, ISO date string, or Date object) */
|
|
52
63
|
expiresAt?: number | string | Date;
|
|
53
|
-
/**
|
|
54
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Sign the content with the provided credentials.
|
|
66
|
+
* The library handles creating the signature envelope internally.
|
|
67
|
+
*/
|
|
68
|
+
sign?: SignOptions;
|
|
55
69
|
}
|
|
56
70
|
interface UploadResult {
|
|
57
71
|
/** Full URL with encryption key in fragment */
|
|
@@ -151,81 +165,6 @@ interface SetExpirationResult {
|
|
|
151
165
|
*/
|
|
152
166
|
declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
|
|
153
167
|
|
|
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
168
|
/**
|
|
230
169
|
* Generate a random file ID (30 bits, 5 chars base62)
|
|
231
170
|
*/
|
|
@@ -280,4 +219,50 @@ declare function base64Encode(data: Uint8Array): string;
|
|
|
280
219
|
*/
|
|
281
220
|
declare function base64Decode(str: string): Uint8Array;
|
|
282
221
|
|
|
283
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Signing and verification utilities for mux.md
|
|
224
|
+
*
|
|
225
|
+
* Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
/** Supported key types */
|
|
229
|
+
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
|
|
230
|
+
/** Parsed public key with type and raw bytes */
|
|
231
|
+
interface ParsedPublicKey {
|
|
232
|
+
type: KeyType;
|
|
233
|
+
keyBytes: Uint8Array;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Parse an SSH public key and extract the key bytes and type.
|
|
237
|
+
* Supports formats:
|
|
238
|
+
* - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
|
|
239
|
+
* - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
|
|
240
|
+
* - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
|
|
241
|
+
* - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
|
|
242
|
+
* - Raw base64 (32 bytes when decoded = Ed25519)
|
|
243
|
+
*/
|
|
244
|
+
declare function parsePublicKey(keyString: string): ParsedPublicKey;
|
|
245
|
+
/**
|
|
246
|
+
* Verify a signature using the appropriate algorithm based on key type.
|
|
247
|
+
* For Ed25519: signature is raw 64 bytes
|
|
248
|
+
* For ECDSA: signature is DER-encoded or raw r||s format
|
|
249
|
+
*
|
|
250
|
+
* @param parsedKey - Parsed public key (from parsePublicKey)
|
|
251
|
+
* @param message - Original message bytes
|
|
252
|
+
* @param signature - Signature bytes (not base64)
|
|
253
|
+
* @returns true if signature is valid
|
|
254
|
+
*/
|
|
255
|
+
declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
256
|
+
/**
|
|
257
|
+
* Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
|
|
258
|
+
* @param publicKey - Raw public key bytes
|
|
259
|
+
* @returns Fingerprint string like "SHA256:abc123..."
|
|
260
|
+
*/
|
|
261
|
+
declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
|
|
262
|
+
/**
|
|
263
|
+
* Format a fingerprint for nice display.
|
|
264
|
+
* Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
|
|
265
|
+
*/
|
|
266
|
+
declare function formatFingerprint(fingerprint: string): string;
|
|
267
|
+
|
|
268
|
+
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,142 @@ 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
|
+
email: options?.email,
|
|
195
|
+
githubUser: options?.githubUser
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function verifySignature(parsedKey, message, signature) {
|
|
199
|
+
try {
|
|
200
|
+
switch (parsedKey.type) {
|
|
201
|
+
case "ed25519":
|
|
202
|
+
return await ed.verifyAsync(signature, message, parsedKey.keyBytes);
|
|
203
|
+
case "ecdsa-p256":
|
|
204
|
+
return p256.verify(signature, message, parsedKey.keyBytes, {
|
|
205
|
+
prehash: true
|
|
206
|
+
});
|
|
207
|
+
case "ecdsa-p384":
|
|
208
|
+
return p384.verify(signature, message, parsedKey.keyBytes, {
|
|
209
|
+
prehash: true
|
|
210
|
+
});
|
|
211
|
+
case "ecdsa-p521":
|
|
212
|
+
return p521.verify(signature, message, parsedKey.keyBytes, {
|
|
213
|
+
prehash: true
|
|
214
|
+
});
|
|
215
|
+
default:
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function computeFingerprint(publicKey) {
|
|
223
|
+
const hash = await crypto.subtle.digest(
|
|
224
|
+
"SHA-256",
|
|
225
|
+
publicKey.buffer
|
|
226
|
+
);
|
|
227
|
+
const hashArray = new Uint8Array(hash);
|
|
228
|
+
const base64 = btoa(String.fromCharCode(...hashArray));
|
|
229
|
+
return `SHA256:${base64.replace(/=+$/, "")}`;
|
|
230
|
+
}
|
|
231
|
+
function formatFingerprint(fingerprint) {
|
|
232
|
+
const base64Part = fingerprint.startsWith("SHA256:") ? fingerprint.slice(7) : fingerprint;
|
|
233
|
+
try {
|
|
234
|
+
const binary = atob(base64Part);
|
|
235
|
+
const hex = Array.from(binary).map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
236
|
+
const short = hex.slice(0, 16);
|
|
237
|
+
return short.match(/.{4}/g)?.join(" ") || short;
|
|
238
|
+
} catch {
|
|
239
|
+
return fingerprint.slice(0, 16).toUpperCase();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
107
243
|
// src/client.ts
|
|
108
244
|
var DEFAULT_BASE_URL = "https://mux.md";
|
|
109
245
|
async function upload(data, fileInfo, options = {}) {
|
|
@@ -112,11 +248,20 @@ async function upload(data, fileInfo, options = {}) {
|
|
|
112
248
|
const salt = generateSalt();
|
|
113
249
|
const iv = generateIV();
|
|
114
250
|
const cryptoKey = await deriveKey(keyMaterial, salt);
|
|
251
|
+
let signatureEnvelope;
|
|
252
|
+
if (options.sign) {
|
|
253
|
+
signatureEnvelope = await createSignatureEnvelope(
|
|
254
|
+
data,
|
|
255
|
+
options.sign.privateKey,
|
|
256
|
+
options.sign.publicKey,
|
|
257
|
+
{ email: options.sign.email, githubUser: options.sign.githubUser }
|
|
258
|
+
);
|
|
259
|
+
}
|
|
115
260
|
let plaintext;
|
|
116
|
-
if (
|
|
261
|
+
if (signatureEnvelope) {
|
|
117
262
|
const signed = {
|
|
118
263
|
content: new TextDecoder().decode(data),
|
|
119
|
-
sig:
|
|
264
|
+
sig: signatureEnvelope
|
|
120
265
|
};
|
|
121
266
|
plaintext = new TextEncoder().encode(JSON.stringify(signed));
|
|
122
267
|
} else {
|
|
@@ -308,143 +453,6 @@ async function setExpiration(id, mutateKey, expiresAt, options = {}) {
|
|
|
308
453
|
}
|
|
309
454
|
return response.json();
|
|
310
455
|
}
|
|
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
456
|
export {
|
|
449
457
|
base64Decode,
|
|
450
458
|
base64Encode,
|
|
@@ -452,7 +460,6 @@ export {
|
|
|
452
460
|
base64UrlEncode,
|
|
453
461
|
buildUrl,
|
|
454
462
|
computeFingerprint,
|
|
455
|
-
createSignatureEnvelope,
|
|
456
463
|
decrypt,
|
|
457
464
|
deleteFile,
|
|
458
465
|
deriveKey,
|
|
@@ -468,8 +475,6 @@ export {
|
|
|
468
475
|
parsePublicKey,
|
|
469
476
|
parseUrl,
|
|
470
477
|
setExpiration,
|
|
471
|
-
signECDSA,
|
|
472
|
-
signEd25519,
|
|
473
478
|
upload,
|
|
474
479
|
verifySignature
|
|
475
480
|
};
|