Skip to content

Add njs to verify jwt and hmac #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions njs/misc/hmac_verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
async function getKey() {
const format = 'raw'
const keyData = new TextEncoder().encode(process.env.SECRET);
const algorithm = { name: 'HMAC', hash: 'SHA-1' };
const isExtractable = false;
const keyUsages = ['sign'];

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
return crypto.subtle.importKey(
format, // RAW
keyData, // Convert to 8-bit unsigned Buffer Array
algorithm, // HMAC algorithm
isExtractable, // Extract key
keyUsages // Key actions
);
}

async function generateHmac(message) {
const algorithm = 'HMAC';
const key = await getKey();
const data = new TextEncoder().encode(message);

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign
const signature = await crypto.subtle.sign(
algorithm, // HMAC algorithm
key, // CryptoKey
data // Convert to 8-bit unsigned Buffer Array
);

return btoa(Array
.from(new Uint8Array(signature))
.map(b => String.fromCharCode(b))
.join('')
);
}

function parseToken(token) {
const parsedToken = token
.replace(/-/g, "=")
.replace(/_/g, "+");

return {
digest: parsedToken.substring(0, 28),
iv: parsedToken.substring(28, 52),
encrypted: parsedToken.substring(52),
};
}

async function isSigned(token) {
const parsedToken = parseToken(token);
const hmac = await generateHmac(parsedToken.encrypted)

return (parsedToken.digest === hmac);
}
74 changes: 74 additions & 0 deletions njs/misc/jwt_verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
async function getKey() {
const pem = atob(process.env.PUBLIC_KEY
.replace('-----BEGIN PUBLIC KEY-----', '')
.replace('-----END PUBLIC KEY-----', '')
.replace(/\s/g, ''));

const format = 'spki';
const keyData = Uint8Array.from(pem, (char) => char.charCodeAt(0)).buffer;
const algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
const isExtractable = true;
const keyUsages = ['verify'];

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
return crypto.subtle.importKey(
format, // Subject Public Key Info
keyData, // Convert to 8-bit unsigned Buffer Array
algorithm, // RS256 algorithm
isExtractable, // Extract key
keyUsages // Key actions
);
}

function base64UrlDecode(input) {
const padding = '='.repeat((4 - (input.length % 4)) % 4);

return atob(input + padding);
}

function decodeBase64Url(base64Url) {
return base64Url
.replace(/-/g, '+')
.replace(/_/g, '/');
}

async function verify(token) {
const components = token.split('.');
const headerB64Url = components[0];
const payloadB64Url = components[1];
const signatureB64Url = components[2];

const isJWT = (headerB64Url && payloadB64Url && signatureB64Url);
if (!isJWT) {
console.error('Invalid JWT Format');

return { isValid: false };
}

// Decode base64URL to Base64
const headerBase64 = decodeBase64Url(headerB64Url);
const payloadBase64 = decodeBase64Url(payloadB64Url);
const signatureB64 = decodeBase64Url(signatureB64Url);

// Decode the Base64 bytes into utf-8
const header = JSON.parse(atob(headerBase64));
const payload = JSON.parse(atob(payloadBase64));
const signature = base64UrlDecode(signatureB64);

const algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
const key = await getKey();
const signatureBase = Uint8Array.from(signature, (char) => char.charCodeAt(0));
const signingInput = new TextEncoder().encode(`${headerB64Url}.${payloadB64Url}`);

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify
const isValid = await crypto.subtle.verify(
algorithm, // RSA256 algorithm
key, // CryptoKey
signatureBase, // Convert base64 to 8-bit unsigned
signingInput // Recreate the unsigned data to be verified (header.payload)
);

return { header, payload, isValid };
}

export default { verify }