@vizamodo/aws-sts-core 0.3.47 → 0.3.48
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/federation/login.js +2 -31
- package/dist/sts/issue.d.ts +11 -2
- package/dist/sts/issue.js +155 -148
- package/package.json +2 -2
package/dist/federation/login.js
CHANGED
|
@@ -11,29 +11,17 @@ export async function buildFederationLoginUrl(input) {
|
|
|
11
11
|
throw new Error("[federation] invalid or expired credentials");
|
|
12
12
|
}
|
|
13
13
|
const tokenHash = await sha256Hex(input.sessionToken);
|
|
14
|
-
//
|
|
15
|
-
const cacheKey = `aws-signin
|
|
14
|
+
// Use region and tokenHash for cache key
|
|
15
|
+
const cacheKey = `aws-signin:${input.region}:${tokenHash}`;
|
|
16
16
|
const sessionJson = JSON.stringify(session);
|
|
17
17
|
const encoded = encodeURIComponent(sessionJson);
|
|
18
18
|
const SigninToken = await getCachedOrFetch(cacheKey, async () => {
|
|
19
|
-
console.debug("[signin] fetcher start", {
|
|
20
|
-
cacheKey,
|
|
21
|
-
expiration: input.expiration,
|
|
22
|
-
now: Date.now()
|
|
23
|
-
});
|
|
24
19
|
const tokenResp = await fetch(`https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=${encoded}`);
|
|
25
|
-
console.debug("[signin] fetch response", {
|
|
26
|
-
ok: tokenResp.ok,
|
|
27
|
-
status: tokenResp.status
|
|
28
|
-
});
|
|
29
20
|
if (!tokenResp.ok) {
|
|
30
21
|
// best-effort: do not cache failures
|
|
31
22
|
throw new Error("[signin] failed to fetch SigninToken");
|
|
32
23
|
}
|
|
33
24
|
const json = await tokenResp.json();
|
|
34
|
-
console.debug("[signin] json parsed", {
|
|
35
|
-
hasToken: !!json?.SigninToken
|
|
36
|
-
});
|
|
37
25
|
const token = json?.SigninToken;
|
|
38
26
|
if (!token) {
|
|
39
27
|
// do not cache invalid response
|
|
@@ -43,29 +31,12 @@ export async function buildFederationLoginUrl(input) {
|
|
|
43
31
|
const SIGNIN_TOKEN_TTL_SEC = 15 * 60;
|
|
44
32
|
// derive effective TTL = min(tokenTTL, credentialTTL)
|
|
45
33
|
const credRemainingSec = Math.floor((Date.parse(input.expiration) - Date.now()) / 1000);
|
|
46
|
-
console.debug("[signin] TTL compute", {
|
|
47
|
-
credRemainingSec,
|
|
48
|
-
expiration: input.expiration,
|
|
49
|
-
now: Date.now()
|
|
50
|
-
});
|
|
51
34
|
const effectiveTtlSec = Math.min(SIGNIN_TOKEN_TTL_SEC, credRemainingSec);
|
|
52
|
-
console.debug("[signin] effective TTL", {
|
|
53
|
-
effectiveTtlSec,
|
|
54
|
-
SIGNIN_TOKEN_TTL_SEC
|
|
55
|
-
});
|
|
56
35
|
// if credential too close to expiry → skip caching
|
|
57
36
|
if (effectiveTtlSec <= 0) {
|
|
58
|
-
console.debug("[signin] skip cache (ttl<=0)", {
|
|
59
|
-
effectiveTtlSec
|
|
60
|
-
});
|
|
61
37
|
return token;
|
|
62
38
|
}
|
|
63
39
|
const expiryIso = new Date(Date.now() + effectiveTtlSec * 1000).toISOString();
|
|
64
|
-
console.debug("[signin] wrapResult", {
|
|
65
|
-
effectiveTtlSec,
|
|
66
|
-
expiryIso,
|
|
67
|
-
now: Date.now()
|
|
68
|
-
});
|
|
69
40
|
// restore primitive storage (cache layer now handles primitive safely)
|
|
70
41
|
return wrapResult(token, expiryIso);
|
|
71
42
|
}, { ttlSec: 60, ...(input.forceRefresh !== undefined ? { forceRefresh: input.forceRefresh } : {}) } // allow caller-controlled retry
|
package/dist/sts/issue.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AwsCredentialResult } from "../types";
|
|
2
|
-
export
|
|
2
|
+
export interface IssueAwsCredentialsInput {
|
|
3
3
|
roleArn: string;
|
|
4
4
|
profileArn: string;
|
|
5
5
|
trustAnchorArn: string;
|
|
@@ -8,4 +8,13 @@ export declare function issueAwsCredentials(input: {
|
|
|
8
8
|
privateKeyPkcs8Base64: string;
|
|
9
9
|
profile: string;
|
|
10
10
|
forceRefresh?: boolean;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reset isolate-level signing material and cert serial caches.
|
|
14
|
+
* ONLY for use in tests — forces re-import of signing key on next call.
|
|
15
|
+
*
|
|
16
|
+
* L1 cache is bypassed in tests via `forceRefresh: true` on each call —
|
|
17
|
+
* there is no need to expose a cache-clear API from the core library.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resetIsolateCache(): void;
|
|
20
|
+
export declare function issueAwsCredentials(input: IssueAwsCredentialsInput): Promise<AwsCredentialResult>;
|
package/dist/sts/issue.js
CHANGED
|
@@ -5,10 +5,14 @@ import { buildStringToSign } from "../sigv4/string-to-sign";
|
|
|
5
5
|
import { signStringToSign } from "./signer";
|
|
6
6
|
import { InternalError } from "./errors";
|
|
7
7
|
import { sha256Hex } from "../crypto/sha256";
|
|
8
|
-
//
|
|
8
|
+
// ── Constants ──────────────────────────────────────────────────────────────
|
|
9
9
|
const ALGORITHM = "AWS4-X509-ECDSA-SHA256";
|
|
10
10
|
const SERVICE = "rolesanywhere";
|
|
11
11
|
const PATH = "/sessions";
|
|
12
|
+
/**
|
|
13
|
+
* Per-profile AWS session duration (seconds).
|
|
14
|
+
* Clamped to [MIN_SESSION_TTL, MAX_SESSION_TTL] at resolution time.
|
|
15
|
+
*/
|
|
12
16
|
const PROFILE_TTL = {
|
|
13
17
|
Runtime: 45 * 60,
|
|
14
18
|
ConsoleReadOnly: 12 * 60 * 60,
|
|
@@ -17,178 +21,183 @@ const PROFILE_TTL = {
|
|
|
17
21
|
BillingAdmin: 3 * 60 * 60,
|
|
18
22
|
BillingAccountant: 3 * 60 * 60,
|
|
19
23
|
};
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
//
|
|
24
|
-
// Single Promise
|
|
24
|
+
const DEFAULT_SESSION_TTL = 2 * 60 * 60;
|
|
25
|
+
const MIN_SESSION_TTL = 45 * 60; // 45 min
|
|
26
|
+
const MAX_SESSION_TTL = 12 * 60 * 60; // 12 h
|
|
27
|
+
// ── Isolate-level signing-material cache ───────────────────────────────────
|
|
28
|
+
// Single Promise slot prevents concurrent cold-start import races.
|
|
25
29
|
let signingKeyPromise = null;
|
|
26
30
|
let cachedSigningKey = null;
|
|
27
31
|
let cachedCertBase64 = null;
|
|
28
32
|
let cachedPrivateKeyBase64 = null;
|
|
29
|
-
//
|
|
33
|
+
// ── Isolate-level cert-serial cache ───────────────────────────────────────
|
|
34
|
+
// DER walk is CPU-bound; the cert rarely rotates within an isolate lifetime.
|
|
30
35
|
let cachedCertSerialDec = null;
|
|
31
36
|
let cachedCertSerialSource = null;
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
// ── Test utilities ─────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Reset isolate-level signing material and cert serial caches.
|
|
40
|
+
* ONLY for use in tests — forces re-import of signing key on next call.
|
|
41
|
+
*
|
|
42
|
+
* L1 cache is bypassed in tests via `forceRefresh: true` on each call —
|
|
43
|
+
* there is no need to expose a cache-clear API from the core library.
|
|
44
|
+
*/
|
|
45
|
+
export function resetIsolateCache() {
|
|
46
|
+
signingKeyPromise = null;
|
|
47
|
+
cachedSigningKey = null;
|
|
48
|
+
cachedCertBase64 = null;
|
|
49
|
+
cachedPrivateKeyBase64 = null;
|
|
50
|
+
cachedCertSerialDec = null;
|
|
51
|
+
cachedCertSerialSource = null;
|
|
52
|
+
}
|
|
53
|
+
// ── Signing material ───────────────────────────────────────────────────────
|
|
54
|
+
async function getSigningMaterial(certBase64, privateKeyPkcs8Base64) {
|
|
55
|
+
// Fast path: same material already imported in this isolate.
|
|
37
56
|
if (cachedSigningKey &&
|
|
38
|
-
cachedCertBase64 ===
|
|
39
|
-
cachedPrivateKeyBase64 ===
|
|
40
|
-
return
|
|
57
|
+
cachedCertBase64 === certBase64 &&
|
|
58
|
+
cachedPrivateKeyBase64 === privateKeyPkcs8Base64) {
|
|
59
|
+
return cachedSigningKey;
|
|
41
60
|
}
|
|
42
|
-
//
|
|
43
|
-
if (
|
|
44
|
-
|
|
61
|
+
// If material changed → reset cached key + promise (once)
|
|
62
|
+
if (cachedCertBase64 !== certBase64 ||
|
|
63
|
+
cachedPrivateKeyBase64 !== privateKeyPkcs8Base64) {
|
|
45
64
|
cachedSigningKey = null;
|
|
65
|
+
signingKeyPromise = null;
|
|
46
66
|
}
|
|
67
|
+
// Deduplicate concurrent imports
|
|
47
68
|
if (!signingKeyPromise) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
signingKeyPromise = crypto.subtle
|
|
70
|
+
.importKey("pkcs8", base64ToBytes(privateKeyPkcs8Base64), { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"])
|
|
71
|
+
.then((key) => {
|
|
72
|
+
cachedSigningKey = key;
|
|
73
|
+
cachedCertBase64 = certBase64;
|
|
74
|
+
cachedPrivateKeyBase64 = privateKeyPkcs8Base64;
|
|
75
|
+
return key;
|
|
76
|
+
})
|
|
77
|
+
.catch(() => {
|
|
78
|
+
signingKeyPromise = null; // allow retry
|
|
53
79
|
throw new InternalError("invalid_signing_material");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
cachedSigningKey = await signingKeyPromise;
|
|
58
|
-
cachedCertBase64 = input.certBase64;
|
|
59
|
-
cachedPrivateKeyBase64 = input.privateKeyPkcs8Base64;
|
|
60
|
-
return { signingKey: cachedSigningKey, certBase64: cachedCertBase64 };
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
signingKeyPromise = null; // allow retry on next call
|
|
64
|
-
throw new InternalError("invalid_signing_material");
|
|
80
|
+
});
|
|
65
81
|
}
|
|
82
|
+
return signingKeyPromise;
|
|
66
83
|
}
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const ttl = PROFILE_TTL[profile] ?? DEFAULT_TTL;
|
|
72
|
-
return Math.min(Math.max(ttl, MIN_PROFILE_TTL), MAX_PROFILE_TTL);
|
|
84
|
+
// ── Session TTL ────────────────────────────────────────────────────────────
|
|
85
|
+
function resolveSessionTtl(profile) {
|
|
86
|
+
const ttl = PROFILE_TTL[profile] ?? DEFAULT_SESSION_TTL;
|
|
87
|
+
return Math.min(Math.max(ttl, MIN_SESSION_TTL), MAX_SESSION_TTL);
|
|
73
88
|
}
|
|
74
|
-
//
|
|
75
|
-
// Main export
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
89
|
+
// ── Main export ────────────────────────────────────────────────────────────
|
|
77
90
|
export async function issueAwsCredentials(input) {
|
|
78
91
|
const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, forceRefresh, } = input;
|
|
79
|
-
const sessionTtl =
|
|
92
|
+
const sessionTtl = resolveSessionTtl(profile);
|
|
80
93
|
const normalizedCert = normalizeCert(certBase64);
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
84
|
-
certSerialDec = cachedCertSerialDec;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
certSerialDec = parseCertSerialDec(normalizedCert); // throws InternalError on bad DER
|
|
88
|
-
cachedCertSerialDec = certSerialDec;
|
|
89
|
-
cachedCertSerialSource = normalizedCert;
|
|
90
|
-
}
|
|
94
|
+
// Cert serial — use isolate cache to avoid redundant DER walks.
|
|
95
|
+
const certSerialDec = getCertSerialDec(normalizedCert);
|
|
91
96
|
const cacheKey = `${region}|${roleArn}|${profileArn}|${trustAnchorArn}|${certSerialDec}`;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
97
|
+
return getCachedOrFetch(cacheKey, () => fetchCredentials({
|
|
98
|
+
roleArn, profileArn, trustAnchorArn,
|
|
99
|
+
region, normalizedCert, privateKeyPkcs8Base64,
|
|
100
|
+
sessionTtl,
|
|
101
|
+
}), { ttlSec: 60, forceRefresh });
|
|
102
|
+
}
|
|
103
|
+
async function fetchCredentials(input) {
|
|
104
|
+
const { roleArn, profileArn, trustAnchorArn, region, normalizedCert, privateKeyPkcs8Base64, sessionTtl, } = input;
|
|
105
|
+
const signingKey = await getSigningMaterial(normalizedCert, privateKeyPkcs8Base64);
|
|
106
|
+
const host = `${SERVICE}.${region}.amazonaws.com`;
|
|
107
|
+
const iso = new Date().toISOString();
|
|
108
|
+
const amzDate = isoToAmzDate(iso);
|
|
109
|
+
const dateStamp = iso.slice(0, 4) + iso.slice(5, 7) + iso.slice(8, 10);
|
|
110
|
+
const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
|
|
111
|
+
const payloadHash = await sha256Hex(body);
|
|
112
|
+
const baseHeaders = {
|
|
113
|
+
"content-type": "application/json",
|
|
114
|
+
"host": host,
|
|
115
|
+
"x-amz-date": amzDate,
|
|
116
|
+
"x-amz-x509": normalizedCert,
|
|
117
|
+
};
|
|
118
|
+
const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
|
|
119
|
+
const credentialScope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
|
|
120
|
+
const canonicalRequest = buildCanonicalRequest({
|
|
121
|
+
method: "POST",
|
|
122
|
+
canonicalUri: PATH,
|
|
123
|
+
query: "",
|
|
124
|
+
canonicalHeaders,
|
|
125
|
+
signedHeaders,
|
|
126
|
+
payloadHash,
|
|
127
|
+
});
|
|
128
|
+
const canonicalRequestHash = await sha256Hex(canonicalRequest);
|
|
129
|
+
const stringToSign = buildStringToSign({
|
|
130
|
+
algorithm: ALGORITHM,
|
|
131
|
+
amzDate,
|
|
132
|
+
credentialScope,
|
|
133
|
+
canonicalRequestHash,
|
|
134
|
+
});
|
|
135
|
+
const signatureHex = await signStringToSign(stringToSign, signingKey);
|
|
136
|
+
const certSerialDec = getCertSerialDec(normalizedCert);
|
|
137
|
+
const finalHeaders = new Headers({
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
"X-Amz-Date": amzDate,
|
|
140
|
+
"X-Amz-X509": normalizedCert,
|
|
141
|
+
"Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
|
|
142
|
+
});
|
|
143
|
+
let res;
|
|
144
|
+
try {
|
|
145
|
+
res = await fetch(`https://${host}${PATH}`, {
|
|
137
146
|
method: "POST",
|
|
138
147
|
headers: finalHeaders,
|
|
139
148
|
body,
|
|
140
149
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.debug("[issueAwsCredentials] fallback return (no TTL)", {
|
|
174
|
-
accessKeyId: value.accessKeyId
|
|
175
|
-
});
|
|
176
|
-
return value;
|
|
177
|
-
}, {
|
|
178
|
-
ttlSec: 60,
|
|
179
|
-
...(forceRefresh !== undefined ? { forceRefresh } : {})
|
|
180
|
-
});
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.warn("[aws-unreachable]", { region, err });
|
|
153
|
+
throw new InternalError("aws_unreachable");
|
|
154
|
+
}
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
console.warn("[aws-rejected]", { status: res.status, region });
|
|
157
|
+
throw new InternalError("aws_rejected");
|
|
158
|
+
}
|
|
159
|
+
const json = await res.json();
|
|
160
|
+
const creds = json?.credentialSet?.[0]?.credentials;
|
|
161
|
+
if (!creds?.accessKeyId || !creds?.secretAccessKey || !creds?.sessionToken) {
|
|
162
|
+
console.warn("[issueAwsCredentials] malformed AWS credential response");
|
|
163
|
+
throw new InternalError("aws_malformed_credentials");
|
|
164
|
+
}
|
|
165
|
+
const value = {
|
|
166
|
+
accessKeyId: creds.accessKeyId,
|
|
167
|
+
secretAccessKey: creds.secretAccessKey,
|
|
168
|
+
sessionToken: creds.sessionToken,
|
|
169
|
+
expiration: creds.expiration,
|
|
170
|
+
};
|
|
171
|
+
// Cache for 1/3 of the credential lifetime so it's refreshed well before expiry.
|
|
172
|
+
// deriveEdgeTtlSec in cache.ts will further subtract EDGE_BUFFER_SEC (5 min).
|
|
173
|
+
const expiresAtMs = Date.parse(creds.expiration);
|
|
174
|
+
const receivedAt = Date.now();
|
|
175
|
+
const credLifetimeSec = Math.floor((expiresAtMs - receivedAt) / 1000);
|
|
176
|
+
if (Number.isFinite(expiresAtMs) && credLifetimeSec > 0) {
|
|
177
|
+
const cacheTtlSec = Math.floor(credLifetimeSec / 3);
|
|
178
|
+
const cacheExpiry = new Date(receivedAt + cacheTtlSec * 1000).toISOString();
|
|
179
|
+
return wrapResult(value, cacheExpiry);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
181
182
|
}
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
// ---------------------------------------------------------------------------
|
|
185
|
-
/** Strip whitespace and reject PEM-wrapped input. */
|
|
183
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
184
|
+
/** Strip whitespace; reject PEM-wrapped input. */
|
|
186
185
|
function normalizeCert(raw) {
|
|
187
186
|
if (raw.includes("BEGIN CERTIFICATE")) {
|
|
188
187
|
throw new InternalError("pem_not_allowed");
|
|
189
188
|
}
|
|
190
189
|
return raw.replace(/\s+/g, "");
|
|
191
190
|
}
|
|
191
|
+
/** Return cert serial as decimal string, using isolate-level cache. */
|
|
192
|
+
function getCertSerialDec(normalizedCert) {
|
|
193
|
+
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
194
|
+
return cachedCertSerialDec;
|
|
195
|
+
}
|
|
196
|
+
const serial = parseCertSerialDec(normalizedCert);
|
|
197
|
+
cachedCertSerialDec = serial;
|
|
198
|
+
cachedCertSerialSource = normalizedCert;
|
|
199
|
+
return serial;
|
|
200
|
+
}
|
|
192
201
|
/**
|
|
193
202
|
* Minimal DER walk to extract the certificate serial number as a decimal string.
|
|
194
203
|
* Throws InternalError("invalid_cert_der") on any parse failure.
|
|
@@ -220,8 +229,7 @@ function parseCertSerialDec(normalizedCertBase64) {
|
|
|
220
229
|
// Skip optional [0] EXPLICIT version field.
|
|
221
230
|
if (der[offset] === 0xa0) {
|
|
222
231
|
offset++;
|
|
223
|
-
|
|
224
|
-
offset += vLen;
|
|
232
|
+
offset += readLen();
|
|
225
233
|
}
|
|
226
234
|
if (der[offset++] !== 0x02)
|
|
227
235
|
throw new Error("bad serial tag");
|
|
@@ -233,7 +241,6 @@ function parseCertSerialDec(normalizedCertBase64) {
|
|
|
233
241
|
if (serial.length > 1 && serial[0] === 0x00) {
|
|
234
242
|
serial = serial.slice(1);
|
|
235
243
|
}
|
|
236
|
-
// Accumulate directly to BigInt — avoids intermediate hex string allocation.
|
|
237
244
|
let serialBig = 0n;
|
|
238
245
|
for (let i = 0; i < serial.length; i++) {
|
|
239
246
|
serialBig = (serialBig << 8n) | BigInt(serial[i]);
|
|
@@ -246,7 +253,7 @@ function parseCertSerialDec(normalizedCertBase64) {
|
|
|
246
253
|
}
|
|
247
254
|
}
|
|
248
255
|
/**
|
|
249
|
-
* Convert
|
|
256
|
+
* Convert ISO-8601 to compact AMZ date-time format.
|
|
250
257
|
* e.g. "2026-03-07T12:00:00.000Z" → "20260307T120000Z"
|
|
251
258
|
*/
|
|
252
259
|
function isoToAmzDate(iso) {
|
|
@@ -259,7 +266,7 @@ function isoToAmzDate(iso) {
|
|
|
259
266
|
iso.slice(17, 19) +
|
|
260
267
|
"Z");
|
|
261
268
|
}
|
|
262
|
-
/**
|
|
269
|
+
/** base64 → Uint8Array via V8's vectorized atob path. */
|
|
263
270
|
function base64ToBytes(base64) {
|
|
264
271
|
const binary = atob(base64);
|
|
265
272
|
const bytes = new Uint8Array(binary.length);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizamodo/aws-sts-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.48",
|
|
4
4
|
"description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"vitest": "^4.1.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@vizamodo/edge-cache-core": "^0.3.
|
|
31
|
+
"@vizamodo/edge-cache-core": "^0.3.40"
|
|
32
32
|
}
|
|
33
33
|
}
|