@vizamodo/aws-sts-core 0.4.3 → 0.4.6
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.d.ts +0 -8
- package/dist/sts/issue.js +126 -235
- package/package.json +1 -1
package/dist/sts/issue.d.ts
CHANGED
|
@@ -9,12 +9,4 @@ export interface IssueAwsCredentialsInput {
|
|
|
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
12
|
export declare function issueAwsCredentials(input: IssueAwsCredentialsInput): Promise<AwsCredentialResult>;
|
package/dist/sts/issue.js
CHANGED
|
@@ -9,10 +9,6 @@ import { getCachedOrFetch, wrapResult } from "@vizamodo/edge-cache-core";
|
|
|
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
|
-
*/
|
|
16
12
|
const PROFILE_TTL = {
|
|
17
13
|
Runtime: 45 * 60,
|
|
18
14
|
ConsoleReadOnly: 12 * 60 * 60,
|
|
@@ -22,94 +18,59 @@ const PROFILE_TTL = {
|
|
|
22
18
|
BillingAccountant: 3 * 60 * 60,
|
|
23
19
|
};
|
|
24
20
|
const DEFAULT_SESSION_TTL = 2 * 60 * 60;
|
|
25
|
-
const MIN_SESSION_TTL = 45 * 60;
|
|
26
|
-
const MAX_SESSION_TTL = 12 * 60 * 60;
|
|
27
|
-
// ── Isolate-level
|
|
28
|
-
//
|
|
21
|
+
const MIN_SESSION_TTL = 45 * 60;
|
|
22
|
+
const MAX_SESSION_TTL = 12 * 60 * 60;
|
|
23
|
+
// ── Isolate-level Caches (L1) ─────────────────────────────────────────────
|
|
24
|
+
// Cache vật liệu ký để tránh re-import CryptoKey liên tục
|
|
29
25
|
let signingKeyPromise = null;
|
|
30
26
|
let cachedSigningKey = null;
|
|
31
27
|
let cachedCertBase64 = null;
|
|
32
28
|
let cachedPrivateKeyBase64 = null;
|
|
33
|
-
//
|
|
34
|
-
// DER walk is CPU-bound; the cert rarely rotates within an isolate lifetime.
|
|
29
|
+
// Cache Serial Number để tránh redundant DER walks (CPU-bound)
|
|
35
30
|
let cachedCertSerialDec = null;
|
|
36
31
|
let cachedCertSerialSource = null;
|
|
37
|
-
// ──
|
|
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.
|
|
56
|
-
if (cachedSigningKey &&
|
|
57
|
-
cachedCertBase64 === certBase64 &&
|
|
58
|
-
cachedPrivateKeyBase64 === privateKeyPkcs8Base64) {
|
|
59
|
-
return cachedSigningKey;
|
|
60
|
-
}
|
|
61
|
-
// Material changed or first call — reset and re-import.
|
|
62
|
-
// Cache vars are updated inside .then() so concurrent callers
|
|
63
|
-
// awaiting the same promise all see consistent state after resolve.
|
|
64
|
-
signingKeyPromise = null;
|
|
65
|
-
cachedSigningKey = null;
|
|
66
|
-
cachedCertBase64 = null;
|
|
67
|
-
cachedPrivateKeyBase64 = null;
|
|
68
|
-
signingKeyPromise = crypto.subtle
|
|
69
|
-
.importKey("pkcs8", base64ToBytes(privateKeyPkcs8Base64), { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"])
|
|
70
|
-
.then((key) => {
|
|
71
|
-
// Update cache atomically after successful import.
|
|
72
|
-
// All concurrent callers awaiting this promise will see
|
|
73
|
-
// consistent state and hit the fast path on next call.
|
|
74
|
-
cachedSigningKey = key;
|
|
75
|
-
cachedCertBase64 = certBase64;
|
|
76
|
-
cachedPrivateKeyBase64 = privateKeyPkcs8Base64;
|
|
77
|
-
return key;
|
|
78
|
-
})
|
|
79
|
-
.catch(() => {
|
|
80
|
-
signingKeyPromise = null; // allow retry on next call
|
|
81
|
-
throw new InternalError("invalid_signing_material");
|
|
82
|
-
});
|
|
83
|
-
return signingKeyPromise;
|
|
84
|
-
}
|
|
85
|
-
// ── Session TTL ────────────────────────────────────────────────────────────
|
|
86
|
-
function resolveSessionTtl(profile) {
|
|
87
|
-
const ttl = PROFILE_TTL[profile] ?? DEFAULT_SESSION_TTL;
|
|
88
|
-
return Math.min(Math.max(ttl, MIN_SESSION_TTL), MAX_SESSION_TTL);
|
|
89
|
-
}
|
|
90
|
-
// ── Main export ────────────────────────────────────────────────────────────
|
|
32
|
+
// ── Main Export ────────────────────────────────────────────────────────────
|
|
91
33
|
export async function issueAwsCredentials(input) {
|
|
92
34
|
const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, forceRefresh, } = input;
|
|
93
35
|
const sessionTtl = resolveSessionTtl(profile);
|
|
94
36
|
const normalizedCert = normalizeCert(certBase64);
|
|
95
|
-
// Cert serial — use isolate cache to avoid redundant DER walks.
|
|
96
37
|
const certSerialDec = getCertSerialDec(normalizedCert);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
38
|
+
// Cache Key duy nhất dựa trên định danh của Role và Certificate
|
|
39
|
+
const cacheKey = `aws|${region}|${roleArn}|${certSerialDec}`;
|
|
40
|
+
return getCachedOrFetch(cacheKey, async () => {
|
|
41
|
+
return fetchAwsCredentials({
|
|
42
|
+
roleArn, profileArn, trustAnchorArn,
|
|
43
|
+
region, normalizedCert, privateKeyPkcs8Base64,
|
|
44
|
+
sessionTtl,
|
|
45
|
+
certSerialDec
|
|
46
|
+
});
|
|
47
|
+
}, {
|
|
48
|
+
// Cache L2 (Edge) sẽ được refresh khi còn 1/3 thời gian hiệu lực
|
|
49
|
+
// hoặc cưỡng bức refresh qua forceRefresh
|
|
50
|
+
ttlSec: 60,
|
|
51
|
+
forceRefresh
|
|
52
|
+
});
|
|
103
53
|
}
|
|
104
|
-
|
|
105
|
-
|
|
54
|
+
// ── Core Logic ─────────────────────────────────────────────────────────────
|
|
55
|
+
async function fetchAwsCredentials(params) {
|
|
56
|
+
const { roleArn, profileArn, trustAnchorArn, region, normalizedCert, privateKeyPkcs8Base64, sessionTtl, certSerialDec } = params;
|
|
57
|
+
// 1. Chuẩn bị Signing Material (Isolate-cached)
|
|
106
58
|
const signingKey = await getSigningMaterial(normalizedCert, privateKeyPkcs8Base64);
|
|
59
|
+
// 2. AWS SigV4 Metadata
|
|
107
60
|
const host = `${SERVICE}.${region}.amazonaws.com`;
|
|
108
|
-
const
|
|
109
|
-
const amzDate =
|
|
110
|
-
const dateStamp =
|
|
111
|
-
const
|
|
61
|
+
const now = new Date();
|
|
62
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, ""); // e.g. 20260318T220520Z
|
|
63
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
64
|
+
const credentialScope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
|
|
65
|
+
// 3. Request Body & Payload Hash
|
|
66
|
+
const body = JSON.stringify({
|
|
67
|
+
trustAnchorArn,
|
|
68
|
+
profileArn,
|
|
69
|
+
roleArn,
|
|
70
|
+
durationSeconds: sessionTtl
|
|
71
|
+
});
|
|
112
72
|
const payloadHash = await sha256Hex(body);
|
|
73
|
+
// 4. Canonical Request & StringToSign
|
|
113
74
|
const baseHeaders = {
|
|
114
75
|
"content-type": "application/json",
|
|
115
76
|
"host": host,
|
|
@@ -117,7 +78,6 @@ async function fetchCredentials(input) {
|
|
|
117
78
|
"x-amz-x509": normalizedCert,
|
|
118
79
|
};
|
|
119
80
|
const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
|
|
120
|
-
const credentialScope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
|
|
121
81
|
const canonicalRequest = buildCanonicalRequest({
|
|
122
82
|
method: "POST",
|
|
123
83
|
canonicalUri: PATH,
|
|
@@ -126,197 +86,128 @@ async function fetchCredentials(input) {
|
|
|
126
86
|
signedHeaders,
|
|
127
87
|
payloadHash,
|
|
128
88
|
});
|
|
129
|
-
const canonicalRequestHash = await sha256Hex(canonicalRequest);
|
|
130
89
|
const stringToSign = buildStringToSign({
|
|
131
90
|
algorithm: ALGORITHM,
|
|
132
91
|
amzDate,
|
|
133
92
|
credentialScope,
|
|
134
|
-
canonicalRequestHash,
|
|
93
|
+
canonicalRequestHash: await sha256Hex(canonicalRequest),
|
|
135
94
|
});
|
|
95
|
+
// 5. Signature
|
|
136
96
|
const signatureHex = await signStringToSign(stringToSign, signingKey);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
console.debug("[aws-debug][cert]", {
|
|
149
|
-
length: normalizedCert.length,
|
|
150
|
-
preview: normalizedCert.slice(0, 30),
|
|
151
|
-
});
|
|
152
|
-
console.debug("[aws-debug][serial]", {
|
|
153
|
-
serialDec: certSerialDec,
|
|
154
|
-
serialHex: BigInt(certSerialDec).toString(16),
|
|
155
|
-
});
|
|
156
|
-
console.debug("[aws-debug][headers]", baseHeaders);
|
|
157
|
-
console.debug("[aws-debug][signedHeaders]", signedHeaders);
|
|
158
|
-
console.debug("[aws-debug][canonicalRequest]", canonicalRequest);
|
|
159
|
-
console.debug("[aws-debug][stringToSign]", stringToSign);
|
|
160
|
-
console.debug("[aws-debug][signatureHex]", signatureHex);
|
|
161
|
-
}
|
|
162
|
-
catch (e) {
|
|
163
|
-
console.warn("[aws-debug][error]", e);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
debugAwsSigning();
|
|
167
|
-
const finalHeaders = new Headers({
|
|
168
|
-
"Content-Type": "application/json",
|
|
169
|
-
"X-Amz-Date": amzDate,
|
|
170
|
-
"X-Amz-X509": normalizedCert,
|
|
171
|
-
"Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
|
|
97
|
+
// 6. Execution
|
|
98
|
+
const res = await fetch(`https://${host}${PATH}`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
"X-Amz-Date": amzDate,
|
|
103
|
+
"X-Amz-X509": normalizedCert,
|
|
104
|
+
"Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
|
|
105
|
+
},
|
|
106
|
+
body,
|
|
172
107
|
});
|
|
173
|
-
let res;
|
|
174
|
-
try {
|
|
175
|
-
res = await fetch(`https://${host}${PATH}`, {
|
|
176
|
-
method: "POST",
|
|
177
|
-
headers: finalHeaders,
|
|
178
|
-
body,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
console.warn("[aws-unreachable]", { region, err });
|
|
183
|
-
throw new InternalError("aws_unreachable");
|
|
184
|
-
}
|
|
185
108
|
if (!res.ok) {
|
|
186
|
-
const
|
|
187
|
-
console.error("[aws-rejected]", {
|
|
188
|
-
status: res.status,
|
|
189
|
-
body: text,
|
|
190
|
-
region,
|
|
191
|
-
});
|
|
109
|
+
const errorText = await res.text().catch(() => "unknown");
|
|
110
|
+
console.error("[aws-rejected]", { status: res.status, body: errorText });
|
|
192
111
|
throw new InternalError("aws_rejected");
|
|
193
112
|
}
|
|
194
|
-
const
|
|
195
|
-
const creds =
|
|
196
|
-
if (!creds?.accessKeyId
|
|
197
|
-
console.warn("[issueAwsCredentials] malformed AWS credential response");
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
const creds = data?.credentialSet?.[0]?.credentials;
|
|
115
|
+
if (!creds?.accessKeyId) {
|
|
198
116
|
throw new InternalError("aws_malformed_credentials");
|
|
199
117
|
}
|
|
200
|
-
const
|
|
118
|
+
const result = {
|
|
201
119
|
accessKeyId: creds.accessKeyId,
|
|
202
120
|
secretAccessKey: creds.secretAccessKey,
|
|
203
121
|
sessionToken: creds.sessionToken,
|
|
204
122
|
expiration: creds.expiration,
|
|
205
123
|
};
|
|
206
|
-
// Cache
|
|
207
|
-
//
|
|
124
|
+
// 7. Cache Control (L2 Edge Cache)
|
|
125
|
+
// Chiến thuật: Cache trong 1/3 thời gian hiệu lực của AWS Credential
|
|
208
126
|
const expiresAtMs = Date.parse(creds.expiration);
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
if (Number.isFinite(expiresAtMs) && credLifetimeSec > 0) {
|
|
127
|
+
const credLifetimeSec = Math.floor((expiresAtMs - Date.now()) / 1000);
|
|
128
|
+
if (credLifetimeSec > 300) { // Chỉ cache nếu còn hiệu lực trên 5 phút
|
|
212
129
|
const cacheTtlSec = Math.floor(credLifetimeSec / 3);
|
|
213
|
-
const cacheExpiry = new Date(
|
|
214
|
-
return wrapResult(
|
|
130
|
+
const cacheExpiry = new Date(Date.now() + cacheTtlSec * 1000).toISOString();
|
|
131
|
+
return wrapResult(result, cacheExpiry);
|
|
215
132
|
}
|
|
216
|
-
return
|
|
133
|
+
return result;
|
|
217
134
|
}
|
|
218
135
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
219
|
-
|
|
136
|
+
function resolveSessionTtl(profile) {
|
|
137
|
+
const ttl = PROFILE_TTL[profile] ?? DEFAULT_SESSION_TTL;
|
|
138
|
+
return Math.min(Math.max(ttl, MIN_SESSION_TTL), MAX_SESSION_TTL);
|
|
139
|
+
}
|
|
220
140
|
function normalizeCert(raw) {
|
|
221
|
-
if (raw.includes("BEGIN CERTIFICATE"))
|
|
141
|
+
if (raw.includes("BEGIN CERTIFICATE"))
|
|
222
142
|
throw new InternalError("pem_not_allowed");
|
|
223
|
-
}
|
|
224
143
|
return raw.replace(/\s+/g, "");
|
|
225
144
|
}
|
|
226
|
-
/** Return cert serial as decimal string, using isolate-level cache. */
|
|
227
145
|
function getCertSerialDec(normalizedCert) {
|
|
228
146
|
if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
|
|
229
147
|
return cachedCertSerialDec;
|
|
230
148
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
149
|
+
const der = base64ToBytes(normalizedCert);
|
|
150
|
+
let offset = 0;
|
|
151
|
+
const readLen = () => {
|
|
152
|
+
const b = der[offset++];
|
|
153
|
+
if ((b & 0x80) === 0)
|
|
154
|
+
return b;
|
|
155
|
+
let n = b & 0x7f;
|
|
156
|
+
let len = 0;
|
|
157
|
+
while (n--)
|
|
158
|
+
len = (len << 8) | der[offset++];
|
|
159
|
+
return len;
|
|
160
|
+
};
|
|
161
|
+
// Strict DER Walk (Sửa lỗi skip sai INTEGER của bản cũ)
|
|
162
|
+
if (der[offset++] !== 0x30)
|
|
163
|
+
throw new Error("Not a SEQUENCE"); // Cert
|
|
164
|
+
readLen();
|
|
165
|
+
if (der[offset++] !== 0x30)
|
|
166
|
+
throw new Error("Not a SEQUENCE"); // TBS
|
|
167
|
+
readLen();
|
|
168
|
+
if (der[offset] === 0xa0) { // Version tag
|
|
169
|
+
offset++;
|
|
170
|
+
offset += readLen();
|
|
171
|
+
}
|
|
172
|
+
if (der[offset++] !== 0x02)
|
|
173
|
+
throw new Error("Serial not an INTEGER");
|
|
174
|
+
const sLen = readLen();
|
|
175
|
+
let serial = der.slice(offset, offset + sLen);
|
|
176
|
+
// Strip ASN.1 padding byte 0x00
|
|
177
|
+
if (serial.length > 1 && serial[0] === 0x00)
|
|
178
|
+
serial = serial.slice(1);
|
|
179
|
+
let big = 0n;
|
|
180
|
+
for (const b of serial)
|
|
181
|
+
big = (big << 8n) | BigInt(b);
|
|
182
|
+
cachedCertSerialDec = big.toString();
|
|
233
183
|
cachedCertSerialSource = normalizedCert;
|
|
234
|
-
return
|
|
184
|
+
return cachedCertSerialDec;
|
|
235
185
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
*/
|
|
240
|
-
function parseCertSerialDec(normalizedCertBase64) {
|
|
241
|
-
try {
|
|
242
|
-
const der = base64ToBytes(normalizedCertBase64);
|
|
243
|
-
let offset = 0;
|
|
244
|
-
function readLen() {
|
|
245
|
-
if (offset >= der.length)
|
|
246
|
-
throw new Error("DER overflow");
|
|
247
|
-
const b = der[offset++];
|
|
248
|
-
if ((b & 0x80) === 0)
|
|
249
|
-
return b;
|
|
250
|
-
const n = b & 0x7f;
|
|
251
|
-
let len = 0;
|
|
252
|
-
if (offset + n > der.length)
|
|
253
|
-
throw new Error("DER overflow");
|
|
254
|
-
for (let i = 0; i < n; i++)
|
|
255
|
-
len = (len << 8) | der[offset++];
|
|
256
|
-
return len;
|
|
257
|
-
}
|
|
258
|
-
// Certificate ::= SEQUENCE
|
|
259
|
-
if (der[offset++] !== 0x30)
|
|
260
|
-
throw new Error("bad cert");
|
|
261
|
-
readLen();
|
|
262
|
-
// tbsCertificate ::= SEQUENCE
|
|
263
|
-
if (der[offset++] !== 0x30)
|
|
264
|
-
throw new Error("bad tbs");
|
|
265
|
-
readLen();
|
|
266
|
-
// Optional version [0] EXPLICIT
|
|
267
|
-
if (der[offset] === 0xa0) {
|
|
268
|
-
offset++; // tag
|
|
269
|
-
offset += readLen(); // skip content
|
|
270
|
-
}
|
|
271
|
-
// Now scan for first INTEGER with "real" length (>2 bytes)
|
|
272
|
-
while (offset < der.length) {
|
|
273
|
-
if (der[offset++] !== 0x02)
|
|
274
|
-
continue;
|
|
275
|
-
const len = readLen();
|
|
276
|
-
if (len <= 2) {
|
|
277
|
-
offset += len; // skip small integers (likely version)
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (offset + len > der.length)
|
|
281
|
-
throw new Error("DER overflow");
|
|
282
|
-
let serial = der.slice(offset, offset + len);
|
|
283
|
-
// Strip leading 0x00 ONLY if it's padding
|
|
284
|
-
if (serial.length > 1 && serial[0] === 0x00 && (serial[1] & 0x80) === 0) {
|
|
285
|
-
serial = serial.slice(1);
|
|
286
|
-
}
|
|
287
|
-
let serialBig = 0n;
|
|
288
|
-
for (let i = 0; i < serial.length; i++) {
|
|
289
|
-
serialBig = (serialBig << 8n) | BigInt(serial[i]);
|
|
290
|
-
}
|
|
291
|
-
return serialBig.toString();
|
|
292
|
-
}
|
|
293
|
-
throw new Error("serial not found");
|
|
294
|
-
}
|
|
295
|
-
catch (e) {
|
|
296
|
-
console.error("[parseCertSerialDec] failed", e);
|
|
297
|
-
throw new InternalError("invalid_cert_der");
|
|
186
|
+
async function getSigningMaterial(cert, pk) {
|
|
187
|
+
if (cachedSigningKey && cachedCertBase64 === cert && cachedPrivateKeyBase64 === pk) {
|
|
188
|
+
return cachedSigningKey;
|
|
298
189
|
}
|
|
190
|
+
const pkBytes = base64ToBytes(pk);
|
|
191
|
+
signingKeyPromise = crypto.subtle.importKey("pkcs8",
|
|
192
|
+
// LẤY BUFFER VÀ ÉP KIỂU:
|
|
193
|
+
// Uint8Array.buffer có thể là ArrayBuffer | SharedArrayBuffer
|
|
194
|
+
// Web Crypto chỉ chấp nhận ArrayBuffer.
|
|
195
|
+
pkBytes.buffer, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]).then(key => {
|
|
196
|
+
cachedSigningKey = key;
|
|
197
|
+
cachedCertBase64 = cert;
|
|
198
|
+
cachedPrivateKeyBase64 = pk;
|
|
199
|
+
return key;
|
|
200
|
+
}).catch(e => {
|
|
201
|
+
signingKeyPromise = null;
|
|
202
|
+
throw new InternalError("invalid_signing_material");
|
|
203
|
+
});
|
|
204
|
+
return signingKeyPromise;
|
|
299
205
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return (iso.slice(0, 4) +
|
|
306
|
-
iso.slice(5, 7) +
|
|
307
|
-
iso.slice(8, 10) +
|
|
308
|
-
"T" +
|
|
309
|
-
iso.slice(11, 13) +
|
|
310
|
-
iso.slice(14, 16) +
|
|
311
|
-
iso.slice(17, 19) +
|
|
312
|
-
"Z");
|
|
313
|
-
}
|
|
314
|
-
/** base64 → Uint8Array via V8's vectorized atob path. */
|
|
315
|
-
function base64ToBytes(base64) {
|
|
316
|
-
const binary = atob(base64);
|
|
317
|
-
const bytes = new Uint8Array(binary.length);
|
|
318
|
-
for (let i = 0; i < binary.length; i++) {
|
|
319
|
-
bytes[i] = binary.charCodeAt(i);
|
|
206
|
+
function base64ToBytes(b64) {
|
|
207
|
+
const bin = atob(b64);
|
|
208
|
+
const bytes = new Uint8Array(bin.length);
|
|
209
|
+
for (let i = 0; i < bin.length; i++) {
|
|
210
|
+
bytes[i] = bin.charCodeAt(i);
|
|
320
211
|
}
|
|
321
212
|
return bytes;
|
|
322
213
|
}
|