Source code

Revision control

Copy as Markdown

Other Tools

function toBase64Url(array) {
return btoa([...array].map(c => String.fromCharCode(c)).join('')).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "")
}
class VAPID {
#publicKey;
#privateKey;
constructor(publicKey, privateKey) {
this.#publicKey = publicKey;
this.#privateKey = privateKey;
}
get publicKey() {
return this.#publicKey;
}
async #jws(audience) {
// BASE64URL(UTF8(JWS Protected Header)) || '.' ||
// BASE64URL(JWS Payload) || '.' ||
// BASE64URL(JWS Signature)
// ECDSA on the NIST P-256 curve [FIPS186], which is identified as "ES256" [RFC7518].
const rawHeader = { typ: "JWT", alg: "ES256" };
const header = toBase64Url(new TextEncoder().encode(JSON.stringify(rawHeader)));
const rawPayload = {
// An "aud" (Audience) claim in the token MUST include the Unicode
// serialization of the origin (Section 6.1 of [RFC6454]) of the push
// resource URL.
aud: audience,
// An "exp" (Expiry) claim MUST be included with the time after which
// the token expires.
exp: parseInt(new Date().getTime() / 1000) + 24 * 60 * 60, // seconds, 24hr
// The "sub" claim SHOULD include a contact URI for the application server as either a
// "mailto:" (email) [RFC6068] or an "https:" [RFC2818] URI.
sub: "mailto:webpush@example.com",
};
const payload = toBase64Url(new TextEncoder().encode(JSON.stringify(rawPayload)));
const input = `${header}.${payload}`;
// ES256 | ECDSA using P-256 and SHA-256
const rawSignature = await crypto.subtle.sign({
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" },
}, this.#privateKey, new TextEncoder().encode(input));
const signature = toBase64Url(new Uint8Array(rawSignature));
return `${input}.${signature}`;
}
async generateAuthHeader(audience) {
// The "t" parameter of the "vapid" authentication scheme carries a JWT
// as described in Section 2.
const t = await this.#jws(audience);
// The "k" parameter includes an ECDSA public key [FIPS186] in
// uncompressed form [X9.62] that is encoded using base64url encoding
// [RFC7515].
const k = toBase64Url(this.#publicKey)
return `vapid t=${t},k=${k}`;
}
};
export async function createVapid() {
// The signature MUST use ECDSA on the NIST P-256 curve [FIPS186]
const keys = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]);
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keys.publicKey));
const privateKey = keys.privateKey;
return new VAPID(publicKey, privateKey);
};