@blamejs/core 0.10.15 → 0.11.1
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/CHANGELOG.md +5 -0
- package/index.js +18 -0
- package/lib/auth/oauth.js +187 -0
- package/lib/auth/saml.js +1366 -13
- package/lib/cms-codec.js +141 -0
- package/lib/compliance.js +73 -0
- package/lib/csp.js +271 -0
- package/lib/dbsc.js +299 -0
- package/lib/fedcm.js +264 -0
- package/lib/hal.js +125 -0
- package/lib/http-client.js +46 -10
- package/lib/importmap-integrity.js +90 -0
- package/lib/jsonapi.js +230 -0
- package/lib/lro.js +200 -0
- package/lib/mail-crypto-pgp.js +312 -2
- package/lib/mail-crypto-smime.js +530 -69
- package/lib/metrics.js +62 -12
- package/lib/middleware/security-headers.js +2 -1
- package/lib/ssrf-guard.js +71 -10
- package/lib/standard-webhooks.js +183 -0
- package/lib/web-push-vapid.js +322 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.webPush
|
|
4
|
+
* @nav Networking
|
|
5
|
+
* @title Web Push (VAPID)
|
|
6
|
+
* @order 240
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* RFC 8292 Voluntary Application Server Identification (VAPID) for
|
|
10
|
+
* Web Push (RFC 8030). Operators sign JWTs with an ECDSA-P256 key
|
|
11
|
+
* to identify themselves to the push service; the browser-side
|
|
12
|
+
* subscription includes the operator's VAPID public key in
|
|
13
|
+
* `applicationServerKey`. RFC 8292 §3 mandates ES256; the framework
|
|
14
|
+
* uses node:crypto for ECDSA because the protocol is not PQC-yet
|
|
15
|
+
* (browser push services don't accept ML-DSA today; track
|
|
16
|
+
* draft-ietf-webpush-vapid-pqc for the migration).
|
|
17
|
+
*
|
|
18
|
+
* `b.webPush.buildVapidAuthHeader({ subscription, contact,
|
|
19
|
+
* privateKeyPem, publicKeyPem })` returns the `Authorization:
|
|
20
|
+
* vapid t=<jwt>, k=<base64url-pubkey>` header value the operator
|
|
21
|
+
* sets on the push-request POST to the push-service endpoint.
|
|
22
|
+
*
|
|
23
|
+
* `b.webPush.generateVapidKeypair()` returns `{ publicKeyPem,
|
|
24
|
+
* privateKeyPem, publicKeyB64Url }` — the b64url-encoded public
|
|
25
|
+
* key is what the browser code passes as `applicationServerKey`.
|
|
26
|
+
*
|
|
27
|
+
* @card
|
|
28
|
+
* RFC 8292 VAPID JWT signer + RFC 8030 push request shape (ECDSA-P256). Operators sign once per subscription endpoint; browsers identify the push origin via the operator's public key.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
var nodeCrypto = require("node:crypto");
|
|
32
|
+
var C = require("./constants");
|
|
33
|
+
var validateOpts = require("./validate-opts");
|
|
34
|
+
var safeUrl = require("./safe-url");
|
|
35
|
+
var bCrypto = require("./crypto");
|
|
36
|
+
var { defineClass } = require("./framework-error");
|
|
37
|
+
|
|
38
|
+
var WebPushError = defineClass("WebPushError", { alwaysPermanent: true });
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @primitive b.webPush.generateVapidKeypair
|
|
42
|
+
* @signature b.webPush.generateVapidKeypair()
|
|
43
|
+
* @since 0.10.16
|
|
44
|
+
* @status stable
|
|
45
|
+
* @related b.webPush.buildVapidAuthHeader
|
|
46
|
+
*
|
|
47
|
+
* Generate a fresh ECDSA-P256 keypair suitable for VAPID. Returns
|
|
48
|
+
* `{ publicKeyPem, privateKeyPem, publicKeyB64Url }`. The b64url-
|
|
49
|
+
* encoded public key is what the browser code passes as
|
|
50
|
+
* `applicationServerKey` to `pushManager.subscribe`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* var kp = b.webPush.generateVapidKeypair();
|
|
54
|
+
* // Browser:
|
|
55
|
+
* // pushManager.subscribe({ applicationServerKey: kp.publicKeyB64Url })
|
|
56
|
+
*/
|
|
57
|
+
function generateVapidKeypair() {
|
|
58
|
+
var kp = nodeCrypto.generateKeyPairSync("ec", {
|
|
59
|
+
namedCurve: "prime256v1",
|
|
60
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
61
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
62
|
+
});
|
|
63
|
+
// RFC 8292 §3.2 — uncompressed point (0x04 ‖ X ‖ Y), base64url-encoded.
|
|
64
|
+
var pubKeyObj = nodeCrypto.createPublicKey(kp.publicKey);
|
|
65
|
+
var jwk = pubKeyObj.export({ format: "jwk" });
|
|
66
|
+
var raw = Buffer.concat([
|
|
67
|
+
Buffer.from([0x04]), // allow:raw-byte-literal — uncompressed point prefix per SEC1 §2.3.3
|
|
68
|
+
Buffer.from(jwk.x, "base64url"),
|
|
69
|
+
Buffer.from(jwk.y, "base64url"),
|
|
70
|
+
]);
|
|
71
|
+
return {
|
|
72
|
+
publicKeyPem: kp.publicKey,
|
|
73
|
+
privateKeyPem: kp.privateKey,
|
|
74
|
+
publicKeyB64Url: bCrypto.toBase64Url(raw),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @primitive b.webPush.buildVapidAuthHeader
|
|
80
|
+
* @signature b.webPush.buildVapidAuthHeader(opts)
|
|
81
|
+
* @since 0.10.16
|
|
82
|
+
* @status stable
|
|
83
|
+
* @related b.webPush.generateVapidKeypair
|
|
84
|
+
*
|
|
85
|
+
* Build the `Authorization: vapid t=<jwt>, k=<pubkey-b64url>` header
|
|
86
|
+
* value per RFC 8292 §3. The JWT claims (`aud` / `exp` / `sub`) are
|
|
87
|
+
* computed from the subscription endpoint origin + operator contact;
|
|
88
|
+
* `exp` defaults to 12 hours (RFC 8292 §2 caps at 24 hours).
|
|
89
|
+
*
|
|
90
|
+
* @opts
|
|
91
|
+
* subscription: { endpoint: string }, // browser-returned subscription
|
|
92
|
+
* contact: string, // mailto:... or https:... per RFC 8292 §2
|
|
93
|
+
* privateKeyPem: string, // ECDSA-P256 PEM-encoded private key
|
|
94
|
+
* publicKeyB64Url: string, // public key from generateVapidKeypair()
|
|
95
|
+
* ttlSec: number, // optional, default 12h
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* var hdr = b.webPush.buildVapidAuthHeader({
|
|
99
|
+
* subscription: { endpoint: "https://fcm.googleapis.com/wp/abc" },
|
|
100
|
+
* contact: "mailto:ops@example.com",
|
|
101
|
+
* privateKeyPem: kp.privateKeyPem,
|
|
102
|
+
* publicKeyB64Url: kp.publicKeyB64Url,
|
|
103
|
+
* });
|
|
104
|
+
* // → "vapid t=<jwt>, k=<b64url>"
|
|
105
|
+
*/
|
|
106
|
+
function buildVapidAuthHeader(opts) {
|
|
107
|
+
opts = validateOpts.requireObject(opts, "webPush.buildVapidAuthHeader",
|
|
108
|
+
WebPushError, "web-push/bad-opts");
|
|
109
|
+
validateOpts(opts, ["subscription", "contact", "privateKeyPem",
|
|
110
|
+
"publicKeyB64Url", "ttlSec"],
|
|
111
|
+
"webPush.buildVapidAuthHeader");
|
|
112
|
+
if (!opts.subscription || typeof opts.subscription.endpoint !== "string") {
|
|
113
|
+
throw new WebPushError("web-push/bad-subscription",
|
|
114
|
+
"buildVapidAuthHeader: opts.subscription must include a string endpoint");
|
|
115
|
+
}
|
|
116
|
+
validateOpts.requireNonEmptyString(opts.contact, "contact",
|
|
117
|
+
WebPushError, "web-push/bad-contact");
|
|
118
|
+
if (!/^(mailto:|https:)/i.test(opts.contact)) {
|
|
119
|
+
throw new WebPushError("web-push/bad-contact",
|
|
120
|
+
"buildVapidAuthHeader: contact must start with 'mailto:' or 'https:' per RFC 8292 §2");
|
|
121
|
+
}
|
|
122
|
+
validateOpts.requireNonEmptyString(opts.privateKeyPem, "privateKeyPem",
|
|
123
|
+
WebPushError, "web-push/bad-key");
|
|
124
|
+
validateOpts.requireNonEmptyString(opts.publicKeyB64Url, "publicKeyB64Url",
|
|
125
|
+
WebPushError, "web-push/bad-key");
|
|
126
|
+
validateOpts.optionalPositiveFinite(opts.ttlSec, "webPush.buildVapidAuthHeader: ttlSec",
|
|
127
|
+
WebPushError, "web-push/bad-ttl");
|
|
128
|
+
var ttlSec = opts.ttlSec || C.TIME.hours(12);
|
|
129
|
+
// Audience: origin of the subscription endpoint per RFC 8292 §2.
|
|
130
|
+
var endpointUrl;
|
|
131
|
+
try { endpointUrl = safeUrl.parse(opts.subscription.endpoint); }
|
|
132
|
+
catch (_e) {
|
|
133
|
+
throw new WebPushError("web-push/bad-endpoint",
|
|
134
|
+
"buildVapidAuthHeader: subscription.endpoint is not a parseable URL");
|
|
135
|
+
}
|
|
136
|
+
var aud = endpointUrl.origin;
|
|
137
|
+
var now = Math.floor(Date.now() / 1000); // allow:raw-time-literal — wall-clock seconds for JWT exp
|
|
138
|
+
// Inline JWT sign with ES256 — VAPID strictly mandates ECDSA-P256
|
|
139
|
+
// (RFC 8292 §3.1). The framework jwt.sign is PQC-first and refuses
|
|
140
|
+
// ES256 by design; VAPID is a wire-protocol constraint outside
|
|
141
|
+
// that policy. b.webPush owns the ES256 signing inline so the
|
|
142
|
+
// framework's broader PQC posture remains intact.
|
|
143
|
+
var header = { typ: "JWT", alg: "ES256" };
|
|
144
|
+
var payload = { aud: aud, exp: now + ttlSec, sub: opts.contact };
|
|
145
|
+
var headerB64 = bCrypto.toBase64Url(Buffer.from(JSON.stringify(header), "utf8"));
|
|
146
|
+
var payloadB64 = bCrypto.toBase64Url(Buffer.from(JSON.stringify(payload), "utf8"));
|
|
147
|
+
var signingInput = headerB64 + "." + payloadB64;
|
|
148
|
+
var keyObj = nodeCrypto.createPrivateKey(opts.privateKeyPem);
|
|
149
|
+
if (keyObj.asymmetricKeyType !== "ec") {
|
|
150
|
+
throw new WebPushError("web-push/bad-key",
|
|
151
|
+
"buildVapidAuthHeader: privateKeyPem must be an ECDSA-P256 key (RFC 8292 §3.1)");
|
|
152
|
+
}
|
|
153
|
+
// node:crypto produces DER-encoded ECDSA signature; JWT ES256
|
|
154
|
+
// requires the raw 64-byte r||s shape. Convert.
|
|
155
|
+
var derSig = nodeCrypto.sign("sha256", Buffer.from(signingInput, "utf8"), keyObj);
|
|
156
|
+
var rawSig = _ecdsaDerToRaw(derSig, 32); // allow:raw-byte-literal — 32-byte P-256 component
|
|
157
|
+
var token = signingInput + "." + bCrypto.toBase64Url(rawSig);
|
|
158
|
+
return "vapid t=" + token + ", k=" + opts.publicKeyB64Url;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _ecdsaDerToRaw(der, componentLen) {
|
|
162
|
+
// ECDSA-Sig-Value DER = SEQUENCE { r INTEGER, s INTEGER }.
|
|
163
|
+
if (der[0] !== 0x30) { // allow:raw-byte-literal — ASN.1 SEQUENCE tag
|
|
164
|
+
throw new WebPushError("web-push/bad-sig",
|
|
165
|
+
"ECDSA signature is not a DER SEQUENCE");
|
|
166
|
+
}
|
|
167
|
+
var off = 2;
|
|
168
|
+
if (der[1] & 0x80) off = 2 + (der[1] & 0x7f); // allow:raw-byte-literal — long-form length byte
|
|
169
|
+
if (der[off] !== 0x02) throw new WebPushError("web-push/bad-sig", "missing r INTEGER"); // allow:raw-byte-literal — ASN.1 INTEGER tag
|
|
170
|
+
var rLen = der[off + 1];
|
|
171
|
+
var rStart = off + 2;
|
|
172
|
+
var r = der.slice(rStart, rStart + rLen);
|
|
173
|
+
off = rStart + rLen;
|
|
174
|
+
if (der[off] !== 0x02) throw new WebPushError("web-push/bad-sig", "missing s INTEGER"); // allow:raw-byte-literal — ASN.1 INTEGER tag
|
|
175
|
+
var sLen = der[off + 1];
|
|
176
|
+
var sStart = off + 2;
|
|
177
|
+
var s = der.slice(sStart, sStart + sLen);
|
|
178
|
+
// Trim leading zero pad (DER requires it when high bit set; JWT raw doesn't).
|
|
179
|
+
if (r.length > componentLen && r[0] === 0x00) r = r.slice(1); // allow:raw-byte-literal — DER sign-bit pad
|
|
180
|
+
if (s.length > componentLen && s[0] === 0x00) s = s.slice(1); // allow:raw-byte-literal — DER sign-bit pad
|
|
181
|
+
var out = Buffer.alloc(componentLen * 2);
|
|
182
|
+
r.copy(out, componentLen - r.length);
|
|
183
|
+
s.copy(out, componentLen * 2 - s.length);
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @primitive b.webPush.encrypt
|
|
189
|
+
* @signature b.webPush.encrypt(opts)
|
|
190
|
+
* @since 0.10.16
|
|
191
|
+
* @status stable
|
|
192
|
+
* @related b.webPush.buildVapidAuthHeader
|
|
193
|
+
*
|
|
194
|
+
* Encrypt a Web Push message payload per RFC 8291 (Message Encryption
|
|
195
|
+
* for Web Push) using the aes128gcm content-coding per RFC 8188.
|
|
196
|
+
* Returns `{ body, headers }`:
|
|
197
|
+
* - `body` is the Buffer to POST to the subscription endpoint
|
|
198
|
+
* - `headers` carries the spec-required Content-Encoding +
|
|
199
|
+
* Content-Length + TTL (caller-overridable) so operators wire
|
|
200
|
+
* them onto the push-request alongside the VAPID Authorization.
|
|
201
|
+
*
|
|
202
|
+
* The recipient's subscription object provides `p256dh` (their ECDH
|
|
203
|
+
* P-256 public key, base64url) and `auth` (16-byte auth secret,
|
|
204
|
+
* base64url). The framework computes the ephemeral keypair, performs
|
|
205
|
+
* ECDH, runs the two-stage HKDF per RFC 8291 §3.4, and AES-128-GCM
|
|
206
|
+
* encrypts with the padded plaintext per RFC 8188 §2.
|
|
207
|
+
*
|
|
208
|
+
* @opts
|
|
209
|
+
* subscription: { endpoint, keys: { p256dh, auth } },
|
|
210
|
+
* payload: Buffer|string,
|
|
211
|
+
* ttlSec: number, // default 28d (RFC 8030 §5.2)
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* var e = b.webPush.encrypt({
|
|
215
|
+
* subscription: { endpoint: sub.endpoint, keys: { p256dh, auth } },
|
|
216
|
+
* payload: "hello",
|
|
217
|
+
* });
|
|
218
|
+
* b.httpClient.request({
|
|
219
|
+
* url: sub.endpoint, method: "POST",
|
|
220
|
+
* headers: Object.assign({}, e.headers, {
|
|
221
|
+
* Authorization: vapidHeader,
|
|
222
|
+
* }),
|
|
223
|
+
* body: e.body,
|
|
224
|
+
* });
|
|
225
|
+
*/
|
|
226
|
+
function encrypt(opts) {
|
|
227
|
+
opts = validateOpts.requireObject(opts, "webPush.encrypt",
|
|
228
|
+
WebPushError, "web-push/bad-opts");
|
|
229
|
+
validateOpts(opts, ["subscription", "payload", "ttlSec"], "webPush.encrypt");
|
|
230
|
+
if (!opts.subscription || typeof opts.subscription !== "object" ||
|
|
231
|
+
!opts.subscription.keys || typeof opts.subscription.keys !== "object") {
|
|
232
|
+
throw new WebPushError("web-push/bad-subscription",
|
|
233
|
+
"encrypt: subscription must have a keys: { p256dh, auth } object");
|
|
234
|
+
}
|
|
235
|
+
validateOpts.requireNonEmptyString(opts.subscription.keys.p256dh, "p256dh",
|
|
236
|
+
WebPushError, "web-push/bad-p256dh");
|
|
237
|
+
validateOpts.requireNonEmptyString(opts.subscription.keys.auth, "auth",
|
|
238
|
+
WebPushError, "web-push/bad-auth");
|
|
239
|
+
var plaintext = Buffer.isBuffer(opts.payload) ? opts.payload
|
|
240
|
+
: typeof opts.payload === "string" ? Buffer.from(opts.payload, "utf8")
|
|
241
|
+
: null;
|
|
242
|
+
if (!plaintext) {
|
|
243
|
+
throw new WebPushError("web-push/bad-payload",
|
|
244
|
+
"encrypt: payload must be a Buffer or string");
|
|
245
|
+
}
|
|
246
|
+
// Decode the subscription's p256dh + auth.
|
|
247
|
+
var recipientPubRaw = Buffer.from(opts.subscription.keys.p256dh, "base64url");
|
|
248
|
+
if (recipientPubRaw.length !== 65 || recipientPubRaw[0] !== 0x04) { // allow:raw-byte-literal — uncompressed P-256 point shape per SEC1 §2.3.3
|
|
249
|
+
throw new WebPushError("web-push/bad-p256dh",
|
|
250
|
+
"encrypt: p256dh must be a 65-byte uncompressed P-256 point");
|
|
251
|
+
}
|
|
252
|
+
var authSecret = Buffer.from(opts.subscription.keys.auth, "base64url");
|
|
253
|
+
if (authSecret.length !== 16) { // allow:raw-byte-literal — RFC 8291 §3.2 auth_secret length
|
|
254
|
+
throw new WebPushError("web-push/bad-auth",
|
|
255
|
+
"encrypt: auth must be a 16-byte secret (got " + authSecret.length + ")");
|
|
256
|
+
}
|
|
257
|
+
// Generate ephemeral ECDH P-256 keypair.
|
|
258
|
+
var ephemeral = nodeCrypto.createECDH("prime256v1");
|
|
259
|
+
ephemeral.generateKeys();
|
|
260
|
+
var ephemeralPubRaw = ephemeral.getPublicKey(); // uncompressed 65 bytes
|
|
261
|
+
// ECDH shared secret.
|
|
262
|
+
var sharedSecret = ephemeral.computeSecret(recipientPubRaw); // allow:raw-byte-literal — ECDH shared secret (32 bytes per P-256)
|
|
263
|
+
// RFC 8291 §3.4 two-stage HKDF:
|
|
264
|
+
// PRK_key = HKDF-Extract(salt=auth_secret, IKM=ECDH_shared)
|
|
265
|
+
// key_info = "WebPush: info\x00" || ua_public || as_public
|
|
266
|
+
// IKM = HKDF-Expand(PRK_key, key_info, 32)
|
|
267
|
+
// Then RFC 8188 §2.2:
|
|
268
|
+
// salt = 16 random bytes
|
|
269
|
+
// PRK = HKDF-Extract(salt, IKM)
|
|
270
|
+
// cek_info = "Content-Encoding: aes128gcm\x00"
|
|
271
|
+
// nonce_info = "Content-Encoding: nonce\x00"
|
|
272
|
+
// CEK = HKDF-Expand(PRK, cek_info, 16)
|
|
273
|
+
// nonce = HKDF-Expand(PRK, nonce_info, 12)
|
|
274
|
+
var keyInfo = Buffer.concat([
|
|
275
|
+
Buffer.from("WebPush: info\x00", "utf8"),
|
|
276
|
+
recipientPubRaw,
|
|
277
|
+
ephemeralPubRaw,
|
|
278
|
+
]);
|
|
279
|
+
var ikm = _hkdf(authSecret, sharedSecret, keyInfo, 32); // allow:raw-byte-literal — 256-bit IKM
|
|
280
|
+
var salt = nodeCrypto.randomBytes(16); // allow:raw-byte-literal — RFC 8188 §2.2 16-byte salt
|
|
281
|
+
var cek = _hkdf(salt, ikm, Buffer.from("Content-Encoding: aes128gcm\x00", "utf8"), 16); // allow:raw-byte-literal — 128-bit AEAD key
|
|
282
|
+
var nonce = _hkdf(salt, ikm, Buffer.from("Content-Encoding: nonce\x00", "utf8"), 12); // allow:raw-byte-literal — 96-bit AEAD nonce
|
|
283
|
+
// RFC 8188 §2 padding: plaintext || 0x02 (delimiter for single-record).
|
|
284
|
+
// RFC 8291 mandates single-record (record_size > plaintext+padding+tag).
|
|
285
|
+
var padded = Buffer.concat([plaintext, Buffer.from([0x02])]); // allow:raw-byte-literal — RFC 8188 single-record delimiter
|
|
286
|
+
var cipher = nodeCrypto.createCipheriv("aes-128-gcm", cek, nonce);
|
|
287
|
+
var ct = Buffer.concat([cipher.update(padded), cipher.final()]);
|
|
288
|
+
var tag = cipher.getAuthTag();
|
|
289
|
+
// RFC 8188 §2.1 header: salt(16) || rs(4 big-endian) || idlen(1) || keyid
|
|
290
|
+
// For RFC 8291 the keyid is the as_public (ephemeral pubkey, 65 bytes).
|
|
291
|
+
var rs = padded.length + 16; // allow:raw-byte-literal — record size = plaintext + tag length
|
|
292
|
+
var header = Buffer.alloc(16 + 4 + 1); // allow:raw-byte-literal — salt + rs + idlen layout
|
|
293
|
+
salt.copy(header, 0);
|
|
294
|
+
header.writeUInt32BE(rs, 16); // allow:raw-byte-literal — salt offset
|
|
295
|
+
header[20] = ephemeralPubRaw.length; // allow:raw-byte-literal — rs offset
|
|
296
|
+
var body = Buffer.concat([header, ephemeralPubRaw, ct, tag]);
|
|
297
|
+
var ttlSec = opts.ttlSec || (28 * 24 * 3600); // allow:raw-time-literal — RFC 8030 §5.2 default
|
|
298
|
+
return {
|
|
299
|
+
body: body,
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Encoding": "aes128gcm",
|
|
302
|
+
"Content-Length": String(body.length),
|
|
303
|
+
"TTL": String(ttlSec),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _hkdf(salt, ikm, info, length) {
|
|
309
|
+
// RFC 5869 HKDF-Extract + Expand using SHA-256 (per RFC 8291 / 8188).
|
|
310
|
+
var prk = nodeCrypto.createHmac("sha256", salt).update(ikm).digest();
|
|
311
|
+
// Expand with one-byte counter (length <= 32 always in this use).
|
|
312
|
+
var t = Buffer.concat([info, Buffer.from([0x01])]); // allow:raw-byte-literal — HKDF counter start
|
|
313
|
+
var out = nodeCrypto.createHmac("sha256", prk).update(t).digest();
|
|
314
|
+
return out.slice(0, length);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = {
|
|
318
|
+
generateVapidKeypair: generateVapidKeypair,
|
|
319
|
+
buildVapidAuthHeader: buildVapidAuthHeader,
|
|
320
|
+
encrypt: encrypt,
|
|
321
|
+
WebPushError: WebPushError,
|
|
322
|
+
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:7fbc6c04-e867-4e6a-b8a0-d1686fdb9dc7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-19T15:04:27.770Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.11.1",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.11.1",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.11.1",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.
|
|
57
|
+
"ref": "@blamejs/core@0.11.1",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|