@vizamodo/aws-sts-core 0.2.39 → 0.3.1
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/sts/issue.js +85 -87
- package/package.json +1 -1
package/dist/sts/issue.js
CHANGED
|
@@ -20,10 +20,16 @@ const DEFAULT_TTL = 2 * 60 * 60;
|
|
|
20
20
|
let cachedSigningKey = null;
|
|
21
21
|
let cachedCertBase64 = null;
|
|
22
22
|
let cachedPrivateKeyBase64 = null;
|
|
23
|
-
// ----
|
|
23
|
+
// ---- cached certificate serial (DER parse is expensive, cert rarely changes) ----
|
|
24
|
+
let cachedCertSerialDec = null;
|
|
25
|
+
let cachedCertSerialSource = null;
|
|
26
|
+
// ---- shared encoder ----
|
|
24
27
|
const textEncoder = new TextEncoder();
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// Precomputed hex table for fast byte→hex conversion (no per-byte toString alloc)
|
|
29
|
+
const HEX_TABLE = new Array(256);
|
|
30
|
+
for (let i = 0; i < 256; i++) {
|
|
31
|
+
HEX_TABLE[i] = (i < 16 ? "0" : "") + i.toString(16);
|
|
32
|
+
}
|
|
27
33
|
async function getSigningMaterial(input) {
|
|
28
34
|
if (cachedSigningKey &&
|
|
29
35
|
cachedCertBase64 === input.certBase64 &&
|
|
@@ -33,7 +39,7 @@ async function getSigningMaterial(input) {
|
|
|
33
39
|
}
|
|
34
40
|
console.debug("[sts-cache] signingKey MISS → importing key");
|
|
35
41
|
try {
|
|
36
|
-
const keyBuffer =
|
|
42
|
+
const keyBuffer = base64ToBytes(input.privateKeyPkcs8Base64);
|
|
37
43
|
cachedSigningKey = await crypto.subtle.importKey("pkcs8", keyBuffer, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
|
|
38
44
|
cachedCertBase64 = input.certBase64;
|
|
39
45
|
cachedPrivateKeyBase64 = input.privateKeyPkcs8Base64;
|
|
@@ -53,7 +59,13 @@ export async function issueAwsCredentials(input) {
|
|
|
53
59
|
const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, } = input;
|
|
54
60
|
// 1. Kiểm tra đầu vào & Fix TTL an toàn
|
|
55
61
|
const sessionTtl = resolveSessionTtlByProfile(profile);
|
|
56
|
-
|
|
62
|
+
// Normalize certificate once so every subsystem (cache, DER parse, headers)
|
|
63
|
+
// uses the exact same canonical string
|
|
64
|
+
const normalizedCert = normalizeCert(certBase64);
|
|
65
|
+
const { signingKey } = await getSigningMaterial({
|
|
66
|
+
certBase64: normalizedCert,
|
|
67
|
+
privateKeyPkcs8Base64,
|
|
68
|
+
});
|
|
57
69
|
// 2. Setup constants
|
|
58
70
|
const host = `${SERVICE}.${region}.amazonaws.com`;
|
|
59
71
|
const path = PATH;
|
|
@@ -65,56 +77,60 @@ export async function issueAwsCredentials(input) {
|
|
|
65
77
|
const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
|
|
66
78
|
const payloadHash = await getPayloadHash(body);
|
|
67
79
|
// --- REFACTOR: Làm sạch chứng chỉ chặt chẽ ---
|
|
68
|
-
const normalizedCert = normalizeCert(certBase64);
|
|
80
|
+
// const normalizedCert = normalizeCert(certBase64);
|
|
69
81
|
// --- REFACTOR: Parser ASN.1 an toàn để lấy Serial Number (DECIMAL) ---
|
|
70
82
|
// --- MINIMAL DER WALK: extract serial number ---
|
|
71
83
|
let certSerialDec;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
85
|
+
console.debug("[sts-cache] certSerial HIT");
|
|
86
|
+
certSerialDec = cachedCertSerialDec;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.debug("[sts-cache] certSerial MISS → parsing DER");
|
|
90
|
+
try {
|
|
91
|
+
const der = base64ToBytes(normalizedCert);
|
|
92
|
+
let offset = 0;
|
|
93
|
+
function readLen() {
|
|
94
|
+
const b = der[offset++];
|
|
95
|
+
if ((b & 0x80) === 0)
|
|
96
|
+
return b;
|
|
97
|
+
const n = b & 0x7f;
|
|
98
|
+
let len = 0;
|
|
99
|
+
for (let i = 0; i < n; i++) {
|
|
100
|
+
len = (len << 8) | der[offset++];
|
|
101
|
+
}
|
|
102
|
+
return len;
|
|
84
103
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
104
|
+
if (der[offset++] !== 0x30)
|
|
105
|
+
throw new Error("bad cert");
|
|
106
|
+
readLen();
|
|
107
|
+
if (der[offset++] !== 0x30)
|
|
108
|
+
throw new Error("bad tbs");
|
|
109
|
+
readLen();
|
|
110
|
+
if (der[offset] === 0xa0) {
|
|
111
|
+
offset++;
|
|
112
|
+
const vLen = readLen();
|
|
113
|
+
offset += vLen;
|
|
114
|
+
}
|
|
115
|
+
if (der[offset++] !== 0x02)
|
|
116
|
+
throw new Error("bad serial tag");
|
|
117
|
+
const serialLen = readLen();
|
|
118
|
+
let serial = der.slice(offset, offset + serialLen);
|
|
119
|
+
if (serial.length > 1 && serial[0] === 0x00) {
|
|
120
|
+
serial = serial.slice(1);
|
|
121
|
+
}
|
|
122
|
+
let hex = "";
|
|
123
|
+
for (let i = 0; i < serial.length; i++) {
|
|
124
|
+
hex += HEX_TABLE[serial[i]];
|
|
125
|
+
}
|
|
126
|
+
certSerialDec = BigInt("0x" + hex).toString();
|
|
127
|
+
cachedCertSerialDec = certSerialDec;
|
|
128
|
+
cachedCertSerialSource = normalizedCert;
|
|
100
129
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw new
|
|
104
|
-
const serialLen = readLen();
|
|
105
|
-
let serial = der.slice(offset, offset + serialLen);
|
|
106
|
-
// strip leading 0x00 padding if present (DER signed integer rule)
|
|
107
|
-
if (serial.length > 1 && serial[0] === 0x00) {
|
|
108
|
-
serial = serial.slice(1);
|
|
130
|
+
catch (e) {
|
|
131
|
+
console.error("[issueAwsCredentials] Failed to parse cert serial", e);
|
|
132
|
+
throw new InternalError("invalid_cert_der");
|
|
109
133
|
}
|
|
110
|
-
certSerialDec = BigInt("0x" +
|
|
111
|
-
Array.from(serial)
|
|
112
|
-
.map(b => b.toString(16).padStart(2, "0"))
|
|
113
|
-
.join("")).toString();
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
console.error("[issueAwsCredentials] Failed to parse cert serial", e);
|
|
117
|
-
throw new InternalError("invalid_cert_der");
|
|
118
134
|
}
|
|
119
135
|
// 4. Tính toán Signature
|
|
120
136
|
const baseHeaders = {
|
|
@@ -123,9 +139,9 @@ export async function issueAwsCredentials(input) {
|
|
|
123
139
|
"x-amz-date": amzDate,
|
|
124
140
|
"x-amz-x509": normalizedCert,
|
|
125
141
|
};
|
|
126
|
-
const { canonicalHeaders, signedHeaders } =
|
|
142
|
+
const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
|
|
127
143
|
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
|
|
128
|
-
const canonicalRequest =
|
|
144
|
+
const canonicalRequest = buildCanonicalRequest({
|
|
129
145
|
method: "POST",
|
|
130
146
|
canonicalUri: path,
|
|
131
147
|
query: "",
|
|
@@ -160,7 +176,6 @@ export async function issueAwsCredentials(input) {
|
|
|
160
176
|
// 5. Build Authorization Header với số Serial (DECIMAL)
|
|
161
177
|
const finalHeaders = new Headers({
|
|
162
178
|
"Content-Type": "application/json",
|
|
163
|
-
"Host": host,
|
|
164
179
|
"X-Amz-Date": amzDate,
|
|
165
180
|
"X-Amz-X509": normalizedCert,
|
|
166
181
|
"Authorization": `AWS4-X509-ECDSA-SHA256 Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`
|
|
@@ -180,7 +195,7 @@ export async function issueAwsCredentials(input) {
|
|
|
180
195
|
amzDate,
|
|
181
196
|
cacheState: {
|
|
182
197
|
hasSigningKey: !!cachedSigningKey,
|
|
183
|
-
cachedCertMatch: cachedCertBase64 ===
|
|
198
|
+
cachedCertMatch: cachedCertBase64 === normalizedCert,
|
|
184
199
|
}
|
|
185
200
|
});
|
|
186
201
|
// 6. Execution
|
|
@@ -204,6 +219,10 @@ export async function issueAwsCredentials(input) {
|
|
|
204
219
|
}
|
|
205
220
|
const json = await res.json();
|
|
206
221
|
const creds = json?.credentialSet?.[0]?.credentials;
|
|
222
|
+
if (!creds?.accessKeyId || !creds?.secretAccessKey || !creds?.sessionToken) {
|
|
223
|
+
console.error("[issueAwsCredentials] malformed AWS credential response", { json });
|
|
224
|
+
throw new InternalError("aws_malformed_credentials");
|
|
225
|
+
}
|
|
207
226
|
return {
|
|
208
227
|
accessKeyId: creds.accessKeyId,
|
|
209
228
|
secretAccessKey: creds.secretAccessKey,
|
|
@@ -236,49 +255,28 @@ async function sha256Hex(input) {
|
|
|
236
255
|
const data = textEncoder.encode(input);
|
|
237
256
|
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
238
257
|
const bytes = new Uint8Array(hash);
|
|
239
|
-
|
|
258
|
+
// build hex string with minimal allocations
|
|
259
|
+
let out = "";
|
|
240
260
|
for (let i = 0; i < bytes.length; i++) {
|
|
241
|
-
|
|
242
|
-
hex[i] = h.length === 1 ? "0" + h : h;
|
|
261
|
+
out += HEX_TABLE[bytes[i]];
|
|
243
262
|
}
|
|
244
|
-
return
|
|
263
|
+
return out;
|
|
245
264
|
}
|
|
246
265
|
async function getCanonicalRequestHash(canonicalRequest) {
|
|
247
266
|
return sha256Hex(canonicalRequest);
|
|
248
267
|
}
|
|
249
268
|
async function getPayloadHash(body) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
console.debug("[sts-cache] payloadHash MISS");
|
|
255
|
-
const hash = await sha256Hex(body);
|
|
256
|
-
lastBody = body;
|
|
257
|
-
lastPayloadHash = hash;
|
|
258
|
-
return hash;
|
|
259
|
-
}
|
|
260
|
-
function getCanonicalizedHeaders(baseHeaders) {
|
|
261
|
-
// Debug input headers before canonicalization
|
|
262
|
-
console.debug("[sts-debug] canonicalizeHeaders input", {
|
|
263
|
-
headers: baseHeaders,
|
|
264
|
-
keys: Object.keys(baseHeaders),
|
|
265
|
-
});
|
|
266
|
-
const result = canonicalizeHeaders(baseHeaders);
|
|
267
|
-
// Debug canonicalization result
|
|
268
|
-
console.debug("[sts-debug] canonicalizeHeaders output", {
|
|
269
|
-
canonicalHeaders: result.canonicalHeaders,
|
|
270
|
-
signedHeaders: result.signedHeaders,
|
|
271
|
-
});
|
|
272
|
-
return result;
|
|
273
|
-
}
|
|
274
|
-
function getCanonicalRequest(input) {
|
|
275
|
-
return buildCanonicalRequest(input);
|
|
269
|
+
// Payload changes are rare but SHA‑256 here is extremely cheap (~tens of µs),
|
|
270
|
+
// so caching adds complexity without measurable benefit.
|
|
271
|
+
return sha256Hex(body);
|
|
276
272
|
}
|
|
277
|
-
|
|
273
|
+
// Faster base64 decode → Uint8Array (avoids extra ArrayBuffer wrapping)
|
|
274
|
+
function base64ToBytes(base64) {
|
|
278
275
|
const binary = atob(base64);
|
|
279
|
-
const
|
|
280
|
-
|
|
276
|
+
const len = binary.length;
|
|
277
|
+
const bytes = new Uint8Array(len);
|
|
278
|
+
for (let i = 0; i < len; i++) {
|
|
281
279
|
bytes[i] = binary.charCodeAt(i);
|
|
282
280
|
}
|
|
283
|
-
return bytes
|
|
281
|
+
return bytes;
|
|
284
282
|
}
|