Skip to content

Commit 67195f6

Browse files
authored
Merge pull request #1277 from cplussharp/jwk
2 parents 4619a51 + cbf990f commit 67195f6

File tree

5 files changed

+530
-0
lines changed

5 files changed

+530
-0
lines changed

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@
168168
"Hex to PEM",
169169
"Hex to Object Identifier",
170170
"Object Identifier to Hex",
171+
"PEM to JWK",
172+
"JWK to PEM",
171173
"Generate PGP Key Pair",
172174
"PGP Encrypt",
173175
"PGP Decrypt",

src/core/operations/JWKToPem.mjs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @author cplussharp
3+
* @copyright Crown Copyright 2021
4+
* @license Apache-2.0
5+
*/
6+
7+
import r from "jsrsasign";
8+
import Operation from "../Operation.mjs";
9+
import OperationError from "../errors/OperationError.mjs";
10+
11+
/**
12+
* PEM to JWK operation
13+
*/
14+
class PEMToJWK extends Operation {
15+
16+
/**
17+
* PEMToJWK constructor
18+
*/
19+
constructor() {
20+
super();
21+
22+
this.name = "JWK to PEM";
23+
this.module = "PublicKey";
24+
this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8).";
25+
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
26+
this.inputType = "string";
27+
this.outputType = "string";
28+
this.args = [];
29+
this.checks = [
30+
{
31+
"pattern": "\"kty\":\\s*\"(EC|RSA)\"",
32+
"flags": "gm",
33+
"args": []
34+
}
35+
];
36+
}
37+
38+
/**
39+
* @param {string} input
40+
* @param {Object[]} args
41+
* @returns {string}
42+
*/
43+
run(input, args) {
44+
const inputJson = JSON.parse(input);
45+
46+
let keys = [];
47+
if (Array.isArray(inputJson)) {
48+
// list of keys => transform all keys
49+
keys = inputJson;
50+
} else if (Array.isArray(inputJson.keys)) {
51+
// JSON Web Key Set => transform all keys
52+
keys = inputJson.keys;
53+
} else if (typeof inputJson === "object") {
54+
// single key
55+
keys.push(inputJson);
56+
} else {
57+
throw new OperationError("Input is not a JSON Web Key");
58+
}
59+
60+
let output = "";
61+
for (let i=0; i<keys.length; i++) {
62+
const jwk = keys[i];
63+
if (typeof jwk.kty !== "string") {
64+
throw new OperationError("Invalid JWK format");
65+
} else if ("|RSA|EC|".indexOf(jwk.kty) === -1) {
66+
throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);
67+
}
68+
69+
const key = r.KEYUTIL.getKey(jwk);
70+
const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, "PKCS8PRV") : r.KEYUTIL.getPEM(key);
71+
72+
// PEM ends with '\n', so a new key always starts on a new line
73+
output += pem;
74+
}
75+
76+
return output;
77+
}
78+
}
79+
80+
export default PEMToJWK;

src/core/operations/PEMToJWK.mjs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* @author cplussharp
3+
* @copyright Crown Copyright 2021
4+
* @license Apache-2.0
5+
*/
6+
7+
import r from "jsrsasign";
8+
import Operation from "../Operation.mjs";
9+
import OperationError from "../errors/OperationError.mjs";
10+
11+
/**
12+
* PEM to JWK operation
13+
*/
14+
class PEMToJWK extends Operation {
15+
16+
/**
17+
* PEMToJWK constructor
18+
*/
19+
constructor() {
20+
super();
21+
22+
this.name = "PEM to JWK";
23+
this.module = "PublicKey";
24+
this.description = "Converts Keys in PEM format to a JSON Web Key format.";
25+
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
26+
this.inputType = "string";
27+
this.outputType = "string";
28+
this.args = [];
29+
this.checks = [
30+
{
31+
"pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----",
32+
"args": []
33+
}
34+
];
35+
}
36+
37+
/**
38+
* @param {string} input
39+
* @param {Object[]} args
40+
* @returns {string}
41+
*/
42+
run(input, args) {
43+
let output = "";
44+
let match;
45+
const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g;
46+
while ((match = regex.exec(input)) !== null) {
47+
// find corresponding end tag
48+
const indexBase64 = match.index + match[0].length;
49+
const header = input.substring(match.index, indexBase64);
50+
const footer = `-----END ${match[1]}-----`;
51+
const indexFooter = input.indexOf(footer, indexBase64);
52+
if (indexFooter === -1) {
53+
throw new OperationError(`PEM footer '${footer}' not found`);
54+
}
55+
56+
const pem = input.substring(match.index, indexFooter + footer.length);
57+
if (match[1].indexOf("KEY") !== -1) {
58+
if (header === "-----BEGIN RSA PUBLIC KEY-----") {
59+
throw new OperationError("Unsupported RSA public key format. Only PKCS#8 is supported.");
60+
}
61+
62+
const key = r.KEYUTIL.getKey(pem);
63+
if (key.type === "DSA") {
64+
throw new OperationError("DSA keys are not supported for JWK");
65+
}
66+
const jwk = r.KEYUTIL.getJWKFromKey(key);
67+
if (output.length > 0) {
68+
output += "\n";
69+
}
70+
output += JSON.stringify(jwk);
71+
} else if (match[1] === "CERTIFICATE") {
72+
const cert = new r.X509();
73+
cert.readCertPEM(pem);
74+
const key = cert.getPublicKey();
75+
const jwk = r.KEYUTIL.getJWKFromKey(key);
76+
if (output.length > 0) {
77+
output += "\n";
78+
}
79+
output += JSON.stringify(jwk);
80+
} else {
81+
throw new OperationError(`Unsupported PEM type '${match[1]}'`);
82+
}
83+
}
84+
return output;
85+
}
86+
}
87+
88+
export default PEMToJWK;

tests/operations/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import "./tests/JSONBeautify.mjs";
8989
import "./tests/JSONMinify.mjs";
9090
import "./tests/JSONtoCSV.mjs";
9191
import "./tests/Jump.mjs";
92+
import "./tests/JWK.mjs";
9293
import "./tests/JWTDecode.mjs";
9394
import "./tests/JWTSign.mjs";
9495
import "./tests/JWTVerify.mjs";

0 commit comments

Comments
 (0)