@vizamodo/aws-sts-core 0.3.5 → 0.3.8
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/crypto/sha256.d.ts +2 -0
- package/dist/crypto/sha256.js +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sigv4/request.d.ts +17 -0
- package/dist/sigv4/request.js +124 -0
- package/dist/sts/issue.js +1 -17
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const textEncoder = new TextEncoder();
|
|
2
|
+
const HEX_TABLE = new Array(256);
|
|
3
|
+
for (let i = 0; i < 256; i++) {
|
|
4
|
+
HEX_TABLE[i] = (i < 16 ? "0" : "") + i.toString(16);
|
|
5
|
+
}
|
|
6
|
+
export function hex(buf) {
|
|
7
|
+
const arr = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
8
|
+
const out = new Array(arr.length);
|
|
9
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10
|
+
out[i] = HEX_TABLE[arr[i]];
|
|
11
|
+
}
|
|
12
|
+
return out.join("");
|
|
13
|
+
}
|
|
14
|
+
export async function sha256Hex(input) {
|
|
15
|
+
const hash = await crypto.subtle.digest("SHA-256", textEncoder.encode(input));
|
|
16
|
+
const bytes = new Uint8Array(hash);
|
|
17
|
+
const hexParts = new Array(32);
|
|
18
|
+
for (let i = 0; i < 32; i++) {
|
|
19
|
+
hexParts[i] = HEX_TABLE[bytes[i]];
|
|
20
|
+
}
|
|
21
|
+
return hexParts.join("");
|
|
22
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type AwsCredentials = {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
sessionToken?: string;
|
|
5
|
+
};
|
|
6
|
+
export type BuildSignedAwsRequestInput = {
|
|
7
|
+
service: string;
|
|
8
|
+
region: string;
|
|
9
|
+
target: string;
|
|
10
|
+
body: string;
|
|
11
|
+
contentType?: string;
|
|
12
|
+
credentials: AwsCredentials;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Build signed AWS JSON API request
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildSignedAwsRequest(input: BuildSignedAwsRequestInput): Promise<RequestInit>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
SigV4 signed AWS JSON request builder.
|
|
3
|
+
|
|
4
|
+
Designed for edge runtimes (Cloudflare Workers, Deno, Bun, Node 20+).
|
|
5
|
+
No AWS SDK dependency.
|
|
6
|
+
*/
|
|
7
|
+
import { sha256Hex, hex } from "../crypto/sha256";
|
|
8
|
+
const DEFAULT_CONTENT_TYPE = "application/x-amz-json-1.1";
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const signingKeyCache = new Map();
|
|
11
|
+
const MAX_SIGNING_KEY_CACHE = 64;
|
|
12
|
+
const hmacKeyCache = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* HMAC SHA256
|
|
15
|
+
*/
|
|
16
|
+
async function hmac(key, data) {
|
|
17
|
+
const rawKey = key instanceof Uint8Array ? key : new Uint8Array(key);
|
|
18
|
+
const keyId = hex(rawKey);
|
|
19
|
+
let cryptoKey = hmacKeyCache.get(keyId);
|
|
20
|
+
if (!cryptoKey) {
|
|
21
|
+
cryptoKey = await crypto.subtle.importKey("raw", rawKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
22
|
+
hmacKeyCache.set(keyId, cryptoKey);
|
|
23
|
+
}
|
|
24
|
+
return crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(data));
|
|
25
|
+
}
|
|
26
|
+
function toUint8(buf) {
|
|
27
|
+
return new Uint8Array(buf);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Derive SigV4 signing key
|
|
31
|
+
*/
|
|
32
|
+
async function getSignatureKey(secretKey, date, region, service) {
|
|
33
|
+
const kDate = await hmac(new TextEncoder().encode("AWS4" + secretKey), date);
|
|
34
|
+
const kRegion = await hmac(toUint8(kDate), region);
|
|
35
|
+
const kService = await hmac(toUint8(kRegion), service);
|
|
36
|
+
return hmac(toUint8(kService), "aws4_request");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build signed AWS JSON API request
|
|
40
|
+
*/
|
|
41
|
+
export async function buildSignedAwsRequest(input) {
|
|
42
|
+
const { service, region, target, body, credentials } = input;
|
|
43
|
+
const contentType = input.contentType ?? DEFAULT_CONTENT_TYPE;
|
|
44
|
+
const host = `${service}.${region}.amazonaws.com`;
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const iso = now.toISOString();
|
|
47
|
+
const amzDate = iso.slice(0, 4) +
|
|
48
|
+
iso.slice(5, 7) +
|
|
49
|
+
iso.slice(8, 10) +
|
|
50
|
+
"T" +
|
|
51
|
+
iso.slice(11, 13) +
|
|
52
|
+
iso.slice(14, 16) +
|
|
53
|
+
iso.slice(17, 19) +
|
|
54
|
+
"Z";
|
|
55
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
56
|
+
const canonicalHeaders = `content-type:${contentType}\n` +
|
|
57
|
+
`host:${host}\n` +
|
|
58
|
+
`x-amz-date:${amzDate}\n` +
|
|
59
|
+
`x-amz-target:${target}\n` +
|
|
60
|
+
(credentials.sessionToken
|
|
61
|
+
? `x-amz-security-token:${credentials.sessionToken}\n`
|
|
62
|
+
: "");
|
|
63
|
+
const signedHeaders = credentials.sessionToken
|
|
64
|
+
? "content-type;host;x-amz-date;x-amz-security-token;x-amz-target"
|
|
65
|
+
: "content-type;host;x-amz-date;x-amz-target";
|
|
66
|
+
const payloadHash = await sha256Hex(body);
|
|
67
|
+
const canonicalRequest = "POST\n" +
|
|
68
|
+
"/\n" +
|
|
69
|
+
"\n" +
|
|
70
|
+
canonicalHeaders +
|
|
71
|
+
"\n" +
|
|
72
|
+
signedHeaders +
|
|
73
|
+
"\n" +
|
|
74
|
+
payloadHash;
|
|
75
|
+
const credentialScope = dateStamp +
|
|
76
|
+
"/" +
|
|
77
|
+
region +
|
|
78
|
+
"/" +
|
|
79
|
+
service +
|
|
80
|
+
"/aws4_request";
|
|
81
|
+
const stringToSign = "AWS4-HMAC-SHA256\n" +
|
|
82
|
+
amzDate +
|
|
83
|
+
"\n" +
|
|
84
|
+
credentialScope +
|
|
85
|
+
"\n" +
|
|
86
|
+
await sha256Hex(canonicalRequest);
|
|
87
|
+
const cacheKey = await sha256Hex(credentials.secretAccessKey +
|
|
88
|
+
"|" +
|
|
89
|
+
dateStamp +
|
|
90
|
+
"|" +
|
|
91
|
+
region +
|
|
92
|
+
"|" +
|
|
93
|
+
service);
|
|
94
|
+
let signingKey = signingKeyCache.get(cacheKey);
|
|
95
|
+
if (!signingKey) {
|
|
96
|
+
signingKey = await getSignatureKey(credentials.secretAccessKey, dateStamp, region, service);
|
|
97
|
+
if (signingKeyCache.size > MAX_SIGNING_KEY_CACHE) {
|
|
98
|
+
const firstKey = signingKeyCache.keys().next().value;
|
|
99
|
+
if (firstKey)
|
|
100
|
+
signingKeyCache.delete(firstKey);
|
|
101
|
+
}
|
|
102
|
+
signingKeyCache.set(cacheKey, signingKey);
|
|
103
|
+
}
|
|
104
|
+
const signature = hex(await hmac(signingKey, stringToSign));
|
|
105
|
+
const authorization = "AWS4-HMAC-SHA256 " +
|
|
106
|
+
`Credential=${credentials.accessKeyId}/${credentialScope}, ` +
|
|
107
|
+
`SignedHeaders=${signedHeaders}, ` +
|
|
108
|
+
`Signature=${signature}`;
|
|
109
|
+
const headers = {
|
|
110
|
+
"Content-Type": contentType,
|
|
111
|
+
"X-Amz-Date": amzDate,
|
|
112
|
+
"X-Amz-Target": target,
|
|
113
|
+
Authorization: authorization,
|
|
114
|
+
Host: host
|
|
115
|
+
};
|
|
116
|
+
if (credentials.sessionToken) {
|
|
117
|
+
headers["X-Amz-Security-Token"] = credentials.sessionToken;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers,
|
|
122
|
+
body
|
|
123
|
+
};
|
|
124
|
+
}
|
package/dist/sts/issue.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buildCanonicalRequest } from "../sigv4/canonical";
|
|
|
3
3
|
import { buildStringToSign } from "../sigv4/string-to-sign";
|
|
4
4
|
import { signStringToSign } from "./signer";
|
|
5
5
|
import { InternalError } from "./errors";
|
|
6
|
+
import { sha256Hex } from "../crypto/sha256";
|
|
6
7
|
// ---- constants (cleaner configuration, no runtime cost) ----
|
|
7
8
|
const ALGORITHM = "AWS4-X509-ECDSA-SHA256";
|
|
8
9
|
const SERVICE = "rolesanywhere";
|
|
@@ -26,13 +27,6 @@ let cachedPrivateKeyBase64 = null;
|
|
|
26
27
|
let cachedCertSerialDec = null;
|
|
27
28
|
let cachedCertSerialSource = null;
|
|
28
29
|
const stsCredentialCache = new Map();
|
|
29
|
-
// ---- shared encoder ----
|
|
30
|
-
const textEncoder = new TextEncoder();
|
|
31
|
-
// Precomputed hex table for fast byte→hex conversion (no per-byte toString alloc)
|
|
32
|
-
const HEX_TABLE = new Array(256);
|
|
33
|
-
for (let i = 0; i < 256; i++) {
|
|
34
|
-
HEX_TABLE[i] = (i < 16 ? "0" : "") + i.toString(16);
|
|
35
|
-
}
|
|
36
30
|
async function getSigningMaterial(input) {
|
|
37
31
|
if (cachedSigningKey &&
|
|
38
32
|
cachedCertBase64 === input.certBase64 &&
|
|
@@ -261,16 +255,6 @@ function normalizeCert(raw) {
|
|
|
261
255
|
// remove whitespace and newlines to ensure stable cache keys
|
|
262
256
|
return raw.replace(/\s+/g, "");
|
|
263
257
|
}
|
|
264
|
-
async function sha256Hex(input) {
|
|
265
|
-
// Tận dụng textEncoder có sẵn
|
|
266
|
-
const hash = await crypto.subtle.digest("SHA-256", textEncoder.encode(input));
|
|
267
|
-
const bytes = new Uint8Array(hash);
|
|
268
|
-
const hexParts = new Array(32); // SHA-256 luôn là 32 bytes
|
|
269
|
-
for (let i = 0; i < 32; i++) {
|
|
270
|
-
hexParts[i] = HEX_TABLE[bytes[i]];
|
|
271
|
-
}
|
|
272
|
-
return hexParts.join("");
|
|
273
|
-
}
|
|
274
258
|
async function getCanonicalRequestHash(canonicalRequest) {
|
|
275
259
|
return sha256Hex(canonicalRequest);
|
|
276
260
|
}
|