Compare commits

...

9 Commits

  1. 2
      .gitignore
  2. 5
      Dockerfile
  3. 3
      build.sh
  4. BIN
      crypto_playpen/header.bin
  5. 1
      crypto_playpen/message.txt
  6. 5
      crypto_playpen/privatekey.pem
  7. 1
      crypto_playpen/public_key.bin
  8. BIN
      crypto_playpen/public_key.der
  9. 4
      crypto_playpen/publickey.pem
  10. BIN
      crypto_playpen/signature.bin
  11. 74
      keys.md
  12. 19171
      package-lock.json
  13. 113
      package.json
  14. BIN
      push_server
  15. 4
      src/db.ts
  16. 36
      src/main.ts
  17. 115
      src/notificationService.ts
  18. 89
      src/vapidService.ts

2
.gitignore

@ -1,3 +1,5 @@
*~ *~
node_modules node_modules
build build
web_push
data

5
Dockerfile

@ -1,7 +1,10 @@
FROM node:18.17.1-alpine3.17 FROM node:18.17.1-alpine3.17
RUN mkdir -p /usr/src/app/data
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apk add bash
COPY package*.json ./ COPY package*.json ./
COPY tsconfig.json ./ COPY tsconfig.json ./
COPY .eslintrc.json ./ COPY .eslintrc.json ./
@ -11,8 +14,6 @@ RUN npm install
RUN npm run build RUN npm run build
COPY . .
EXPOSE 3000 EXPOSE 3000
CMD [ "npm", "start" ] CMD [ "npm", "start" ]

3
build.sh

@ -0,0 +1,3 @@
#!/bin/bash
docker build . -t endorser-push-server:1.0 --no-cache

BIN
crypto_playpen/header.bin

Binary file not shown.

1
crypto_playpen/message.txt

@ -0,0 +1 @@
hello!

5
crypto_playpen/privatekey.pem

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOjRzTX6T5FkhmOscZZdGp1b1PuOgk2p/YoJ7abFaJPPoAoGCCqGSM49
AwEHoUQDQgAEQazvs+7/4y9drkN8RZCB3ZCFVhMZQLtcJmgeY5x9+RXqYE18VHJs
qagywecu9JLckZFFcraOX2hsifyEPQgCYw==
-----END EC PRIVATE KEY-----

1
crypto_playpen/public_key.bin

