This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// VAPID - Voluntary Application Server Identification for Web Push | |
// Step-by-Step Verification example | |
/* | |
[[ Web Push Archtecture with VAPID verification ]] | |
@author leegeunhyeok <dev.ghlee@gmail.com> | |
[0]Has VAPID Key Pair | |
| [7] | |
V [6] Verify request | |
+-------------+ (Request Push Message) using Public key | |
| Application | Sign JWT with +--------------+ | |
| Server |---- VAPID Private key ---->| Push Service | [3]Register Device and | |
+-------------+ + +--------------+ store VAPID Public key | |
^ | User Subscription ^ | | | |
| | | | | | |
[5]Subscription | | [1]Distribute VAPID Public key | | | | |
| | | | | | |
| V | | | | |
+--------+ [2]Subscribe + VAPID Public key | | | [8]Send push message to UA | |
| User |------------------------------------+ | | if request is valid | |
| Agent |<-------------------------------------+ | | |
+--------+ [4] Subscription | | |
^ | | |
| | | |
+--------------------------------------------+ | |
**/ | |
const webpush = require('web-push') | |
const urlBase64 = require('urlsafe-base64') | |
const asn1 = require('asn1.js') | |
const jws = require('jws') | |
const ECPublicKeyASN = asn1.define('ECPublicKey', function () { | |
this.seq().obj( | |
this.key('algorithm').seq().obj( | |
this.key('id').objid(), | |
this.key('curve').objid() | |
), | |
this.key('pub').bitstr() | |
) | |
}) | |
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function() { | |
this.seq().obj( | |
this.key('version').int(), | |
this.key('privateKey').octstr(), | |
this.key('parameters').explicit(0).objid().optional() | |
) | |
}) | |
// VAPID Public key to pem | |
function getPublicPEM (key) { | |
return ECPublicKeyASN.encode({ | |
algorithm: { | |
id: [1, 2, 840, 10045, 2, 1], | |
curve: [1, 2, 840, 10045, 3, 1, 7] | |
}, | |
pub: { | |
unused: 0, | |
data: key | |
} | |
}, 'pem', { | |
label: 'PUBLIC KEY' | |
}) | |
} | |
// VAPID Private key to pem | |
function getPrivatePEM (key) { | |
return ECPrivateKeyASN.encode({ | |
version: 1, | |
privateKey: key, | |
parameters: [1, 2, 840, 10045, 3, 1, 7] // prime256v1 | |
}, 'pem', { | |
label: 'EC PRIVATE KEY' | |
}) | |
} | |
function line (end = '') { | |
console.log('==============================' + end) | |
} | |
// Generate VAPID pair | |
const vapidKeys = webpush.generateVAPIDKeys() | |
// JSON Header | |
const header = { | |
typ: 'JWT', | |
alg: 'ES256' | |
} | |
// JSON Payload | |
const payload = { | |
aud: 'https://github.com', | |
exp: new Date().getTime(), | |
sub: 'mailto:dev.ghlee@gmail.com' | |
} | |
// VAPID to pem | |
const vapidKeysPem = { | |
publicKey: getPublicPEM(urlBase64.decode(vapidKeys.publicKey)), | |
privateKey: getPrivatePEM(urlBase64.decode(vapidKeys.privateKey)) | |
} | |
// Sign with VAPID Private key | |
const token = jws.sign({ | |
header, | |
payload, | |
privateKey: vapidKeysPem.privateKey | |
}) | |
console.log('VAPID Public Key:', vapidKeys.publicKey) | |
console.log('VAPID Private Key:', vapidKeys.privateKey) | |
line() | |
console.log('VAPID Public pem\n', vapidKeysPem.publicKey) | |
line() | |
console.log('VAPID Private pem\n', vapidKeysPem.privateKey) | |
line() | |
console.log('JSON Header', header) | |
console.log('JSON Payload', payload) | |
line() | |
console.log('Signed JWT:', token) | |
line() | |
// Verify with public key | |
const ok = jws.verify(token, header.alg, Buffer.from(vapidKeysPem.publicKey)) | |
console.log('Verified:', ok) | |
line() | |
let result | |
try { | |
// Verify with wrong public key | |
result = jws.verify(token, header.alg, Buffer.from(vapidKeysPem.publicKey + '000')) | |
} catch { | |
result = false | |
} | |
console.log('Verified:', result) | |
/* | |
[[ Result ]] | |
VAPID Public Key: BB9djjdQ236nQNnKTFfKuRZ9lqYKS3gQomKAN1LG2I76kN8ghMNqKk1QiwC_phNnmGlV-GgtGSMbx8-fHrHnK4A | |
VAPID Private Key: SARF5d1o5aVgHIbs9foU56ZhqpIrRxo-wsYVPCDA5B0 | |
============================== | |
VAPID Public pem: | |
-----BEGIN PUBLIC KEY----- | |
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH12ON1DbfqdA2cpMV8q5Fn2WpgpLeBCiYoA3UsbYjvqQ3yCEw2oqTVCLAL+mE2eYaVX4aC0ZIxvHz58esecrgA== | |
-----END PUBLIC KEY----- | |
============================== | |
// VAPID Private pem: | |
-----BEGIN EC PRIVATE KEY----- | |
MDECAQEEIEgEReXdaOWlYByG7PX6FOemYaqSK0caPsLGFTwgwOQdoAoGCCqGSM49AwEH | |
-----END EC PRIVATE KEY----- | |
============================== | |
JSON Header { typ: 'JWT', alg: 'ES256' } | |
JSON Payload { aud: 'https://github.com', | |
exp: 1591003437177, | |
sub: 'mailto:dev.ghlee@gmail.com' } | |
============================== | |
Signed JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dpdGh1bi5jb20iLCJleHAiOjE1OTEwMDM0MzcxNzcsInN1YiI6Im1haWx0bzpkZXYuZ2hsZWVAZ21haWwuY29tIn0.-tmFgevHgt88tjqIrS1Oa4K-Bd3KblXhFJ09YYISB9G9--qiHZ-kFyMrLcEzF3JhxIfC6QIYCoiask9oovvqoQ | |
============================== | |
Verified: true | |
============================== | |
Verified: false | |
**/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment