Merge 2cbe0d75754b4b2c3cc99051f3a8aa670beda133 into 4290ea753912378913b1f3f54e0fc5720afeda5d
This commit is contained in:
commit
04ba0d93b2
10
package-lock.json
generated
10
package-lock.json
generated
@ -53,6 +53,7 @@
|
||||
"highlight.js": "^11.11.1",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "1.6.0",
|
||||
"jose": "^6.2.3",
|
||||
"jq-web": "^0.5.1",
|
||||
"jquery": "3.7.1",
|
||||
"js-ascon": "^1.3.0",
|
||||
@ -12496,6 +12497,15 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
|
||||
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/jpeg-js": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
||||
|
||||
@ -138,6 +138,7 @@
|
||||
"highlight.js": "^11.11.1",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "1.6.0",
|
||||
"jose": "^6.2.3",
|
||||
"jq-web": "^0.5.1",
|
||||
"jquery": "3.7.1",
|
||||
"js-ascon": "^1.3.0",
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import * as asn1js from "asn1js";
|
||||
|
||||
export const MD_ALGORITHMS = {
|
||||
"SHA-1": forge.md.sha1,
|
||||
@ -15,3 +16,51 @@ export const MD_ALGORITHMS = {
|
||||
"SHA-384": forge.md.sha384,
|
||||
"SHA-512": forge.md.sha512,
|
||||
};
|
||||
|
||||
const rsaEncryptionOID = "1.2.840.113549.1.1.1";
|
||||
|
||||
/**
|
||||
* Convert PKCS#1 RSA private key (PEM) to PKCS#8 PEM
|
||||
* @param {string} originalPem
|
||||
* @returns {string}
|
||||
*/
|
||||
export function pkcs1ToPkcs8(originalPem) {
|
||||
// remove PEM headers
|
||||
const b64 = originalPem
|
||||
.replace(/-----BEGIN RSA PRIVATE KEY-----/g, "")
|
||||
.replace(/-----END RSA PRIVATE KEY-----/g, "")
|
||||
.replace(/\s+/g, "");
|
||||
|
||||
const pkcs1Der = Uint8Array.from(atob(b64), c => c.charCodeAt(0)).buffer;
|
||||
|
||||
// PKCS#8 structure:
|
||||
// PrivateKeyInfo ::= SEQUENCE {
|
||||
// version INTEGER,
|
||||
// privateKeyAlgorithm AlgorithmIdentifier,
|
||||
// privateKey OCTET STRING
|
||||
// }
|
||||
|
||||
const pkcs8Schema = new asn1js.Sequence({
|
||||
value: [
|
||||
new asn1js.Integer({ value: 0 }),
|
||||
new asn1js.Sequence({
|
||||
value: [
|
||||
// rsaEncryption OID
|
||||
new asn1js.ObjectIdentifier({ value: rsaEncryptionOID }),
|
||||
new asn1js.Null()
|
||||
]
|
||||
}),
|
||||
new asn1js.OctetString({ valueHex: pkcs1Der })
|
||||
]
|
||||
});
|
||||
|
||||
const pkcs8Der = pkcs8Schema.toBER(false);
|
||||
|
||||
const pkcs8B64 = btoa(
|
||||
String.fromCharCode(...new Uint8Array(pkcs8Der))
|
||||
);
|
||||
|
||||
const lines = pkcs8B64.match(/.{1,64}/g).join("\n");
|
||||
|
||||
return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
|
||||
}
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Operation from "../Operation.mjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { SignJWT, importPKCS8 } from "jose";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
|
||||
|
||||
import {pkcs1ToPkcs8} from "../lib/RSA.mjs";
|
||||
|
||||
/**
|
||||
* JWT Sign operation
|
||||
@ -50,21 +50,47 @@ class JWTSign extends Operation {
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
async run(input, args) {
|
||||
const [key, algorithm, header] = args;
|
||||
|
||||
let secret;
|
||||
try {
|
||||
return jwt.sign(input, key, {
|
||||
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||
header: JSON.parse(header || "{}")
|
||||
});
|
||||
if (key.startsWith("-----BEGIN RSA PRIVATE KEY-----")) {
|
||||
secret = await importPKCS8(pkcs1ToPkcs8(key), algorithm);
|
||||
} else if (key.startsWith("-----BEGIN PRIVATE KEY-----")) {
|
||||
secret = await importPKCS8(key, algorithm);
|
||||
} else {
|
||||
secret = new TextEncoder().encode(key);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
const fullHeader = { alg: algorithm, typ: "JWT" };
|
||||
try {
|
||||
if (header !== "{}") {
|
||||
Object.assign(fullHeader, JSON.parse(header));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Header must be a valid (or empty) json object.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await new SignJWT(input)
|
||||
.setProtectedHeader(fullHeader)
|
||||
.sign(secret);
|
||||
|
||||
return token;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default JWTSign;
|
||||
|
||||
@ -220,7 +220,7 @@ module.exports = {
|
||||
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
||||
testOp(browser, "Jsonata Query", '{"a": "SGVsbG8gV29ybGQh"}', '"Hello World!"', ["$base64decode($.a)"]);
|
||||
// testOp(browser, "JWT Decode", "test input", "test_output");
|
||||
// testOp(browser, "JWT Sign", "test input", "test_output");
|
||||
testOp(browser, "JWT Sign", '{"a":{"b":1}}', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjp7ImIiOjF9fQ.5PSBsZ9_B-qZa8H3l9tRAEV6qt8NgEHNJaoyjVcnTsU", ["A-key-of-256-bits-or-larger-as-per-RFC7518", "HS256", "{}"]);
|
||||
// testOp(browser, "JWT Verify", "test input", "test_output");
|
||||
// testOp(browser, "JavaScript Beautify", "test input", "test_output");
|
||||
// testOp(browser, "JavaScript Minify", "test input", "test_output");
|
||||
|
||||
@ -89,6 +89,19 @@ TestRegister.addTests([
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Sign: HS256, invalid header",
|
||||
input: inputObject,
|
||||
expectedOutput: `Header must be a valid (or empty) json object.
|
||||
|
||||
SyntaxError: Unexpected token 'h', "this is not JSON" is not valid JSON`,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [hsKey, "HS256", "this is not JSON"],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Sign: HS256 with custom header",
|
||||
input: inputObject,
|
||||
@ -142,7 +155,7 @@ TestRegister.addTests([
|
||||
input: inputObject,
|
||||
expectedOutput: `Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
||||
Error: "alg" parameter "ES384" requires curve "secp384r1".`,
|
||||
DataError: Named curve mismatch`,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
@ -189,7 +202,7 @@ Error: "alg" parameter "ES384" requires curve "secp384r1".`,
|
||||
input: inputObject,
|
||||
expectedOutput: `Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
||||
Error: secretOrPrivateKey has a minimum key size of 2048 bits for RS256`,
|
||||
TypeError: RS256 requires key modulusLength to be 2048 bits or larger`,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user