@ -0,0 +1 @@
A¬ï³îÿã/]®C|E��Ý�…V@»\&hcœ}ùê`M|Trl©¨2Áç.ô’Ü‘‘Er¶Ž_hl‰ü„=c

BIN
crypto_playpen/public_key.der

Binary file not shown.

4
crypto_playpen/publickey.pem

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQazvs+7/4y9drkN8RZCB3ZCFVhMZ
QLtcJmgeY5x9+RXqYE18VHJsqagywecu9JLckZFFcraOX2hsifyEPQgCYw==
-----END PUBLIC KEY-----

BIN
crypto_playpen/signature.bin

Binary file not shown.

74
keys.md

@ -0,0 +1,74 @@
# NOTES on working with Cryptographic Keys
Since the VAPID key pair was created using cyprto.createECDH we could reconstitute our public key
using only the private key:
```
const curveName = 'prime256v1';
const ecdh = crypto.createECDH(curveName);
const privateKeyBuffer = Buffer.from(privateKey, 'base64');
ecdh.setPrivateKey(privateKeyBuffer);
const rawPublicKeyBuffer = ecdh.getPublicKey();
```
Unfortunately, crypto module creates only "raw" keys. And when working with jsonwebtoken.sign method
we must have a PEM or something with ASN metadata. So, we create PEMs using eckeys-util module:
```
const pems = ecKeyUtils.generatePem({curveName, privateKey: ecdh.getPrivateKey(), publicKey: rawPublicKeyBuffer });
console.log("privateKey: ", pems.privateKey);
console.log();
console.log("publicKey: ", pems.publicKey);
const jwtToken = jwt.sign(jwtInfo, pems.privateKey, { algorithm: 'ES256' });
```
I trie here to create my own ASN1 metadata but this seems doomed due to ignorance of what were the required
components:
```
const asn1Header = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex');
const derPublicKeyBuffer = Buffer.concat([asn1Header, rawPublicKeyBuffer]);
const base64DerPublicKey = derPublicKeyBuffer.toString('base64');
console.log("base64DerPublicKey: ", base64DerPublicKey)
```
Such an approach creates a DER key pair. An alternative to that method is:
```
const ders = ecKeyUtils.generateDer({curveName, privateKey: ecdh.getPrivateKey(), publicKey: rawPublicKeyBuffer });
console.log("privateKey: ", ders.privateKey);
console.log("publicKey: ", ders.publicKey);
```
... using eckeys-util again ... but I'm not 100% sure if these have all the necessary ASN1 metadata AND
the DER key will produce this error ...
```
Error: secretOrPrivateKey must be an asymmetric key when using ES256
at module.exports [as sign] (/usr/src/app/node_modules/jsonwebtoken/sign.js:124:22)
```
... when used in the sign method. So, apparently, sign does not like the DER binary format but it is
fine with PEM.
## When sending a notification request to the Mozilla endpoint it does not like the Crypto-Key header:
```
{
"code":400,
"errno":110,
"error":"Bad Request",
"message":"Invalid aes128gcm Crypto-Key header",
"more_info":"http://autopush.readthedocs.io/en/latest/http.html#error-codes"
}
```
fcm.google.com push server:
```
authorization header had invalid format. authorization header should have the following format: t=jwtToken; k=base64(publicApplicationServerKey)
403
```

19171
package-lock.json

File diff suppressed because it is too large

113
package.json

@ -1,58 +1,59 @@
{ {
"name": "node-typescript-boilerplate", "name": "node-typescript-boilerplate",
"version": "0.0.0", "version": "0.0.0",
"description": "Minimalistic boilerplate to quick-start Node.js development in TypeScript.", "description": "Minimalistic boilerplate to quick-start Node.js development in TypeScript.",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">= 18.12 <19" "node": ">= 18.12 <19"
}, },
"devDependencies": { "devDependencies": {
"@types/body-parser": "^1.19.2", "@types/body-parser": "^1.19.2",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "~29.5", "@types/jest": "~29.5",
"@types/jsonwebtoken": "^9.0.2", "@types/jsonwebtoken": "^9.0.2",
"@types/node": "~20", "@types/node": "~20",
"@types/sqlite3": "^3.1.8", "@types/sqlite3": "^3.1.8",
"@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0", "@typescript-eslint/parser": "^6.4.0",
"eslint": "~8.47", "eslint": "~8.47",
"eslint-config-prettier": "~9.0", "eslint-config-prettier": "~9.0",
"eslint-plugin-jest": "~27.2", "eslint-plugin-jest": "~27.2",
"jest": "~29.6", "jest": "~29.6",
"prettier": "~3.0", "prettier": "~3.0",
"rimraf": "~5.0", "rimraf": "~5.0",
"ts-api-utils": "~1.0", "ts-api-utils": "~1.0",
"ts-jest": "~29.1", "ts-jest": "~29.1",
"typescript": "~5.1" "typescript": "~5.1"
}, },
"scripts": { "scripts": {
"start": "node build/src/main.js", "start": "node build/src/main.js",
"clean": "rimraf coverage build tmp", "clean": "rimraf coverage build tmp",
"prebuild": "npm run lint", "prebuild": "npm run lint",
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"build:watch": "tsc -w -p tsconfig.json", "build:watch": "tsc -w -p tsconfig.json",
"build:release": "npm run clean && tsc -p tsconfig.release.json", "build:release": "npm run clean && tsc -p tsconfig.release.json",
"lint": "eslint . --ext .ts --ext .mts", "lint": "eslint . --ext .ts --ext .mts",
"test": "jest --coverage", "test": "jest --coverage",
"prettier": "prettier --config .prettierrc --write .", "prettier": "prettier --config .prettierrc --write .",
"test:watch": "jest --watch" "test:watch": "jest --watch"
}, },
"author": "Jakub Synowiec <jsynowiec@users.noreply.github.com>", "author": "Jakub Synowiec <jsynowiec@users.noreply.github.com>",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"elliptic": "^6.5.4", "eckey-utils": "^0.7.13",
"express": "^4.18.2", "elliptic": "^6.5.4",
"http_ece": "^1.1.0", "express": "^4.18.2",
"jsonwebtoken": "^9.0.1", "http_ece": "^1.1.0",
"node-fetch": "^3.3.2", "jsonwebtoken": "^9.0.1",
"npm-check-updates": "16.11.1", "node-fetch": "^3.3.2",
"reflect-metadata": "^0.1.13", "npm-check-updates": "16.11.1",
"sqlite3": "^5.1.6", "reflect-metadata": "^0.1.13",
"tslib": "~2.6", "sqlite3": "^5.1.6",
"typeorm": "^0.3.17" "tslib": "~2.6",
}, "typeorm": "^0.3.17"
"volta": { },
"node": "18.12.1" "volta": {
} "node": "18.12.1"
}
} }

BIN
push_server

Binary file not shown.

4
src/db.ts

@ -15,7 +15,7 @@ class DBService {
private constructor() { private constructor() {
this.dataSource = new DataSource({ this.dataSource = new DataSource({
type: "sqlite", type: "sqlite",
database: "push_server", database: "/usr/src/app/data/push_server",
entities: [VapidKeys, Subscription], entities: [VapidKeys, Subscription],
synchronize: true synchronize: true
}); });
@ -86,11 +86,9 @@ class DBService {
async getVapidKeys(): Promise<VapidKeys[]> { async getVapidKeys(): Promise<VapidKeys[]> {
console.log(__filename, "getVapidKeys", this.isReady);
let result = [ new VapidKeys() ]; let result = [ new VapidKeys() ];
if ( this.isReady ) { if ( this.isReady ) {
result = await this.dataSource.manager.find(VapidKeys); result = await this.dataSource.manager.find(VapidKeys);
console.log(__filename, "results of find: ", result);
} else { } else {
console.log(__filename, "Database is not ready"); console.log(__filename, "Database is not ready");

36
src/main.ts

@ -14,7 +14,7 @@ import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
export interface Subscription { export interface BrowserSubscription {
endpoint: string; endpoint: string;
keys: { keys: {
p256dh: string; p256dh: string;
@ -27,15 +27,13 @@ class Server {
private port: number; private port: number;
private worker?: Worker; private worker?: Worker;
private subscriptionService: SubscriptionService = SubscriptionService.getInstance(); private subscriptionService: SubscriptionService = SubscriptionService.getInstance();
private notificationService: NotificationService; private notificationService: NotificationService = NotificationService.getInstance();
dbService: DBService = DBService.getInstance(); dbService: DBService = DBService.getInstance();
vapidService: VapidService = VapidService.getInstance(); vapidService: VapidService = VapidService.getInstance();
private message: Message;
constructor(port: number) { constructor(port: number) {
this.app = express(); this.app = express();
this.port = port; this.port = port;
this.notificationService = new NotificationService();
this.setupRoutes(); this.setupRoutes();
this.startWorker(); this.startWorker();
@ -44,32 +42,41 @@ class Server {
private setupRoutes(): void { private setupRoutes(): void {
this.app.post('/subscribe', async (req: Request, res: Response) => { this.app.use(express.json())
const subscription = req.body as Subscription; this.app.post('/web-push/subscribe', async (req: Request, res: Response) => {
const subscription = req.body as BrowserSubscription;
const message = { "title": "You are subscribed." } as Message;
await this.subscriptionService.addSubscription(subscription); await this.subscriptionService.addSubscription(subscription);
await this.notificationService.sendNotification(subscription, message);
res.status(201).send(); res.status(201).send();
}); });
this.app.post('/unsubscribe', async (req: Request, res: Response) => { this.app.post('/web-push/unsubscribe', async (req: Request, res: Response) => {
const subscription = req.body as Subscription; const subscription = req.body as BrowserSubscription;
console.log(subscription); console.log(subscription);
res.status(501).send(); res.status(501).send();
}); });
this.app.post('/mute', async (req: Request, res: Response) => { this.app.post('/web-push/mute', async (req: Request, res: Response) => {
const subscription = req.body as Subscription; const subscription = req.body as BrowserSubscription;
console.log(subscription); console.log(subscription);
res.status(501).send(); res.status(501).send();
}); });
this.app.get('/vapid', async (_: Request, res: Response) => { this.app.get('/web-push/vapid', async (_: Request, res: Response) => {
const vapidkeys: VapidKeys[] = await this.vapidService.getVapidKeys(); const vapidkeys: VapidKeys[] = await this.vapidService.getVapidKeys();
const vapidkey = vapidkeys[0]; const vapidkey = vapidkeys[0];
res.send({"vapidKey": vapidkey['publicKey']}); res.send({"vapidKey": vapidkey['publicKey']});
}); });
this.app.use((req, _, next) => {
console.log("Raw body:", req.body);
next();
});
} }
@ -80,8 +87,7 @@ class Server {
this.worker.on('message', (message) => { this.worker.on('message', (message) => {
console.log(message); console.log(message);
this.message = { "title": "Check TimeSafari"} as Message;
this.notificationService.broadcast(this.message);
}); });
this.worker.on('error', (error) => { this.worker.on('error', (error) => {
@ -95,6 +101,7 @@ class Server {
}); });
} }
private setupWorkerListeners(): void { private setupWorkerListeners(): void {
if (this.worker) { if (this.worker) {
this.worker.on('message', (message) => { this.worker.on('message', (message) => {
@ -107,6 +114,7 @@ class Server {
} }
} }
public start(): void { public start(): void {
this.app.listen(this.port, () => { this.app.listen(this.port, () => {
console.log(`Server is running on http://localhost:${this.port}`); console.log(`Server is running on http://localhost:${this.port}`);
@ -116,7 +124,6 @@ class Server {
// Initialize and start the server // Initialize and start the server
const server = new Server(3000); const server = new Server(3000);
@ -128,6 +135,7 @@ const executeAsyncFunction = async () => {
} }
}; };
setTimeout(() => { setTimeout(() => {
executeAsyncFunction().catch(error => { executeAsyncFunction().catch(error => {
// Handle any errors here // Handle any errors here

115
src/notificationService.ts

@ -1,10 +1,9 @@
import SubscriptionService from './subscriptionService.js';
import VapidService from './vapidService.js'; import VapidService from './vapidService.js';
import { VapidKeys } from './VapidKeys.js'; import { VapidKeys } from './VapidKeys.js';
import { IncomingMessage } from 'http';
import * as https from 'https'; import * as https from 'https';
import * as http_ece from 'http_ece'; import * as http_ece from 'http_ece';
import crypto from 'crypto'; import crypto from 'crypto';
import { Subscription } from "./Subscription.js"
export interface Message { export interface Message {
title: string; title: string;
@ -12,83 +11,105 @@ export interface Message {
[key: string]: any; [key: string]: any;
} }
export class NotificationService {
private subscriptionService: SubscriptionService = SubscriptionService.getInstance(); export interface BrowserSubscription {
endpoint: string;
keys: {
p256dh: string;
auth: string;
};
}
interface MyIncomingMessage extends IncomingMessage {
errno?: number;
}
export class NotificationService {
private static instance: NotificationService;
private vapidService: VapidService = VapidService.getInstance(); private vapidService: VapidService = VapidService.getInstance();
constructor() { constructor() {
} }
private generateSalt(length = 16): Buffer { public static getInstance(): NotificationService {
return crypto.randomBytes(length); if (!NotificationService.instance) {
NotificationService.instance = new NotificationService();
}
return NotificationService.instance;
} }
async broadcast(message: Message): Promise<void> {
const subscriptions = await this.subscriptionService.fetchSubscriptions();
for (const subscription of subscriptions) {
await this.pushToEndpoint(subscription, message);
}
}
async sendNotification(subscription: Subscription, message: Message) { async sendNotification(subscription: BrowserSubscription, message: Message) {
await this.pushToEndpoint(subscription, message); await this.pushToEndpoint(subscription, message);
} }
private async pushToEndpoint(subscription: Subscription, message: Message): Promise<void> {
const payload = JSON.stringify(message);
const encrypted = this.encrypt(subscription.keys_p256dh, subscription.keys_auth, payload); private async pushToEndpoint(subscription: BrowserSubscription, message: Message): Promise<void> {
const payloadString = JSON.stringify(message);
const payloadBuffer = Buffer.from(payloadString, 'utf-8');
const vapidKeys: VapidKeys[] = await this.vapidService.getVapidKeys();
const vapidkey: VapidKeys = vapidKeys[0];
const encrypted = await this.encrypt(subscription.keys.p256dh, subscription.keys.auth, payloadBuffer);
const endpoint = subscription.endpoint; const endpoint = subscription.endpoint;
const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com'); const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com', vapidkey);
const parsedUrl = new URL(subscription.endpoint); const parsedUrl = new URL(subscription.endpoint);
const options: https.RequestOptions = { const options: https.RequestOptions = {
method: 'POST', method: 'POST',
hostname: parsedUrl.hostname, hostname: parsedUrl.hostname,
path: parsedUrl.pathname, path: parsedUrl.pathname,
port: parsedUrl.port, port: 443,
headers: { headers: { ...vapidHeaders, 'TTL': '60', 'Content-Encoding': 'aes128gcm', 'Content-Type': 'application/octet-stream', 'Content-Length': encrypted.length },
...vapidHeaders,
'TTL': '60',
'Content-Encoding': 'aes128gcm',
'Content-Type': 'application/octet-stream',
'Content-Length': encrypted.length
},
}; };
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const req = https.request(options, (res) => { const req = https.request(options, (res: MyIncomingMessage) => {
if (res.statusCode! >= 200 && res.statusCode! < 300) { let body = '';
resolve();
} else { console.log('Headers:', res.headers);
reject(new Error(`Failed to send push notification. Status code: ${res.statusCode}`));
} res.on('data', chunk => { body += chunk; });
});
res.on('end', () => {
console.log('Body:', body);
console.log(res.statusCode);
req.on('error', (error) => { if (res.statusCode! >= 200 && res.statusCode! < 300) {
reject(new Error(`Failed to send push notification. Error: ${error.message}`)); resolve();
} else {
reject(new Error(`Failed to send push notification. Status code: ${res.statusCode}, Body: ${body}`));
}
}); });
});
req.write(encrypted); req.on('error', (error) => {
req.end(); reject(new Error(`Failed to send push notification. Error: ${error.message}`));
});
req.write(encrypted);
req.end();
}); });
} }
private encrypt(publicKey: string, auth: string, payload: string): Buffer {
http_ece.keys = {
'p256dh': publicKey,
'auth': auth
};
const vapidKeys: VapidKeys = this.vapidService.getVapidKeys()[0]; private async encrypt( p256dh: string, auth: string, payload: Buffer): Promise<Buffer> {
try {
const ecdh = crypto.createECDH('prime256v1');
ecdh.generateKeys();
const publicKeyBuffer: Buffer = Buffer.from(p256dh, 'base64');
return http_ece.encrypt(payload, { return http_ece.encrypt(payload, {
'salt': this.generateSalt(), 'version': 'aes128gcm',
'dh': vapidKeys.publicKey, 'privateKey': ecdh,
'keyid': 'p256dh', 'dh': publicKeyBuffer,
'contentEncoding': 'aes128gcm' 'authSecret': Buffer.from(auth)
}); });
} catch (error) {
console.error('Error encrypting payload:', error);
throw error;
}
} }
} }

89
src/vapidService.ts

@ -1,4 +1,4 @@
import ecKeyUtils from "eckey-utils";
import { VapidKeys } from './VapidKeys.js'; import { VapidKeys } from './VapidKeys.js';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import crypto from 'crypto'; import crypto from 'crypto';
@ -16,7 +16,6 @@ class VapidService {
private static instance: VapidService; private static instance: VapidService;
private dbService: DBService = DBService.getInstance(); private dbService: DBService = DBService.getInstance();
private constructor() { private constructor() {
} }
@ -53,17 +52,6 @@ class VapidService {
return result; return result;
} }
/*
private async addVapidKeys(vapidkeys: VapidKeyData): Promise<VapidKeys> {
let result = new VapidKeys();
const keys = await this.getVapidKeys();
if (keys.length == 1 && typeof(keys[0].publicKey) == "undefined" ) {
result = await this.dbService.saveVapidKeys(vapidkeys.publicKey, vapidkeys.privateKey);
}
return result;
}
*/
async getVapidKeys(): Promise<VapidKeys[]> { async getVapidKeys(): Promise<VapidKeys[]> {
let result = await this.dbService.getVapidKeys(); let result = await this.dbService.getVapidKeys();
@ -71,21 +59,86 @@ class VapidService {
} }
async createVapidAuthHeader(endpoint: string, expiration: number, subject: string): Promise<{ 'Authorization': string, 'Crypto-Key': string }> { private isP256Key(publicKeyBuffer) {
const { publicKey, privateKey } = await this.getVapidKeys()[0]; if (publicKeyBuffer.length === 65 && publicKeyBuffer[0] === 0x04) {
return true;
}
if (publicKeyBuffer.length === 33 && (publicKeyBuffer[0] === 0x02 || publicKeyBuffer[0] === 0x03)) {
return true;
}
return false;
}
async createVapidAuthHeader(endpoint: string, expiration: number, subject: string, appKeys: VapidKeys): Promise<{ 'Authorization': string, 'Crypto-Key': string }> {
const { publicKey, privateKey } = appKeys;
console.log(publicKey);
const jwtInfo = { const jwtInfo = {
aud: new URL(endpoint).origin, aud: new URL(endpoint).origin,
exp: Math.floor((Date.now() / 1000) + expiration), exp: Math.floor((Date.now() / 1000) + expiration),
sub: subject sub: subject
}; };
console.log(jwtInfo);
const curveName = 'prime256v1';
const ecdh = crypto.createECDH(curveName);
const privateKeyBuffer = Buffer.from(privateKey, 'base64');
ecdh.setPrivateKey(privateKeyBuffer);
const rawPublicKeyBuffer = ecdh.getPublicKey();
const asn1Header = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex');
const derPublicKeyBuffer = Buffer.concat([asn1Header, rawPublicKeyBuffer]);
const base64DerPublicKey = derPublicKeyBuffer.toString('base64');
console.log("base64DerPublicKey: ", base64DerPublicKey)
const jwtToken = jwt.sign(jwtInfo, privateKey, { algorithm: 'ES256' }); const pems = ecKeyUtils.generatePem({curveName, privateKey: ecdh.getPrivateKey(), publicKey: rawPublicKeyBuffer });
return { console.log("privateKey: ", pems.privateKey);
console.log();
console.log("publicKey: ", pems.publicKey);
const ders = ecKeyUtils.generateDer({curveName, privateKey: ecdh.getPrivateKey(), publicKey: rawPublicKeyBuffer });
console.log("privateKey: ", ders.privateKey);
console.log("publicKey: ", ders.publicKey);
const jwtToken = jwt.sign(jwtInfo, pems.privateKey, { algorithm: 'ES256' });
try {
const decoded = Buffer.from(base64DerPublicKey, 'base64').toString('utf-8');
console.log('Valid Base64 Encoded:', decoded);
console.log('isP256Key:', this.isP256Key(Buffer.from(base64DerPublicKey, 'base64')) );
} catch (error) {
console.log('Invalid Base64 Encoding');
}
try {
const decodedKey = Buffer.from(base64DerPublicKey, 'base64');
console.log('Decoded Key Length:', decodedKey.length);
if (decodedKey.length !== 65) {
console.log('Invalid Key Length');
} else {
console.log('Key Length is Valid');
}
} catch (err) {
console.log('Invalid Base64-URL Encoding');
}
const result = {
'Authorization': `vapid t=${jwtToken}, k=${publicKey}`, 'Authorization': `vapid t=${jwtToken}, k=${publicKey}`,
'Crypto-Key': publicKey 'Crypto-Key': "p256ecdsa="+ders.publicKey.toString('base64')
}; };
console.log(result);
return result;
} }
} }

Loading…
Cancel
Save