@vizamodo/aws-sts-core 0.1.21 → 0.1.28

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 CHANGED
@@ -22,12 +22,12 @@ async function getSigningMaterial(input) {
22
22
  }
23
23
  function resolveSessionTtlByProfile(profile) {
24
24
  switch (profile) {
25
- case "hub-console-ro":
26
- return 12 * 60 * 60; // 12h
27
- case "hub-billing-ro":
28
- case "hub-billing-admin":
25
+ case "HubConsoleReadOnly":
26
+ return 1 * 60 * 60; // 12h
27
+ case "HubBillingReadonly":
28
+ case "HubBillingAdmin":
29
29
  return 2 * 60 * 60; // 2h
30
- case "hub-runtime":
30
+ case "HubRuntime":
31
31
  return 90 * 60; // 1h30m
32
32
  default:
33
33
  // fail-safe
@@ -42,49 +42,38 @@ function resolveSessionTtlByProfile(profile) {
42
42
  */
43
43
  export async function issueAwsCredentials(input) {
44
44
  const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, } = input;
45
- if (!roleArn || !profileArn || !trustAnchorArn || !region) {
46
- throw new InternalError("missing_aws_configuration");
47
- }
48
- const sessionTtl = resolveSessionTtlByProfile(profile);
49
- const signingMaterial = await getSigningMaterial({
50
- certBase64,
51
- privateKeyPkcs8Base64,
52
- });
53
- const signingKey = signingMaterial.signingKey;
54
- const method = "POST";
55
- const service = "rolesanywhere";
45
+ // 1. Kiểm tra đầu vào & Fix TTL an toàn
46
+ // AWS mặc định chỉ cho phép 3600s, hãy fix cứng 1h để loại trừ lỗi IAM
47
+ const sessionTtl = Math.min(resolveSessionTtlByProfile(profile), 3600);
48
+ const { signingKey } = await getSigningMaterial({ certBase64, privateKeyPkcs8Base64 });
49
+ // 2. Setup constants
56
50
  const host = `rolesanywhere.${region}.amazonaws.com`;
57
51
  const path = "/sessions";
58
52
  const now = new Date();
59
- const amzDate = now
60
- .toISOString()
61
- .replace(/\.\d{3}Z$/, "Z")
62
- .replace(/[:-]/g, "");
53
+ const amzDate = now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:-]/g, "");
63
54
  const dateStamp = amzDate.slice(0, 8);
64
- const body = JSON.stringify({
65
- trustAnchorArn,
66
- profileArn,
67
- roleArn,
68
- durationSeconds: sessionTtl,
69
- });
55
+ const service = "rolesanywhere";
56
+ // 3. Chuẩn bị Body & PEM Cert (Flat - Không xuống dòng)
57
+ const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
70
58
  const payloadHash = await sha256Hex(body);
71
- const pemCert = toPemCertificate(certBase64);
59
+ const pemCert = `-----BEGIN CERTIFICATE-----${certBase64.replace(/\s+/g, "")}-----END CERTIFICATE-----`;
60
+ // 4. Tính toán Signature (CẦN Host trong Canonical Request)
72
61
  const baseHeaders = {
73
- host,
74
62
  "content-type": "application/json",
63
+ "host": host,
75
64
  "x-amz-date": amzDate,
76
65
  "x-amz-x509-chain": pemCert,
77
66
  };
78
67
  const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
68
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
79
69
  const canonicalRequest = buildCanonicalRequest({
80
- method,
70
+ method: "POST",
81
71
  canonicalUri: path,
82
72
  query: "",
83
73
  canonicalHeaders,
84
74
  signedHeaders,
85
75
  payloadHash,
86
76
  });
87
- const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
88
77
  const stringToSign = buildStringToSign({
89
78
  algorithm: "AWS4-X509-ECDSA-SHA256",
90
79
  amzDate,
@@ -92,48 +81,44 @@ export async function issueAwsCredentials(input) {
92
81
  canonicalRequestHash: await sha256Hex(canonicalRequest),
93
82
  });
94
83
  const signatureHex = await signStringToSign(stringToSign, signingKey);
95
- const authorization = `AWS4-X509-ECDSA-SHA256 ` +
96
- `Credential=${roleArn}/${credentialScope}, ` +
97
- `SignedHeaders=${signedHeaders}, ` +
98
- `Signature=${signatureHex}`;
99
- const headers = {
100
- ...baseHeaders,
101
- Authorization: authorization,
102
- };
103
- let res;
84
+ // 5. Build Headers gửi đi (KHÔNG gửi Host header thủ công trong fetch)
85
+ const finalHeaders = new Headers({
86
+ "Content-Type": "application/json",
87
+ "X-Amz-Date": amzDate,
88
+ "X-Amz-X509-Chain": pemCert,
89
+ "Authorization": `AWS4-X509-ECDSA-SHA256 Credential=${roleArn}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`
90
+ });
91
+ // 6. Execution (fetch)
104
92
  try {
105
- res = await fetch(`https://${host}${path}`, {
106
- method,
107
- headers,
93
+ const res = await fetch(`https://${host}${path}`, {
94
+ method: "POST",
95
+ headers: finalHeaders,
108
96
  body,
109
97
  });
98
+ if (!res.ok) {
99
+ const errorBody = await res.text();
100
+ // Log cực kỳ chi tiết để soi Signature của AWS vs của mình
101
+ console.error("[aws-rejected]", {
102
+ status: res.status,
103
+ awsResponse: errorBody,
104
+ myCanonicalRequest: canonicalRequest, // Copy cái này ra so với AWS
105
+ });
106
+ throw new InternalError("aws_rejected");
107
+ }
108
+ const json = await res.json();
109
+ const creds = json?.credentialSet?.[0]?.credentials;
110
+ return {
111
+ accessKeyId: creds.accessKeyId,
112
+ secretAccessKey: creds.secretAccessKey,
113
+ sessionToken: creds.sessionToken,
114
+ expiration: creds.expiration,
115
+ };
110
116
  }
111
- catch {
117
+ catch (e) {
118
+ if (e instanceof InternalError)
119
+ throw e;
112
120
  throw new InternalError("aws_unreachable");
113
121
  }
114
- if (!res.ok) {
115
- throw new InternalError("aws_rejected");
116
- }
117
- let json;
118
- try {
119
- json = await res.json();
120
- }
121
- catch {
122
- throw new InternalError("invalid_aws_response");
123
- }
124
- const creds = json?.credentialSet?.[0]?.credentials;
125
- if (!creds?.accessKeyId ||
126
- !creds?.secretAccessKey ||
127
- !creds?.sessionToken ||
128
- !creds?.expiration) {
129
- throw new InternalError("malformed_aws_credentials");
130
- }
131
- return {
132
- accessKeyId: creds.accessKeyId,
133
- secretAccessKey: creds.secretAccessKey,
134
- sessionToken: creds.sessionToken,
135
- expiration: creds.expiration,
136
- };
137
122
  }
138
123
  // ---- helpers ----
139
124
  function toPemCertificate(base64) {
@@ -10,54 +10,55 @@ import { InternalError } from "./errors";
10
10
  export async function signStringToSign(stringToSign, signingKey) {
11
11
  try {
12
12
  const data = new TextEncoder().encode(stringToSign);
13
- const signatureRaw = await crypto.subtle.sign({
13
+ const signature = await crypto.subtle.sign({
14
14
  name: "ECDSA",
15
15
  hash: "SHA-256",
16
16
  }, signingKey, data);
17
- // WebCrypto trả về 64 bytes (R và S)
18
- // Cần convert sang DER format cho AWS
19
- const derSignature = rawToDer(new Uint8Array(signatureRaw));
17
+ const derSignature = rawEcdsaToDer(new Uint8Array(signature));
20
18
  return uint8ArrayToHex(derSignature);
21
19
  }
22
- catch (e) {
23
- console.error("[SIGNING_DEBUG]", e);
20
+ catch {
24
21
  throw new InternalError("signing_failed");
25
22
  }
26
23
  }
27
24
  /**
28
- * Convert IEEE P1363 (Raw) ECDSA signature to ASN.1 DER
25
+ * Convert WebCrypto ECDSA signature (IEEE P1363 raw format: r||s)
26
+ * to DER-encoded ASN.1 sequence required by AWS (SigV4 X.509 ECDSA).
29
27
  */
30
- function rawToDer(raw) {
31
- const r = raw.slice(0, 32);
32
- const s = raw.slice(32);
33
- const toAsn1Int = (bytes) => {
34
- // Loại bỏ các byte 0 ở đầu
35
- let start = 0;
36
- while (start < bytes.length - 1 && bytes[start] === 0)
37
- start++;
38
- let payload = bytes.slice(start);
39
- // Nếu bit cao nhất là 1, phải thêm 0x00 để không bị hiểu lầm là số âm
40
- if (payload[0] > 0x7f) {
41
- const padded = new Uint8Array(payload.length + 1);
42
- padded.set(payload, 1);
43
- return padded;
28
+ function rawEcdsaToDer(raw) {
29
+ if (raw.length % 2 !== 0) {
30
+ throw new InternalError("invalid_raw_signature_length");
31
+ }
32
+ const len = raw.length / 2;
33
+ const r = raw.slice(0, len);
34
+ const s = raw.slice(len);
35
+ const encodeInt = (bytes) => {
36
+ // Remove leading zeros
37
+ let i = 0;
38
+ while (i < bytes.length - 1 && bytes[i] === 0)
39
+ i++;
40
+ let v = bytes.slice(i);
41
+ // If high bit is set, prepend 0x00 to indicate positive integer
42
+ if (v[0] & 0x80) {
43
+ const extended = new Uint8Array(v.length + 1);
44
+ extended[0] = 0;
45
+ extended.set(v, 1);
46
+ v = extended;
44
47
  }
45
- return payload;
48
+ const result = new Uint8Array(2 + v.length);
49
+ result[0] = 0x02; // INTEGER tag
50
+ result[1] = v.length;
51
+ result.set(v, 2);
52
+ return result;
46
53
  };
47
- const rAsn1 = toAsn1Int(r);
48
- const sAsn1 = toAsn1Int(s);
49
- const length = rAsn1.length + sAsn1.length + 4;
50
- const der = new Uint8Array(length + 2);
51
- der[0] = 0x30; // Sequence
52
- der[1] = length;
53
- let offset = 2;
54
- der[offset++] = 0x02; // Integer tag
55
- der[offset++] = rAsn1.length;
56
- der.set(rAsn1, offset);
57
- offset += rAsn1.length;
58
- der[offset++] = 0x02; // Integer tag
59
- der[offset++] = sAsn1.length;
60
- der.set(sAsn1, offset);
54
+ const rDer = encodeInt(r);
55
+ const sDer = encodeInt(s);
56
+ const sequenceLen = rDer.length + sDer.length;
57
+ const der = new Uint8Array(2 + sequenceLen);
58
+ der[0] = 0x30; // SEQUENCE tag
59
+ der[1] = sequenceLen;
60
+ der.set(rDer, 2);
61
+ der.set(sDer, 2 + rDer.length);
61
62
  return der;
62
63
  }
63
64
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizamodo/aws-sts-core",
3
- "version": "0.1.21",
3
+ "version": "0.1.28",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",