Skip to content

Instantly share code, notes, and snippets.

@xfthhxk
Created October 2, 2023 19:27
Show Gist options
  • Save xfthhxk/3c986f846b17082e2b5b6e5997b5e240 to your computer and use it in GitHub Desktop.
Save xfthhxk/3c986f846b17082e2b5b6e5997b5e240 to your computer and use it in GitHub Desktop.
Digital Signatures with Node
/*
# 1. Generate public and private keys
# ```shell
# openssl genpkey -algorithm ED25519 -out ed25519.pem
# openssl pkey -in ed25519.pem -pubout > ed25519.pem.pub
# ```
# 2. Register the public key with the remote service
# 3. Ensure node requirements
# ```shell
# npm install axios
# ```
# 4. Invoke:
# ```shell
# node digital-signature.js ed25519.pem 'myKeyId' 'https://example.com/api/v1/thing?x=a'
# ```
*/
const crypto = require('node:crypto');
const fs = require('node:fs');
const axios = require('axios');
function b64str(bs) {
return Buffer.from(bs).toString('base64');
}
function genDigest(s) {
const hash = crypto.createHash('sha256');
hash.update(s);
return hash.digest('base64');
}
function fingerprint(request) {
const url = new URL(request.url);
var path = url.pathname + url.search;
var ans = '(request-target): ' + request.method.toLowerCase() + " " + path + "\n";
ans = ans + 'host: ' + url.host + '\n';
ans = ans + 'date: ' + request.headers['date'];
if ('digest' in request.headers) {
ans = ans + "\ndigest: " + request.headers['digest'];
}
return ans;
}
function genSignature(privateKey, payload) {
return b64str(crypto.sign(null, Buffer.from(payload), privateKey));
}
function genSignatureHeader(request, signature, keyId) {
var headers = ['(request-target)', 'host', 'date'];
if ('digest' in request.headers) {
headers.push('digest');
}
const headersStr = headers.join(' ');
return 'keyId="' + keyId + '",algorithm="ed25519",headers="' + headersStr + '",signature="' + signature + '"';
}
function signatureInterceptor(privateKey, keyId, request) {
request.headers['date'] = new Date().toUTCString();
if ('body' in request) {
request.headers['digest'] = genDigest(request.body);
}
const fp = fingerprint(request);
const sig = genSignature(privateKey, fp);
const sigHeader = genSignatureHeader(request, sig, keyId);
request.headers['signature'] = sigHeader;
return request;
}
const privateKeyFile = process.argv[2];
function readPrivateKey(file) {
const pem = fs.readFileSync(file);
return crypto.createPrivateKey({key: pem, format: 'pem'});
}
const PRIVATE_KEY = readPrivateKey(privateKeyFile);
const keyId = process.argv[3];
const endpoint = process.argv[4];
const instance = axios.create({
timeout: 1000
});
instance.interceptors.request.use(function(request) {
return signatureInterceptor(PRIVATE_KEY, keyId, request);
});
instance({
method: 'get',
url: endpoint,
responseType: 'string'
}).then(function (response) {
console.log(response.data);
}).catch(function(err) {
console.log(err.response.data);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment