@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.
Files changed (2) hide show
  1. package/dist/sts/issue.js +85 -87
  2. 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
- // ---- shared encoder + payload hash cache ----
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
- let lastBody = null;
26
- let lastPayloadHash = null;
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 = base64ToArrayBuffer(input.privateKeyPkcs8Base64);
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
- const { signingKey } = await getSigningMaterial({ certBase64, privateKeyPkcs8Base64 });
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
- try {
73
- const der = new Uint8Array(base64ToArrayBuffer(normalizedCert));
74
- let offset = 0;
75
- // helper: read DER length (short + long form)
76
- function readLen() {
77
- const b = der[offset++];
78
- if ((b & 0x80) === 0)
79
- return b;
80
- const n = b & 0x7f;
81
- let len = 0;
82
- for (let i = 0; i < n; i++) {
83
- len = (len << 8) | der[offset++];
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
- return len;
86
- }
87
- // Certificate SEQUENCE
88
- if (der[offset++] !== 0x30)
89
- throw new Error("bad cert");
90
- readLen();
91
- // TBSCertificate SEQUENCE
92
- if (der[offset++] !== 0x30)
93
- throw new Error("bad tbs");
94
- readLen();
95
- // Optional version [0] EXPLICIT (0xa0)
96
- if (der[offset] === 0xa0) {
97
- offset++; // tag
98
- const vLen = readLen(); // length
99
- offset += vLen; // skip full version block
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
- // SerialNumber INTEGER
102
- if (der[offset++] !== 0x02)
103
- throw new Error("bad serial tag");
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 } = getCanonicalizedHeaders(baseHeaders);
142
+ const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
127
143
  const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
128
- const canonicalRequest = getCanonicalRequest({
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 === certBase64,
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
- const hex = new Array(bytes.length);
258
+ // build hex string with minimal allocations
259
+ let out = "";
240
260
  for (let i = 0; i < bytes.length; i++) {
241
- const h = bytes[i].toString(16);
242
- hex[i] = h.length === 1 ? "0" + h : h;
261
+ out += HEX_TABLE[bytes[i]];
243
262
  }
244
- return hex.join("");
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
- if (lastBody === body && lastPayloadHash) {
251
- console.debug("[sts-cache] payloadHash HIT");
252
- return lastPayloadHash;
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
- function base64ToArrayBuffer(base64) {
273
+ // Faster base64 decode → Uint8Array (avoids extra ArrayBuffer wrapping)
274
+ function base64ToBytes(base64) {
278
275
  const binary = atob(base64);
279
- const bytes = new Uint8Array(binary.length);
280
- for (let i = 0; i < binary.length; i++) {
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.buffer;
281
+ return bytes;
284
282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizamodo/aws-sts-core",
3
- "version": "0.2.39",
3
+ "version": "0.3.1",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",