@vizamodo/aws-sts-core 0.2.19 → 0.2.34

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 +108 -39
  2. package/package.json +3 -2
package/dist/sts/issue.js CHANGED
@@ -3,17 +3,44 @@ 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 + canonical request hash cache ----
24
+ const textEncoder = new TextEncoder();
25
+ let lastCanonicalRequestKey = null;
26
+ let lastCanonicalRequest = null;
27
+ let lastCanonicalRequestHash = null;
28
+ let lastBody = null;
29
+ let lastPayloadHash = null;
30
+ let lastBaseHeadersKey = null;
31
+ let lastCanonicalHeaders = null;
32
+ let lastSignedHeaders = null;
9
33
  async function getSigningMaterial(input) {
10
- if (cachedSigningKey && cachedCertBase64) {
34
+ if (cachedSigningKey &&
35
+ cachedCertBase64 === input.certBase64 &&
36
+ cachedPrivateKeyBase64 === input.privateKeyPkcs8Base64) {
11
37
  return { signingKey: cachedSigningKey, certBase64: cachedCertBase64 };
12
38
  }
13
39
  try {
14
- cachedCertBase64 = input.certBase64;
15
40
  const keyBuffer = base64ToArrayBuffer(input.privateKeyPkcs8Base64);
16
41
  cachedSigningKey = await crypto.subtle.importKey("pkcs8", keyBuffer, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
42
+ cachedCertBase64 = input.certBase64;
43
+ cachedPrivateKeyBase64 = input.privateKeyPkcs8Base64;
17
44
  return { signingKey: cachedSigningKey, certBase64: cachedCertBase64 };
18
45
  }
19
46
  catch {
@@ -21,22 +48,7 @@ async function getSigningMaterial(input) {
21
48
  }
22
49
  }
23
50
  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
- }
51
+ return PROFILE_TTL[profile] ?? DEFAULT_TTL;
40
52
  }
41
53
  /**
42
54
  * Issue short-lived AWS credentials via Roles Anywhere.
@@ -45,23 +57,19 @@ export async function issueAwsCredentials(input) {
45
57
  const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, } = input;
46
58
  // 1. Kiểm tra đầu vào & Fix TTL an toàn
47
59
  const sessionTtl = resolveSessionTtlByProfile(profile);
48
- console.log("profile:", profile, "sessionTtl:", sessionTtl);
49
60
  const { signingKey } = await getSigningMaterial({ certBase64, privateKeyPkcs8Base64 });
50
61
  // 2. Setup constants
51
- const host = `rolesanywhere.${region}.amazonaws.com`;
52
- const path = "/sessions";
62
+ const host = `${SERVICE}.${region}.amazonaws.com`;
63
+ const path = PATH;
53
64
  const now = new Date();
54
- const amzDate = now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:-]/g, "");
65
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
55
66
  const dateStamp = amzDate.slice(0, 8);
56
- const service = "rolesanywhere";
67
+ const service = SERVICE;
57
68
  // 3. Chuẩn bị Body & Cert
58
69
  const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
59
- const payloadHash = await sha256Hex(body);
70
+ const payloadHash = await getPayloadHash(body);
60
71
  // --- 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, "");
72
+ const normalizedCert = normalizeCert(certBase64);
65
73
  // --- REFACTOR: Parser ASN.1 an toàn để lấy Serial Number (DECIMAL) ---
66
74
  // --- MINIMAL DER WALK: extract serial number ---
67
75
  let certSerialDec;
@@ -98,7 +106,11 @@ export async function issueAwsCredentials(input) {
98
106
  if (der[offset++] !== 0x02)
99
107
  throw new Error("bad serial tag");
100
108
  const serialLen = readLen();
101
- const serial = der.slice(offset, offset + serialLen);
109
+ let serial = der.slice(offset, offset + serialLen);
110
+ // strip leading 0x00 padding if present (DER signed integer rule)
111
+ if (serial.length > 1 && serial[0] === 0x00) {
112
+ serial = serial.slice(1);
113
+ }
102
114
  certSerialDec = BigInt("0x" +
103
115
  Array.from(serial)
104
116
  .map(b => b.toString(16).padStart(2, "0"))
@@ -115,9 +127,9 @@ export async function issueAwsCredentials(input) {
115
127
  "x-amz-date": amzDate,
116
128
  "x-amz-x509": normalizedCert,
117
129
  };
118
- const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
130
+ const { canonicalHeaders, signedHeaders } = getCanonicalizedHeaders(baseHeaders);
119
131
  const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
120
- const canonicalRequest = buildCanonicalRequest({
132
+ const canonicalRequest = getCanonicalRequest({
121
133
  method: "POST",
122
134
  canonicalUri: path,
123
135
  query: "",
@@ -125,11 +137,12 @@ export async function issueAwsCredentials(input) {
125
137
  signedHeaders,
126
138
  payloadHash,
127
139
  });
140
+ const canonicalRequestHash = await getCanonicalRequestHash(canonicalRequest);
128
141
  const stringToSign = buildStringToSign({
129
- algorithm: "AWS4-X509-ECDSA-SHA256",
142
+ algorithm: ALGORITHM,
130
143
  amzDate,
131
144
  credentialScope,
132
- canonicalRequestHash: await sha256Hex(canonicalRequest),
145
+ canonicalRequestHash,
133
146
  });
134
147
  const signatureHex = await signStringToSign(stringToSign, signingKey);
135
148
  // 5. Build Authorization Header với số Serial (DECIMAL)
@@ -151,8 +164,7 @@ export async function issueAwsCredentials(input) {
151
164
  // Debug logs
152
165
  console.error("[aws-rejected] Request details", {
153
166
  status: res.status,
154
- response: errorBody,
155
- serial: certSerialDec
167
+ response: errorBody
156
168
  });
157
169
  throw new InternalError("aws_rejected");
158
170
  }
@@ -172,13 +184,70 @@ export async function issueAwsCredentials(input) {
172
184
  }
173
185
  }
174
186
  // ---- helpers ----
187
+ function normalizeCert(raw) {
188
+ if (raw.includes("BEGIN CERTIFICATE")) {
189
+ throw new InternalError("pem_not_allowed");
190
+ }
191
+ return raw.trim();
192
+ }
175
193
  async function sha256Hex(input) {
176
- const data = new TextEncoder().encode(input);
194
+ const data = textEncoder.encode(input);
177
195
  const hash = await crypto.subtle.digest("SHA-256", data);
178
196
  const bytes = new Uint8Array(hash);
179
- return Array.from(bytes)
180
- .map((b) => b.toString(16).padStart(2, "0"))
181
- .join("");
197
+ const hex = new Array(bytes.length);
198
+ for (let i = 0; i < bytes.length; i++) {
199
+ const h = bytes[i].toString(16);
200
+ hex[i] = h.length === 1 ? "0" + h : h;
201
+ }
202
+ return hex.join("");
203
+ }
204
+ async function getCanonicalRequestHash(canonicalRequest) {
205
+ if (lastCanonicalRequest === canonicalRequest && lastCanonicalRequestHash) {
206
+ return lastCanonicalRequestHash;
207
+ }
208
+ const hash = await sha256Hex(canonicalRequest);
209
+ lastCanonicalRequest = canonicalRequest;
210
+ lastCanonicalRequestHash = hash;
211
+ return hash;
212
+ }
213
+ async function getPayloadHash(body) {
214
+ if (lastBody === body && lastPayloadHash) {
215
+ return lastPayloadHash;
216
+ }
217
+ const hash = await sha256Hex(body);
218
+ lastBody = body;
219
+ lastPayloadHash = hash;
220
+ return hash;
221
+ }
222
+ function getCanonicalizedHeaders(baseHeaders) {
223
+ // build deterministic cache key without JSON.stringify allocation
224
+ const key = baseHeaders["content-type"] + "|" +
225
+ baseHeaders["host"] + "|" +
226
+ baseHeaders["x-amz-date"] + "|" +
227
+ baseHeaders["x-amz-x509"];
228
+ if (lastBaseHeadersKey === key && lastCanonicalHeaders && lastSignedHeaders) {
229
+ return { canonicalHeaders: lastCanonicalHeaders, signedHeaders: lastSignedHeaders };
230
+ }
231
+ const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
232
+ lastBaseHeadersKey = key;
233
+ lastCanonicalHeaders = canonicalHeaders;
234
+ lastSignedHeaders = signedHeaders;
235
+ return { canonicalHeaders, signedHeaders };
236
+ }
237
+ function getCanonicalRequest(input) {
238
+ const key = input.method + "|" +
239
+ input.canonicalUri + "|" +
240
+ input.query + "|" +
241
+ input.canonicalHeaders + "|" +
242
+ input.signedHeaders + "|" +
243
+ input.payloadHash;
244
+ if (lastCanonicalRequestKey === key && lastCanonicalRequest) {
245
+ return lastCanonicalRequest;
246
+ }
247
+ const req = buildCanonicalRequest(input);
248
+ lastCanonicalRequestKey = key;
249
+ lastCanonicalRequest = req;
250
+ return req;
182
251
  }
183
252
  function base64ToArrayBuffer(base64) {
184
253
  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.19",
3
+ "version": "0.2.34",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,8 @@
16
16
  "clean": "rm -rf dist",
17
17
  "prepublishOnly": "npm run build",
18
18
  "dev": "ts-node bin/viza.ts",
19
- "release:prod": "rm -rf dist && npx npm-check-updates -u && npm install && git add package.json package-lock.json && git commit -m 'chore(deps): auto update dependencies before release' || echo 'No changes' && node versioning.js && npm login && npm publish --tag latest --access public && git push"
19
+ "release:prod": "rm -rf dist && npx npm-check-updates -u && npm install && git add package.json package-lock.json && git commit -m 'chore(deps): auto update dependencies before release' || echo 'No changes' && node versioning.js && npm publish --tag latest --access public && git push",
20
+ "release:full": "rm -rf dist && npx npm-check-updates -u && npm install && git add package.json package-lock.json && git commit -m 'chore(deps): auto update dependencies before release' || echo 'No changes' && node versioning.js && npm login && npm publish --tag latest --access public && git push"
20
21
  },
21
22
  "devDependencies": {
22
23
  "typescript": "^5.9.3"