Skip to content

Commit cd44317

Browse files
committed
JWK conversion from/to PEM
1 parent c2cf535 commit cd44317

File tree

5 files changed

+533
-0
lines changed

5 files changed

+533
-0
lines changed

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@
143143
"Hex to PEM",
144144
"Hex to Object Identifier",
145145
"Object Identifier to Hex",
146+
"PEM to JWK",
147+
"JWK to PEM",
146148
"Generate PGP Key Pair",
147149
"PGP Encrypt",
148150
"PGP Decrypt",

src/core/operations/JWKToPem.mjs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 inputSingleLine = input.replace(/[\n\r]+/g, '');
45+
const inputJson = JSON.parse(input);
46+
47+
let keys = [];
48+
if (Array.isArray(inputJson)) {
49+
// list of keys => transform all keys
50+
keys = inputJson;
51+
} else if (Array.isArray(inputJson.keys)) {
52+
// JSON Web Key Set => transform all keys
53+
keys = inputJson.keys;
54+
} else if (typeof inputJson === "object") {
55+
// single key
56+
keys.push(inputJson);
57+
} else {
58+
throw new OperationError("Input is not a JSON Web Key");
59+
}
60+
61+
let output = "";
62+
for (let i=0; i<keys.length; i++) {
63+
const jwk = keys[i];
64+
if (typeof jwk.kty !== "string") {
65+
throw new OperationError("Invalid JWK format");
66+
} else if ("|RSA|EC|".indexOf(jwk.kty) === -1) {
67+
throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);
68+
}
69+
70+
const key = r.KEYUTIL.getKey(jwk);
71+
const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, "PKCS8PRV") : r.KEYUTIL.getPEM(key);
72+
73+
// PEM ends with '\n', so a new key always starts on a new line
74+
output += pem;
75+
}
76+
77+
return output;
78+
}
79+
}
80+
81+
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
@@ -57,6 +57,7 @@ import "./tests/Jump.mjs";
5757
import "./tests/JSONBeautify.mjs";
5858
import "./tests/JSONMinify.mjs";
5959
import "./tests/JSONtoCSV.mjs";
60+
import "./tests/JWK.mjs";
6061
import "./tests/JWTDecode.mjs";
6162
import "./tests/JWTSign.mjs";
6263
import "./tests/JWTVerify.mjs";

0 commit comments

Comments
 (0)