@vizamodo/aws-sts-core 0.2.21 → 0.2.39

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 +132 -38
  2. package/package.json +1 -1
package/dist/sts/issue.js CHANGED
@@ -3,17 +3,40 @@ 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
+ // ---- constants (cleaner configuration, no runtime cost) ----
7
+ const ALGORITHM = "AWS4-X509-ECDSA-SHA256";
8
+ const SERVICE = "rolesanywhere";
9
+ const PATH = "/sessions";
10
+ const PROFILE_TTL = {
11
+ Runtime: 90 * 60,
12
+ ConsoleReadOnly: 12 * 60 * 60,
13
+ ConsoleViewOnlyProd: 12 * 60 * 60,
14
+ BillingReadOnly: 2 * 60 * 60,
15
+ BillingAdmin: 2 * 60 * 60,
16
+ BillingAccountant: 2 * 60 * 60,
17
+ };
18
+ const DEFAULT_TTL = 2 * 60 * 60;
6
19
  // ---- isolate-level cached signing material ----
7
20
  let cachedSigningKey = null;
8
21
  let cachedCertBase64 = null;
22
+ let cachedPrivateKeyBase64 = null;
23
+ // ---- shared encoder + payload hash cache ----
24
+ const textEncoder = new TextEncoder();
25
+ let lastBody = null;
26
+ let lastPayloadHash = null;
9
27
  async function getSigningMaterial(input) {
10
- if (cachedSigningKey && cachedCertBase64) {
28
+ if (cachedSigningKey &&
29
+ cachedCertBase64 === input.certBase64 &&
30
+ cachedPrivateKeyBase64 === input.privateKeyPkcs8Base64) {
31
+ console.debug("[sts-cache] signingKey HIT");
11
32
  return { signingKey: cachedSigningKey, certBase64: cachedCertBase64 };
12
33
  }
34
+ console.debug("[sts-cache] signingKey MISS → importing key");
13
35
  try {
14
- cachedCertBase64 = input.certBase64;
15
36
  const keyBuffer = base64ToArrayBuffer(input.privateKeyPkcs8Base64);
16
37
  cachedSigningKey = await crypto.subtle.importKey("pkcs8", keyBuffer, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
38
+ cachedCertBase64 = input.certBase64;
39
+ cachedPrivateKeyBase64 = input.privateKeyPkcs8Base64;
17
40
  return { signingKey: cachedSigningKey, certBase64: cachedCertBase64 };
18
41
  }
19
42
  catch {
@@ -21,22 +44,7 @@ async function getSigningMaterial(input) {
21
44
  }
22
45
  }
23
46
  function resolveSessionTtlByProfile(profile) {
24
- switch (profile) {
25
- // Runtime
26
- case "Runtime":
27
- return 90 * 60; // 90 minutes
28
- // Console profiles
29
- case "ConsoleReadOnly":
30
- case "ConsoleViewOnlyProd":
31
- return 12 * 60 * 60; // 12 hours
32
- // Billing profiles
33
- case "BillingReadOnly":
34
- case "BillingAdmin":
35
- case "BillingAccountant":
36
- return 2 * 60 * 60; // 2 hours
37
- default:
38
- return 2 * 60 * 60; // fail-safe
39
- }
47
+ return PROFILE_TTL[profile] ?? DEFAULT_TTL;
40
48
  }
41
49
  /**
42
50
  * Issue short-lived AWS credentials via Roles Anywhere.
@@ -45,23 +53,19 @@ export async function issueAwsCredentials(input) {
45
53
  const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, } = input;
46
54
  // 1. Kiểm tra đầu vào & Fix TTL an toàn
47
55
  const sessionTtl = resolveSessionTtlByProfile(profile);
48
- console.log("profile:", profile, "sessionTtl:", sessionTtl);
49
56
  const { signingKey } = await getSigningMaterial({ certBase64, privateKeyPkcs8Base64 });
50
57
  // 2. Setup constants
51
- const host = `rolesanywhere.${region}.amazonaws.com`;
52
- const path = "/sessions";
58
+ const host = `${SERVICE}.${region}.amazonaws.com`;
59
+ const path = PATH;
53
60
  const now = new Date();
54
- const amzDate = now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:-]/g, "");
61
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
55
62
  const dateStamp = amzDate.slice(0, 8);
56
- const service = "rolesanywhere";
63
+ const service = SERVICE;
57
64
  // 3. Chuẩn bị Body & Cert
58
65
  const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
59
- const payloadHash = await sha256Hex(body);
66
+ const payloadHash = await getPayloadHash(body);
60
67
  // --- REFACTOR: Làm sạch chứng chỉ chặt chẽ ---
61
- const normalizedCert = certBase64
62
- .replace(/-----BEGIN CERTIFICATE-----/g, "")
63
- .replace(/-----END CERTIFICATE-----/g, "")
64
- .replace(/\s+/g, "");
68
+ const normalizedCert = normalizeCert(certBase64);
65
69
  // --- REFACTOR: Parser ASN.1 an toàn để lấy Serial Number (DECIMAL) ---
66
70
  // --- MINIMAL DER WALK: extract serial number ---
67
71
  let certSerialDec;
@@ -98,7 +102,11 @@ export async function issueAwsCredentials(input) {
98
102
  if (der[offset++] !== 0x02)
99
103
  throw new Error("bad serial tag");
100
104
  const serialLen = readLen();
101
- const serial = der.slice(offset, offset + serialLen);
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);
109
+ }
102
110
  certSerialDec = BigInt("0x" +
103
111
  Array.from(serial)
104
112
  .map(b => b.toString(16).padStart(2, "0"))
@@ -115,9 +123,9 @@ export async function issueAwsCredentials(input) {
115
123
  "x-amz-date": amzDate,
116
124
  "x-amz-x509": normalizedCert,
117
125
  };
118
- const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
126
+ const { canonicalHeaders, signedHeaders } = getCanonicalizedHeaders(baseHeaders);
119
127
  const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
120
- const canonicalRequest = buildCanonicalRequest({
128
+ const canonicalRequest = getCanonicalRequest({
121
129
  method: "POST",
122
130
  canonicalUri: path,
123
131
  query: "",
@@ -125,20 +133,56 @@ export async function issueAwsCredentials(input) {
125
133
  signedHeaders,
126
134
  payloadHash,
127
135
  });
136
+ console.debug("[sts-debug] canonicalRequest", {
137
+ amzDate,
138
+ signedHeaders,
139
+ payloadHash,
140
+ canonicalHeadersPreview: canonicalHeaders.slice(0, 120),
141
+ canonicalRequestPreview: canonicalRequest.slice(0, 200)
142
+ });
143
+ const canonicalRequestHash = await getCanonicalRequestHash(canonicalRequest);
144
+ console.debug("[sts-debug] canonicalRequestHash", canonicalRequestHash);
128
145
  const stringToSign = buildStringToSign({
129
- algorithm: "AWS4-X509-ECDSA-SHA256",
146
+ algorithm: ALGORITHM,
130
147
  amzDate,
131
148
  credentialScope,
132
- canonicalRequestHash: await sha256Hex(canonicalRequest),
149
+ canonicalRequestHash,
150
+ });
151
+ console.debug("[sts-debug] stringToSign", {
152
+ credentialScope,
153
+ stringPreview: stringToSign.slice(0, 200)
133
154
  });
134
155
  const signatureHex = await signStringToSign(stringToSign, signingKey);
156
+ console.debug("[sts-debug] signature", {
157
+ length: signatureHex.length,
158
+ prefix: signatureHex.slice(0, 32)
159
+ });
135
160
  // 5. Build Authorization Header với số Serial (DECIMAL)
136
161
  const finalHeaders = new Headers({
137
162
  "Content-Type": "application/json",
163
+ "Host": host,
138
164
  "X-Amz-Date": amzDate,
139
165
  "X-Amz-X509": normalizedCert,
140
166
  "Authorization": `AWS4-X509-ECDSA-SHA256 Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`
141
167
  });
168
+ console.debug("[sts-debug] requestHeaders", {
169
+ host,
170
+ amzDate,
171
+ signedHeaders,
172
+ certLen: normalizedCert.length
173
+ });
174
+ console.debug("[sts-request] issuing rolesanywhere session", {
175
+ region,
176
+ profile,
177
+ roleArn,
178
+ profileArn,
179
+ trustAnchorArn,
180
+ amzDate,
181
+ cacheState: {
182
+ hasSigningKey: !!cachedSigningKey,
183
+ cachedCertMatch: cachedCertBase64 === certBase64,
184
+ }
185
+ });
142
186
  // 6. Execution
143
187
  try {
144
188
  const res = await fetch(`https://${host}${path}`, {
@@ -152,7 +196,9 @@ export async function issueAwsCredentials(input) {
152
196
  console.error("[aws-rejected] Request details", {
153
197
  status: res.status,
154
198
  response: errorBody,
155
- serial: certSerialDec
199
+ region,
200
+ profile,
201
+ amzDate
156
202
  });
157
203
  throw new InternalError("aws_rejected");
158
204
  }
@@ -166,19 +212,67 @@ export async function issueAwsCredentials(input) {
166
212
  };
167
213
  }
168
214
  catch (e) {
215
+ console.error("[sts-debug] fetch failure", {
216
+ error: String(e),
217
+ region,
218
+ roleArn,
219
+ profileArn,
220
+ trustAnchorArn,
221
+ amzDate
222
+ });
169
223
  if (e instanceof InternalError)
170
224
  throw e;
171
225
  throw new InternalError("aws_unreachable");
172
226
  }
173
227
  }
174
228
  // ---- helpers ----
229
+ function normalizeCert(raw) {
230
+ if (raw.includes("BEGIN CERTIFICATE")) {
231
+ throw new InternalError("pem_not_allowed");
232
+ }
233
+ return raw.trim();
234
+ }
175
235
  async function sha256Hex(input) {
176
- const data = new TextEncoder().encode(input);
236
+ const data = textEncoder.encode(input);
177
237
  const hash = await crypto.subtle.digest("SHA-256", data);
178
238
  const bytes = new Uint8Array(hash);
179
- return Array.from(bytes)
180
- .map((b) => b.toString(16).padStart(2, "0"))
181
- .join("");
239
+ const hex = new Array(bytes.length);
240
+ for (let i = 0; i < bytes.length; i++) {
241
+ const h = bytes[i].toString(16);
242
+ hex[i] = h.length === 1 ? "0" + h : h;
243
+ }
244
+ return hex.join("");
245
+ }
246
+ async function getCanonicalRequestHash(canonicalRequest) {
247
+ return sha256Hex(canonicalRequest);
248
+ }
249
+ 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);
182
276
  }
183
277
  function base64ToArrayBuffer(base64) {
184
278
  const binary = atob(base64);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizamodo/aws-sts-core",
3
- "version": "0.2.21",
3
+ "version": "0.2.39",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",