Appears fixed Crypto-Key header
This commit is contained in:
BIN
crypto_playpen/header.bin
Normal file
BIN
crypto_playpen/header.bin
Normal file
Binary file not shown.
1
crypto_playpen/message.txt
Normal file
1
crypto_playpen/message.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hello!
|
||||||
5
crypto_playpen/privatekey.pem
Normal file
5
crypto_playpen/privatekey.pem
Normal file
@@ -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
Normal file
1
crypto_playpen/public_key.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
A<><41><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/]<5D>C|E<><45>ݐ<EFBFBD>V@<40>\&hc<>}<7D><15>`M|Trl<72><6C>2<EFBFBD><32>.<2E><>ܑ<EFBFBD>Er<45><72>_hl<68><6C><EFBFBD>=c
|
||||||
BIN
crypto_playpen/public_key.der
Normal file
BIN
crypto_playpen/public_key.der
Normal file
Binary file not shown.
4
crypto_playpen/publickey.pem
Normal file
4
crypto_playpen/publickey.pem
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQazvs+7/4y9drkN8RZCB3ZCFVhMZ
|
||||||
|
QLtcJmgeY5x9+RXqYE18VHJsqagywecu9JLckZFFcraOX2hsifyEPQgCYw==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
BIN
crypto_playpen/signature.bin
Normal file
BIN
crypto_playpen/signature.bin
Normal file
Binary file not shown.
67
keys.md
Normal file
67
keys.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
@@ -19,6 +20,9 @@ export interface BrowserSubscription {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MyIncomingMessage extends IncomingMessage {
|
||||||
|
errno?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class NotificationService {
|
export class NotificationService {
|
||||||
private static instance: NotificationService;
|
private static instance: NotificationService;
|
||||||
@@ -39,6 +43,7 @@ export class NotificationService {
|
|||||||
await this.pushToEndpoint(subscription, message);
|
await this.pushToEndpoint(subscription, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async pushToEndpoint(subscription: BrowserSubscription, message: Message): Promise<void> {
|
private async pushToEndpoint(subscription: BrowserSubscription, message: Message): Promise<void> {
|
||||||
const payloadString = JSON.stringify(message);
|
const payloadString = JSON.stringify(message);
|
||||||
const payloadBuffer = Buffer.from(payloadString, 'utf-8');
|
const payloadBuffer = Buffer.from(payloadString, 'utf-8');
|
||||||
@@ -55,34 +60,36 @@ export class NotificationService {
|
|||||||
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
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(parsedUrl);
|
|
||||||
console.log(options);
|
|
||||||
|
|
||||||
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 {
|
|
||||||
reject(new Error(`Failed to send push notification. Status code: ${res.statusCode}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', (error) => {
|
console.log('Headers:', res.headers);
|
||||||
reject(new Error(`Failed to send push notification. Error: ${error.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
req.write(encrypted);
|
res.on('data', chunk => { body += chunk; });
|
||||||
req.end();
|
|
||||||
|
res.on('end', () => {
|
||||||
|
console.log('Body:', body);
|
||||||
|
console.log(res.statusCode);
|
||||||
|
|
||||||
|
if (res.statusCode! >= 200 && res.statusCode! < 300) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to send push notification. Status code: ${res.statusCode}, Body: ${body}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (error) => {
|
||||||
|
reject(new Error(`Failed to send push notification. Error: ${error.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(encrypted);
|
||||||
|
req.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,9 +59,23 @@ class VapidService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private isP256Key(publicKeyBuffer) {
|
||||||
|
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 }> {
|
async createVapidAuthHeader(endpoint: string, expiration: number, subject: string, appKeys: VapidKeys): Promise<{ 'Authorization': string, 'Crypto-Key': string }> {
|
||||||
const { publicKey, privateKey } = appKeys;
|
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),
|
||||||
@@ -72,14 +86,58 @@ class VapidService {
|
|||||||
const privateKeyBuffer = Buffer.from(privateKey, 'base64');
|
const privateKeyBuffer = Buffer.from(privateKey, 'base64');
|
||||||
ecdh.setPrivateKey(privateKeyBuffer);
|
ecdh.setPrivateKey(privateKeyBuffer);
|
||||||
|
|
||||||
const pems = ecKeyUtils.generatePem({curveName, privateKey: ecdh.getPrivateKey(), publicKey: ecdh.getPublicKey() });
|
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 pems = ecKeyUtils.generatePem({curveName, privateKey: ecdh.getPrivateKey(), publicKey: rawPublicKeyBuffer });
|
||||||
|
|
||||||
|
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' });
|
const jwtToken = jwt.sign(jwtInfo, pems.privateKey, { algorithm: 'ES256' });
|
||||||
|
|
||||||
return {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user