92 lines
No EOL
3 KiB
JavaScript
92 lines
No EOL
3 KiB
JavaScript
// Copyright 2017 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
function urlB64ToUint8Array(base64String) {
|
|
const suffixLength = 4 - base64String.length % 4;
|
|
const base64 = (base64String + '='.repeat(suffixLength))
|
|
.replace(/\-/g, '+')
|
|
.replace(/_/g, '/');
|
|
|
|
const raw = window.atob(base64);
|
|
|
|
const output = new Uint8Array(raw.length);
|
|
Array.from(raw).forEach((c, i) => output[i] = c.charCodeAt(0));
|
|
return output;
|
|
}
|
|
|
|
function stringToBase64Url(s) {
|
|
const base64 = window.btoa(s);
|
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
|
|
}
|
|
|
|
function JSONToBase64Url(data) {
|
|
const s = JSON.stringify(data);
|
|
return stringToBase64Url(s);
|
|
}
|
|
|
|
function uint8ArrayToBase64Url(array) {
|
|
const s = String.fromCodePoint(...array);
|
|
return stringToBase64Url(s);
|
|
}
|
|
|
|
/**
|
|
* @param {string} publicKey public key in URL-safe base64
|
|
* @param {string} privateKey private key in URL-safe base64
|
|
* @param {string} endpoint from PushSubscription
|
|
* @param {string} sender either "mailto:<email>" or a web address
|
|
* @return {!Promise<string>} the Authorization header
|
|
*/
|
|
function prepareAuthorization(publicKey, privateKey, endpoint, sender) {
|
|
const origin = new URL(endpoint).origin;
|
|
const defaultExpiration = Math.floor(Date.now() / 1000) + 43200; // 12 hours in future
|
|
|
|
const header = {
|
|
typ: 'JWT',
|
|
alg: 'ES256'
|
|
};
|
|
const jwtPayload = {
|
|
aud: origin,
|
|
exp: defaultExpiration,
|
|
sub: sender,
|
|
};
|
|
|
|
// unsignedToken is the URL-safe base64 encoded JSON header and body joined by a dot.
|
|
const unsignedToken = JSONToBase64Url(header) + '.' + JSONToBase64Url(jwtPayload);
|
|
|
|
// Sign unsignedToken using ES256 (SHA-256 over ECDSA). This requires the private key.
|
|
const publicKeyArray = urlB64ToUint8Array(publicKey);
|
|
const key = {
|
|
kty: 'EC',
|
|
crv: 'P-256',
|
|
x: uint8ArrayToBase64Url(publicKeyArray.subarray(1, 33)),
|
|
y: uint8ArrayToBase64Url(publicKeyArray.subarray(33, 65)),
|
|
d: privateKey,
|
|
};
|
|
|
|
// Perform the signing. importKey returns a Promise, so wait for it to finish.
|
|
const args = {name: 'ECDSA', namedCurve: 'P-256'};
|
|
return crypto.subtle.importKey('jwk', key, args, true, ['sign'])
|
|
.then(key => {
|
|
return crypto.subtle.sign({
|
|
name: 'ECDSA',
|
|
hash: {
|
|
name: 'SHA-256',
|
|
},
|
|
}, key, (new TextEncoder('utf-8')).encode(unsignedToken));
|
|
})
|
|
.then(buffer => new Uint8Array(buffer))
|
|
.then(signature => {
|
|
return 'WebPush ' + unsignedToken + '.' + uint8ArrayToBase64Url(signature);
|
|
});
|
|
} |