sigflint 0.0.0 → 1.0.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/node.js ADDED
@@ -0,0 +1,585 @@
1
+ // src/node.js
2
+ import crypto2 from "node:crypto";
3
+
4
+ // src/base.js
5
+ var emptyBodyHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
6
+ var symmetric = { algorithm: "AWS4-HMAC-SHA256", scope: (date, region, service) => `${date}/${region}/${service}/aws4_request` };
7
+ var reSafePath = /^[A-Za-z0-9\-._~/]*$/;
8
+ var reSafeQuery = /^[A-Za-z0-9\-._~=&]*$/;
9
+ var reSpaces = /[ \t]+/g;
10
+ var reDirty = /\t| /;
11
+ var pathTable;
12
+ var queryTable;
13
+ function tables() {
14
+ if (pathTable !== void 0) return;
15
+ pathTable = new Array(256);
16
+ queryTable = new Array(256);
17
+ for (let b = 0; b < 256; b++) {
18
+ const c = String.fromCharCode(b);
19
+ const unreserved = /[A-Za-z0-9\-._~]/.test(c);
20
+ const escaped = (b < 16 ? "%0" : "%") + b.toString(16).toUpperCase();
21
+ pathTable[b] = unreserved || c === "/" ? c : escaped;
22
+ queryTable[b] = unreserved ? c : escaped;
23
+ }
24
+ }
25
+ function hexValue(code) {
26
+ if (code >= 48 && code <= 57) return code - 48;
27
+ if (code >= 65 && code <= 70) return code - 55;
28
+ if (code >= 97 && code <= 102) return code - 87;
29
+ return -1;
30
+ }
31
+ function canonicalEncode(string, table) {
32
+ let out = "";
33
+ let start = 0;
34
+ for (let i = 0; i < string.length; i++) {
35
+ const code = string.charCodeAt(i);
36
+ const same = table[code];
37
+ if (same.length === 1 && code !== 43) continue;
38
+ out += string.slice(start, i);
39
+ start = i + 1;
40
+ if (code === 37) {
41
+ const hi = hexValue(string.charCodeAt(i + 1));
42
+ const lo = hexValue(string.charCodeAt(i + 2));
43
+ if (hi >= 0 && lo >= 0) {
44
+ out += table[hi << 4 | lo];
45
+ i += 2;
46
+ start = i + 1;
47
+ } else {
48
+ out += "%25";
49
+ }
50
+ } else {
51
+ out += code === 43 ? "%20" : same;
52
+ }
53
+ }
54
+ return start === 0 ? string : out + string.slice(start);
55
+ }
56
+ function canonicalPathGeneral(pathname) {
57
+ const parts = pathname.split("/");
58
+ const kept = [];
59
+ for (let i = 0; i < parts.length; i++) {
60
+ const part = parts[i];
61
+ if (part === "" || part === ".") continue;
62
+ if (part === "..") kept.pop();
63
+ else kept.push(part);
64
+ }
65
+ const path = (pathname.startsWith("/") ? "/" : "") + kept.join("/") + (pathname.endsWith("/") && kept.length > 0 ? "/" : "");
66
+ if (reSafePath.test(path)) return path === "" ? "/" : path;
67
+ tables();
68
+ let out = "";
69
+ for (let i = 0; i < path.length; i++) out += queryTable[path.charCodeAt(i)] === path[i] || path[i] === "/" ? path[i] : queryTable[path.charCodeAt(i)];
70
+ return out;
71
+ }
72
+ function canonicalPathS3(pathname) {
73
+ if (reSafePath.test(pathname)) return pathname === "" ? "/" : pathname;
74
+ tables();
75
+ return canonicalEncode(pathname, pathTable);
76
+ }
77
+ var canonicalPath = (service, pathname) => service === "s3" ? canonicalPathS3(pathname) : canonicalPathGeneral(pathname);
78
+ function comparePairs(a, b) {
79
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;
80
+ }
81
+ function canonicalQuery(search) {
82
+ if (search.length < 2) return "";
83
+ const query = search.slice(1);
84
+ const simple = reSafeQuery.test(query);
85
+ if (simple) {
86
+ let out2 = "";
87
+ let previousName = "";
88
+ let previousValue = "";
89
+ let start = 0;
90
+ for (; ; ) {
91
+ let end = query.indexOf("&", start);
92
+ if (end === -1) end = query.length;
93
+ let eq = query.indexOf("=", start);
94
+ if (eq === -1 || eq > end) eq = end;
95
+ const name = query.slice(start, eq);
96
+ const value = query.slice(eq + 1, end);
97
+ if (end === start || eq === start || value.includes("=") || start > 0 && (name < previousName || name === previousName && value <= previousValue)) break;
98
+ out2 += (start === 0 ? "" : "&") + name + "=" + value;
99
+ if (end === query.length) return out2;
100
+ previousName = name;
101
+ previousValue = value;
102
+ start = end + 1;
103
+ }
104
+ }
105
+ tables();
106
+ const pairs = [];
107
+ for (const part of query.split("&")) {
108
+ if (part === "") continue;
109
+ const eq = part.indexOf("=");
110
+ const rawName = eq === -1 ? part : part.slice(0, eq);
111
+ const rawValue = eq === -1 ? "" : part.slice(eq + 1);
112
+ pairs.push(simple && !rawValue.includes("=") ? [rawName, rawValue] : [canonicalEncode(rawName, queryTable), canonicalEncode(rawValue, queryTable)]);
113
+ }
114
+ if (pairs.length > 1) pairs.sort(comparePairs);
115
+ let out = "";
116
+ for (let i = 0; i < pairs.length; i++) out += (i === 0 ? "" : "&") + pairs[i][0] + "=" + pairs[i][1];
117
+ return out;
118
+ }
119
+ function insert(names, values, name, value) {
120
+ let i = names.length;
121
+ while (i > 0 && names[i - 1] > name) i--;
122
+ if (i > 0 && names[i - 1] === name) {
123
+ values[i - 1] = value;
124
+ return;
125
+ }
126
+ for (let j = names.length; j > i; j--) {
127
+ names[j] = names[j - 1];
128
+ values[j] = values[j - 1];
129
+ }
130
+ names[i] = name;
131
+ values[i] = value;
132
+ }
133
+ function signHeaderName(name, value) {
134
+ const lowerName = name.toLowerCase();
135
+ if (lowerName === "x-amz-date" || lowerName === "authorization" || lowerName === "__proto__") throw new TypeError(`signHeaders cannot contain ${lowerName}`);
136
+ if (/[\r\n]/.test(value)) throw new TypeError(`signHeaders ${lowerName} has a line break in its value`);
137
+ return lowerName;
138
+ }
139
+ var headerValue = (value) => {
140
+ const trimmed = String(value).trim();
141
+ return reDirty.test(trimmed) ? trimmed.replace(reSpaces, " ") : trimmed;
142
+ };
143
+ var lastSecond = NaN;
144
+ var lastDatetime = "";
145
+ function amzDatetime(now) {
146
+ const second = Math.floor(now / 1e3);
147
+ if (second !== lastSecond) {
148
+ const iso = new Date(second * 1e3).toISOString();
149
+ lastDatetime = iso.slice(0, 4) + iso.slice(5, 7) + iso.slice(8, 10) + "T" + iso.slice(11, 13) + iso.slice(14, 16) + iso.slice(17, 19) + "Z";
150
+ lastSecond = second;
151
+ }
152
+ return lastDatetime;
153
+ }
154
+ function payloadHashOf(request, hexHash2) {
155
+ const body = request.body ?? "";
156
+ return request.unsignedPayload ? "UNSIGNED-PAYLOAD" : body.length === 0 ? emptyBodyHash : hexHash2(body);
157
+ }
158
+ function prepare(request, credentials, payloadHash, scheme = symmetric) {
159
+ const { method, url, signHeaders } = request;
160
+ const region = request.region ?? "";
161
+ const service = request.service ?? "s3";
162
+ const withHash = service === "s3" || request.unsignedPayload === true;
163
+ const sessionToken = credentials.sessionToken;
164
+ const datetime = amzDatetime(request.now ?? Date.now());
165
+ const date = datetime.slice(0, 8);
166
+ const headers = withHash ? { "x-amz-date": datetime, "x-amz-content-sha256": payloadHash } : { "x-amz-date": datetime };
167
+ const extraNames = signHeaders === void 0 ? [] : Object.keys(signHeaders);
168
+ let canonicalHeaders;
169
+ let signedHeaders;
170
+ if (extraNames.length === 0 && !sessionToken) {
171
+ canonicalHeaders = withHash ? `host:${url.host}
172
+ x-amz-content-sha256:${payloadHash}
173
+ x-amz-date:${datetime}
174
+ ` : `host:${url.host}
175
+ x-amz-date:${datetime}
176
+ `;
177
+ signedHeaders = withHash ? "host;x-amz-content-sha256;x-amz-date" : "host;x-amz-date";
178
+ } else {
179
+ const names = [];
180
+ const values = [];
181
+ if (sessionToken) {
182
+ headers["x-amz-security-token"] = sessionToken;
183
+ insert(names, values, "x-amz-security-token", sessionToken.trim());
184
+ }
185
+ for (const name of extraNames) {
186
+ const value = (
187
+ /** @type {Record<string, string>} */
188
+ signHeaders[name]
189
+ );
190
+ const lowerName = signHeaderName(name, value);
191
+ headers[lowerName] = value;
192
+ insert(names, values, lowerName, headerValue(value));
193
+ }
194
+ const baseNames = withHash ? ["host", "x-amz-content-sha256", "x-amz-date"] : ["host", "x-amz-date"];
195
+ const baseValues = withHash ? [url.host, payloadHash, datetime] : [url.host, datetime];
196
+ const nBase = baseNames.length;
197
+ canonicalHeaders = "";
198
+ signedHeaders = "";
199
+ let i = 0;
200
+ let j = 0;
201
+ while (i < names.length || j < nBase) {
202
+ let name;
203
+ let value;
204
+ if (j === nBase || i < names.length && names[i] <= baseNames[j]) {
205
+ name = names[i];
206
+ value = values[i++];
207
+ if (j < nBase && name === baseNames[j]) j++;
208
+ } else {
209
+ name = baseNames[j];
210
+ value = baseValues[j++];
211
+ }
212
+ if (name === "x-amz-content-sha256") payloadHash = value;
213
+ canonicalHeaders += `${name}:${value}
214
+ `;
215
+ signedHeaders += signedHeaders === "" ? name : ";" + name;
216
+ }
217
+ }
218
+ const canonicalRequest = `${method}
219
+ ${canonicalPath(service, url.pathname)}
220
+ ${canonicalQuery(url.search)}
221
+ ${canonicalHeaders}
222
+ ${signedHeaders}
223
+ ${payloadHash}`;
224
+ return { headers, canonicalRequest, datetime, date, region, service, credentialScope: scheme.scope(date, region, service), signedHeaders, algorithm: scheme.algorithm };
225
+ }
226
+ var stringToSign = (prepared, canonicalRequestHash) => `${prepared.algorithm}
227
+ ${prepared.datetime}
228
+ ${prepared.credentialScope}
229
+ ${canonicalRequestHash}`;
230
+ function authorize(prepared, accessKeyId, signature) {
231
+ prepared.headers.authorization = `${prepared.algorithm} Credential=${accessKeyId}/${prepared.credentialScope}, SignedHeaders=${prepared.signedHeaders}, Signature=${signature}`;
232
+ return prepared.headers;
233
+ }
234
+ function createKeys(hmac2) {
235
+ const signingKeys = /* @__PURE__ */ new Map();
236
+ const maxSigningKeys = 1e3;
237
+ let lastSecret;
238
+ let lastDate;
239
+ let lastRegion;
240
+ let lastService;
241
+ let lastKey;
242
+ return function getSigningKey2(secretAccessKey, date, region, service) {
243
+ if (secretAccessKey === lastSecret && date === lastDate && region === lastRegion && service === lastService) return lastKey;
244
+ const cacheKey = `${date}
245
+ ${region}
246
+ ${service}
247
+ ${secretAccessKey}`;
248
+ let signingKey = signingKeys.get(cacheKey);
249
+ if (signingKey === void 0) {
250
+ signingKey = hmac2(hmac2(hmac2(hmac2("AWS4" + secretAccessKey, date), region), service), "aws4_request");
251
+ if (signingKeys.size >= maxSigningKeys) signingKeys.delete(signingKeys.keys().next().value);
252
+ signingKeys.set(cacheKey, signingKey);
253
+ }
254
+ lastSecret = secretAccessKey;
255
+ lastDate = date;
256
+ lastRegion = region;
257
+ lastService = service;
258
+ lastKey = signingKey;
259
+ return signingKey;
260
+ };
261
+ }
262
+ function createSigner({ hexHash: hexHash2, hmacHex: hmacHex2, getSigningKey: getSigningKey2 }) {
263
+ return function getAuthHeaders2(request, credentials) {
264
+ const prepared = prepare(request, credentials, payloadHashOf(request, hexHash2));
265
+ return authorize(
266
+ prepared,
267
+ credentials.accessKeyId,
268
+ hmacHex2(getSigningKey2(credentials.secretAccessKey, prepared.date, prepared.region, prepared.service), stringToSign(prepared, hexHash2(prepared.canonicalRequest)))
269
+ );
270
+ };
271
+ }
272
+
273
+ // src/presign.js
274
+ var encodeQueryPart = (text) => encodeURIComponent(text).replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
275
+ function preparePresign(request, credentials, payloadHash, scheme = symmetric, extra = "") {
276
+ const { method, url, signHeaders } = request;
277
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new TypeError("presign needs an http or https URL");
278
+ if (url.search.length > 1 && (url.search.includes("X-Amz-Signature=") || url.search.includes("X-Amz-Algorithm="))) throw new TypeError("the URL is already presigned");
279
+ const expires = request.expires ?? 3600;
280
+ if (!Number.isInteger(expires) || expires < 1 || expires > 604800) throw new RangeError("expires must be a whole number of seconds from 1 to 604800 (7 days)");
281
+ const region = request.region ?? "";
282
+ const service = request.service ?? "s3";
283
+ const datetime = amzDatetime(request.now ?? Date.now());
284
+ const date = datetime.slice(0, 8);
285
+ const credentialScope = scheme.scope(date, region, service);
286
+ const names = ["host"];
287
+ const values = [url.host];
288
+ if (signHeaders !== void 0) for (const name of Object.keys(signHeaders)) insert(names, values, signHeaderName(name, signHeaders[name]), headerValue(signHeaders[name]));
289
+ let canonicalHeaders = "";
290
+ for (let i = 0; i < names.length; i++) canonicalHeaders += `${names[i]}:${values[i]}
291
+ `;
292
+ const signedHeaders = names.join(";");
293
+ let auth = `X-Amz-Algorithm=${scheme.algorithm}&X-Amz-Credential=${encodeQueryPart(`${credentials.accessKeyId}/${credentialScope}`)}&X-Amz-Date=${datetime}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=${encodeQueryPart(signedHeaders)}`;
294
+ if (credentials.sessionToken) auth += `&X-Amz-Security-Token=${encodeQueryPart(credentials.sessionToken)}`;
295
+ auth += extra;
296
+ const query = url.search.length > 1 ? url.search + "&" + auth : "?" + auth;
297
+ const hash = service === "s3" || request.unsignedPayload ? "UNSIGNED-PAYLOAD" : payloadHash;
298
+ const canonicalRequest = `${method}
299
+ ${canonicalPath(service, url.pathname)}
300
+ ${canonicalQuery(query)}
301
+ ${canonicalHeaders}
302
+ ${signedHeaders}
303
+ ${hash}`;
304
+ return { canonicalRequest, datetime, date, region, service, credentialScope, signedHeaders, algorithm: scheme.algorithm, base: url.origin + url.pathname, query };
305
+ }
306
+ var presignedUrl = (prepared, signature) => `${prepared.base}${prepared.query}&X-Amz-Signature=${signature}`;
307
+ function createPresigner({ hexHash: hexHash2, hmacHex: hmacHex2, getSigningKey: getSigningKey2 }) {
308
+ return function presign2(request, credentials) {
309
+ const prepared = preparePresign(request, credentials, payloadHashOf(request, hexHash2));
310
+ return presignedUrl(
311
+ prepared,
312
+ hmacHex2(getSigningKey2(credentials.secretAccessKey, prepared.date, prepared.region, prepared.service), stringToSign(prepared, hexHash2(prepared.canonicalRequest)))
313
+ );
314
+ };
315
+ }
316
+
317
+ // src/v4a.js
318
+ var asymmetric = { algorithm: "AWS4-ECDSA-P256-SHA256", scope: (date, _region, service) => `${date}/${service}/aws4_request` };
319
+ function regionSetOf(request) {
320
+ const regionSet = request.regionSet;
321
+ if (typeof regionSet !== "string" || regionSet === "") throw new TypeError("SigV4a needs request.regionSet, for example 'us-east-1,us-west-2' or '*'");
322
+ return regionSet;
323
+ }
324
+ var withRegionSet = (request) => ({ ...request, signHeaders: { ...request.signHeaders, "x-amz-region-set": regionSetOf(request) } });
325
+ var regionSetQuery = (request) => `&X-Amz-Region-Set=${encodeQueryPart(regionSetOf(request))}`;
326
+ var kdfInput = (accessKeyId, counter) => `\0\0\0AWS4-ECDSA-P256-SHA256\0${accessKeyId}${String.fromCharCode(counter)}\0\0\0`;
327
+ function scalarFrom(k) {
328
+ const nMinusTwo = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254fn;
329
+ let c = 0n;
330
+ for (let i = 0; i < 32; i++) c = c << 8n | BigInt(k[i]);
331
+ if (c > nMinusTwo) return void 0;
332
+ c += 1n;
333
+ const d = new Uint8Array(32);
334
+ for (let i = 31; i >= 0; i--, c >>= 8n) d[i] = Number(c & 0xffn);
335
+ return d;
336
+ }
337
+ function deriveP256(hmac2, accessKeyId, secretAccessKey) {
338
+ for (let counter = 1; counter < 128; counter++) {
339
+ const d = scalarFrom(hmac2("AWS4A" + secretAccessKey, kdfInput(accessKeyId, counter)));
340
+ if (d !== void 0) return d;
341
+ }
342
+ throw new Error("no SigV4a key for these credentials");
343
+ }
344
+ var signers = /* @__PURE__ */ new WeakMap();
345
+ function createAsymmetric(hmac2, ecdsa2) {
346
+ let sign = signers.get(ecdsa2);
347
+ if (sign === void 0) signers.set(ecdsa2, sign = signerFor(hmac2, ecdsa2));
348
+ return sign;
349
+ }
350
+ function signerFor(hmac2, ecdsa2) {
351
+ const keys = /* @__PURE__ */ new Map();
352
+ return function sign(credentials, text) {
353
+ const id = `${credentials.accessKeyId}/${credentials.secretAccessKey}`;
354
+ let key = keys.get(id);
355
+ if (key === void 0) {
356
+ key = ecdsa2.key(deriveP256(hmac2, credentials.accessKeyId, credentials.secretAccessKey));
357
+ if (keys.size >= 1e3) keys.delete(keys.keys().next().value);
358
+ keys.set(id, key);
359
+ }
360
+ return ecdsa2.sign(key, text);
361
+ };
362
+ }
363
+ function createSignerV4a({ hexHash: hexHash2, hmac: hmac2, ecdsa: ecdsa2 }) {
364
+ const sign = createAsymmetric(hmac2, ecdsa2);
365
+ return function getAuthHeadersV4a2(request, credentials) {
366
+ const prepared = prepare(withRegionSet(request), credentials, payloadHashOf(request, hexHash2), asymmetric);
367
+ return authorize(prepared, credentials.accessKeyId, sign(credentials, stringToSign(prepared, hexHash2(prepared.canonicalRequest))));
368
+ };
369
+ }
370
+ function createPresignerV4a({ hexHash: hexHash2, hmac: hmac2, ecdsa: ecdsa2 }) {
371
+ const sign = createAsymmetric(hmac2, ecdsa2);
372
+ return function presignV4a2(request, credentials) {
373
+ const prepared = preparePresign(request, credentials, payloadHashOf(request, hexHash2), asymmetric, regionSetQuery(request));
374
+ return presignedUrl(prepared, sign(credentials, stringToSign(prepared, hexHash2(prepared.canonicalRequest))));
375
+ };
376
+ }
377
+
378
+ // src/ecdsa-node.js
379
+ import crypto from "node:crypto";
380
+ var b64url = (bytes) => Buffer.from(bytes).toString("base64url");
381
+ var ecdsa = {
382
+ key(d) {
383
+ const ecdh = crypto.createECDH("prime256v1");
384
+ ecdh.setPrivateKey(d);
385
+ const point = ecdh.getPublicKey();
386
+ return crypto.createPrivateKey({ key: { kty: "EC", crv: "P-256", d: b64url(d), x: b64url(point.subarray(1, 33)), y: b64url(point.subarray(33)) }, format: "jwk" });
387
+ },
388
+ sign: (key, text) => crypto.sign("sha256", text, { key, dsaEncoding: "der" }).toString("hex")
389
+ };
390
+
391
+ // src/chunks.js
392
+ var utf8 = /* @__PURE__ */ new TextEncoder();
393
+ function seedOf(authHeaders) {
394
+ const auth = authHeaders.authorization;
395
+ if (typeof auth !== "string" || !auth.startsWith("AWS4-HMAC-SHA256 "))
396
+ throw new TypeError("authHeaders must be the headers getAuthHeaders returned (a SigV4a signature cannot start a chunk or event stream)");
397
+ const scope = auth.slice(auth.indexOf("Credential=") + 11, auth.indexOf(",")).split("/");
398
+ return {
399
+ signature: auth.slice(auth.lastIndexOf("Signature=") + 10),
400
+ datetime: authHeaders["x-amz-date"],
401
+ date: scope[1],
402
+ region: scope[2],
403
+ service: scope[3],
404
+ scope: scope.slice(1).join("/")
405
+ };
406
+ }
407
+ var chunkStringToSign = (datetime, scope, previous, payloadHash) => `AWS4-HMAC-SHA256-PAYLOAD
408
+ ${datetime}
409
+ ${scope}
410
+ ${previous}
411
+ ${emptyBodyHash}
412
+ ${payloadHash}`;
413
+ var trailerStringToSign = (datetime, scope, previous, trailerHash) => `AWS4-HMAC-SHA256-TRAILER
414
+ ${datetime}
415
+ ${scope}
416
+ ${previous}
417
+ ${trailerHash}`;
418
+ function canonicalTrailers(trailers) {
419
+ return Object.keys(trailers).map((name) => [name.toLowerCase(), String(trailers[name]).trim()]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([name, value]) => `${name}:${value}
420
+ `).join("");
421
+ }
422
+ function frameChunk(signature, data2) {
423
+ const head = utf8.encode(`${data2.length.toString(16)};chunk-signature=${signature}\r
424
+ `);
425
+ const out = new Uint8Array(head.length + data2.length + 2);
426
+ out.set(head);
427
+ out.set(data2, head.length);
428
+ out[out.length - 2] = 13;
429
+ out[out.length - 1] = 10;
430
+ return out;
431
+ }
432
+ function frameEnd(signature, trailers, trailerSignature) {
433
+ return utf8.encode(
434
+ trailers === void 0 ? `0;chunk-signature=${signature}\r
435
+ \r
436
+ ` : `0;chunk-signature=${signature}\r
437
+ ${trailers.replace(/\n/g, "\r\n")}x-amz-trailer-signature:${trailerSignature}\r
438
+ \r
439
+ `
440
+ );
441
+ }
442
+ function createChunkSigner({ hexHash: hexHash2, hmacHex: hmacHex2, getSigningKey: getSigningKey2 }) {
443
+ return function chunkSigner2(authHeaders, credentials) {
444
+ const seed = seedOf(authHeaders);
445
+ const key = getSigningKey2(credentials.secretAccessKey, seed.date, seed.region, seed.service);
446
+ let previous = seed.signature;
447
+ const sign = (data2) => previous = hmacHex2(key, chunkStringToSign(seed.datetime, seed.scope, previous, hexHash2(data2)));
448
+ return {
449
+ chunk: (data2) => data2.length === 0 ? data2 : frameChunk(sign(data2), data2),
450
+ // a zero-length chunk would end the upload; it is not sent
451
+ end(trailers) {
452
+ const signature = sign(new Uint8Array(0));
453
+ if (trailers === void 0) return frameEnd(signature);
454
+ const text = canonicalTrailers(trailers);
455
+ const trailerSignature = hmacHex2(key, trailerStringToSign(seed.datetime, seed.scope, signature, hexHash2(text)));
456
+ return frameEnd(signature, text, trailerSignature);
457
+ }
458
+ };
459
+ };
460
+ }
461
+
462
+ // src/events.js
463
+ var crcTable;
464
+ function crc32(bytes, start, end) {
465
+ if (crcTable === void 0) {
466
+ crcTable = new Int32Array(256);
467
+ for (let n = 0; n < 256; n++) {
468
+ let c = n;
469
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
470
+ crcTable[n] = c;
471
+ }
472
+ }
473
+ let crc = -1;
474
+ for (let i = start; i < end; i++) crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
475
+ return ~crc >>> 0;
476
+ }
477
+ function dateHeader(ms) {
478
+ const out = new Uint8Array(15);
479
+ out[0] = 5;
480
+ out.set(utf8.encode(":date"), 1);
481
+ out[6] = 8;
482
+ new DataView(out.buffer).setBigInt64(7, BigInt(Math.floor(ms)));
483
+ return out;
484
+ }
485
+ function frameEvent(dateBytes, signatureHex, inner2) {
486
+ const headersLength = dateBytes.length + 52;
487
+ const out = new Uint8Array(16 + headersLength + inner2.length);
488
+ const view = new DataView(out.buffer);
489
+ view.setUint32(0, out.length);
490
+ view.setUint32(4, headersLength);
491
+ view.setUint32(8, crc32(out, 0, 8));
492
+ out.set(dateBytes, 12);
493
+ let at = 12 + dateBytes.length;
494
+ out[at++] = 16;
495
+ out.set(utf8.encode(":chunk-signature"), at);
496
+ at += 16;
497
+ out[at++] = 6;
498
+ view.setUint16(at, 32);
499
+ at += 2;
500
+ for (let i = 0; i < 32; i++) out[at + i] = parseInt(signatureHex.slice(2 * i, 2 * i + 2), 16);
501
+ at += 32;
502
+ out.set(inner2, at);
503
+ view.setUint32(out.length - 4, crc32(out, 0, out.length - 4));
504
+ return out;
505
+ }
506
+ var eventStringToSign = (datetime, scope, previous, headersHash, payloadHash) => `AWS4-HMAC-SHA256-PAYLOAD
507
+ ${datetime}
508
+ ${scope}
509
+ ${previous}
510
+ ${headersHash}
511
+ ${payloadHash}`;
512
+ var eventScope = (ms, region, service) => {
513
+ const datetime = amzDatetime(ms);
514
+ return { datetime, scope: `${datetime.slice(0, 8)}/${region}/${service}/aws4_request`, date: datetime.slice(0, 8) };
515
+ };
516
+ function createEventSigner({ hexHash: hexHash2, hmacHex: hmacHex2, getSigningKey: getSigningKey2 }) {
517
+ return function eventSigner2(authHeaders, credentials) {
518
+ const seed = seedOf(authHeaders);
519
+ let previous = seed.signature;
520
+ return {
521
+ sign(message, now = Date.now()) {
522
+ const { datetime, scope, date } = eventScope(now, seed.region, seed.service);
523
+ const header = dateHeader(now);
524
+ previous = hmacHex2(
525
+ getSigningKey2(credentials.secretAccessKey, date, seed.region, seed.service),
526
+ eventStringToSign(datetime, scope, previous, hexHash2(header), hexHash2(message))
527
+ );
528
+ return frameEvent(header, previous, message);
529
+ }
530
+ };
531
+ };
532
+ }
533
+
534
+ // src/node.js
535
+ var utf82 = /* @__PURE__ */ new TextEncoder();
536
+ var maxData = 1024;
537
+ var inner = /* @__PURE__ */ new Uint8Array(64 + maxData);
538
+ var outer = /* @__PURE__ */ new Uint8Array(64 + 32);
539
+ var data = /* @__PURE__ */ inner.subarray(64);
540
+ var innerViews = [];
541
+ var blockKey;
542
+ var nibble = (c) => c > 96 ? c - 87 : c - 48;
543
+ function hmacHexOneShot(key, text) {
544
+ if (key !== blockKey) {
545
+ blockKey = key;
546
+ inner.fill(54, 0, 64);
547
+ outer.fill(92, 0, 64);
548
+ for (let i = 0; i < key.length; i++) {
549
+ inner[i] ^= key[i];
550
+ outer[i] ^= key[i];
551
+ }
552
+ }
553
+ const end = 64 + utf82.encodeInto(text, data).written;
554
+ const hex = crypto2.hash("sha256", innerViews[end] ??= inner.subarray(0, end), "hex");
555
+ for (let i = 0; i < 32; i++) outer[64 + i] = nibble(hex.charCodeAt(2 * i)) << 4 | nibble(hex.charCodeAt(2 * i + 1));
556
+ return crypto2.hash("sha256", outer, "hex");
557
+ }
558
+ var hmacHexStreaming = (key, text) => crypto2.createHmac("sha256", key).update(text).digest("hex");
559
+ var hmac = (key, text) => crypto2.createHmac("sha256", key).update(text).digest();
560
+ var oneShotWorks;
561
+ function probe() {
562
+ const key = hmac("probe", "key");
563
+ const text = "AWS4-HMAC-SHA256\n20130524T000000Z";
564
+ const works = hmacHexOneShot(key, text) === hmacHexStreaming(key, text);
565
+ blockKey = void 0;
566
+ return works;
567
+ }
568
+ var hmacHex = (key, text) => (oneShotWorks ??= probe()) && text.length * 3 <= maxData && key.length <= 64 ? hmacHexOneShot(key, text) : hmacHexStreaming(key, text);
569
+ var getSigningKey = /* @__PURE__ */ createKeys(hmac);
570
+ var hexHash = (text) => crypto2.hash("sha256", text, "hex");
571
+ var primitives = { hexHash, hmac, hmacHex, getSigningKey };
572
+ var getAuthHeaders = /* @__PURE__ */ createSigner(primitives);
573
+ var presign = /* @__PURE__ */ createPresigner(primitives);
574
+ var getAuthHeadersV4a = /* @__PURE__ */ createSignerV4a({ hexHash, hmac, ecdsa });
575
+ var presignV4a = /* @__PURE__ */ createPresignerV4a({ hexHash, hmac, ecdsa });
576
+ var chunkSigner = /* @__PURE__ */ createChunkSigner(primitives);
577
+ var eventSigner = /* @__PURE__ */ createEventSigner(primitives);
578
+ export {
579
+ chunkSigner,
580
+ eventSigner,
581
+ getAuthHeaders,
582
+ getAuthHeadersV4a,
583
+ presign,
584
+ presignV4a
585
+ };
package/index.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { SignRequest, Credentials, AuthHeaders } from './types/base.js';
2
+
3
+ export type { SignRequest, Credentials, AuthHeaders };
4
+
5
+ /** A SigV4a request: a SignRequest whose `regionSet` is required, for example 'us-east-1,us-west-2' or '*'. */
6
+ export type SignRequestV4a = SignRequest & { regionSet: string };
7
+
8
+ /** A value that is a promise on Cloudflare Workers and in browsers (WebCrypto has only promises) and the value itself on Node.js, Bun and Deno. Always `await` it. */
9
+ export type MaybePromise<T> = T | Promise<T>;
10
+
11
+ /** Signs an aws-chunked upload: `chunk` for each piece of the body in order, then `end`. */
12
+ export interface ChunkSigner {
13
+ chunk(data: Uint8Array): MaybePromise<Uint8Array>;
14
+ end(trailers?: Record<string, string>): MaybePromise<Uint8Array>;
15
+ }
16
+
17
+ /** Signs the messages of an event stream in order. */
18
+ export interface EventSigner {
19
+ sign(message: Uint8Array, now?: number): MaybePromise<Uint8Array>;
20
+ }
21
+
22
+ /**
23
+ * The headers to send with a request: authorization, x-amz-date, the session token header when there is one, any extra headers that were signed and, for S3 (or
24
+ * when `unsignedPayload` is set), x-amz-content-sha256. `request.service` defaults to 's3'.
25
+ *
26
+ * Node.js, Bun and Deno return the headers directly. Cloudflare Workers and browsers have only promise-based
27
+ * WebCrypto and return a promise. Always `await` the result and the same code runs everywhere.
28
+ */
29
+ export function getAuthHeaders(request: SignRequest, credentials: Credentials): MaybePromise<AuthHeaders>;
30
+
31
+ /** A presigned URL: the original URL with the authentication parameters and the signature added. `request.expires` is the lifetime in seconds (default 3600). */
32
+ export function presign(request: SignRequest, credentials: Credentials): MaybePromise<string>;
33
+
34
+ /** SigV4a (multi-region) headers; `request.regionSet` is required, for example 'us-east-1,us-west-2' or '*'. */
35
+ export function getAuthHeadersV4a(request: SignRequestV4a, credentials: Credentials): MaybePromise<AuthHeaders>;
36
+
37
+ /** A SigV4a presigned URL; `request.regionSet` is required. */
38
+ export function presignV4a(request: SignRequestV4a, credentials: Credentials): MaybePromise<string>;
39
+
40
+ /** Chunk signer for an upload sent with `x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD`; `authHeaders` are the headers getAuthHeaders returned for it. */
41
+ export function chunkSigner(authHeaders: AuthHeaders, credentials: Credentials): ChunkSigner;
42
+
43
+ /** Event-stream signer; `authHeaders` are the headers getAuthHeaders returned for the request that opened the stream. */
44
+ export function eventSigner(authHeaders: AuthHeaders, credentials: Credentials): EventSigner;