Skip to content

Commit 2dc123c

Browse files
committed
[feat] how-to guide: ssl converter; private key inspection; jwt debugger
1 parent a4c8bba commit 2dc123c

11 files changed

Lines changed: 644 additions & 53 deletions

docs/_includes/head_custom.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,27 @@
152152
{% elsif page.title == "Feedback" %}
153153
<title>TEDI | how can TEDI support your systems integration needs</title>
154154

155+
156+
{% elsif page.title == "How-To Guides" %}
157+
<title>TEDI | Useful engineering tools and walk-throughs</title>
158+
159+
{% elsif page.title == "Decode X.509 Certificates" %}
160+
<title>TEDI | Use this tool to decode PEM-encoded X.509 certificates</title>
161+
162+
{% elsif page.title == "Convert Certificate Formats" %}
163+
<title>TEDI | Convert Certificate to PEM, DER, and Base64 formats</title>
164+
165+
{% elsif page.title == "Inspect Private Keys" %}
166+
<title>TEDI | Decode RSA private keys and extract public keys</title>
167+
168+
{% elsif page.title == "JWT Debugger" %}
169+
<title>TEDI | Inspect, verify, and create JSON Web Tokens (JWT)</title>
170+
171+
172+
173+
174+
175+
155176
{% else %}
156177
<title>TEDI | {{ page.title | escape }}</title>
157178
{% endif %}

docs/assets/js/jwt-debugger.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
2+
// JWT Debugger
3+
4+
document.addEventListener('DOMContentLoaded', function () {
5+
const jwtInput = document.getElementById('jwtInput');
6+
const headerInput = document.getElementById('jwtHeaderInput');
7+
const payloadInput = document.getElementById('jwtPayloadInput');
8+
const secretInput = document.getElementById('jwtSecretInput');
9+
const encodedOutput = document.getElementById('jwtEncoded');
10+
const encodingSelect = document.getElementById('secretEncoding');
11+
const verifyStatus = document.getElementById('jwtVerifyStatus');
12+
13+
document.getElementById('decode-jwt')?.addEventListener('click', decodeJWT);
14+
document.getElementById('verify-jwt')?.addEventListener('click', verifyJWT);
15+
document.getElementById('encode-jwt')?.addEventListener('click', encodeJWT);
16+
17+
function decodeJWT() {
18+
const jwt = jwtInput.value.trim();
19+
const parts = jwt.split('.');
20+
if (parts.length !== 3) {
21+
return showError('Invalid JWT format (expected 3 parts)');
22+
}
23+
24+
try {
25+
const header = KJUR.jws.JWS.readSafeJSONString(b64urlDecode(parts[0]));
26+
const payload = KJUR.jws.JWS.readSafeJSONString(b64urlDecode(parts[1]));
27+
28+
headerInput.value = JSON.stringify(header, null, 2);
29+
payloadInput.value = JSON.stringify(payload, null, 2);
30+
showStatus('', '✅ Decoded successfully (not verified)');
31+
} catch (err) {
32+
showError('Failed to decode JWT: ' + err.message);
33+
}
34+
}
35+
36+
function verifyJWT() {
37+
try {
38+
const jwt = jwtInput.value.trim();
39+
const secret = secretInput.value.trim();
40+
const encoding = encodingSelect.value;
41+
if (!jwt || !secret) {
42+
return showError('JWT and secret are required.');
43+
}
44+
45+
const keyObj = {};
46+
keyObj[encoding] = secret;
47+
48+
const isValid = KJUR.jws.JWS.verify(jwt, keyObj, ['HS256']);
49+
50+
if (isValid) {
51+
const [headerB64, payloadB64] = jwt.split('.');
52+
const header = KJUR.jws.JWS.readSafeJSONString(b64urlDecode(headerB64));
53+
const payload = KJUR.jws.JWS.readSafeJSONString(b64urlDecode(payloadB64));
54+
55+
headerInput.value = JSON.stringify(header, null, 2);
56+
payloadInput.value = JSON.stringify(payload, null, 2);
57+
showStatus(true, '✅ Signature is valid');
58+
} else {
59+
showStatus(false, '❌ Signature is invalid');
60+
}
61+
} catch (err) {
62+
showError('Verification failed: ' + err.message);
63+
}
64+
}
65+
66+
function encodeJWT() {
67+
try {
68+
const header = JSON.parse(headerInput.value.trim());
69+
const payload = JSON.parse(payloadInput.value.trim());
70+
const secret = secretInput.value.trim();
71+
const encoding = encodingSelect.value;
72+
73+
const sHeader = JSON.stringify(header);
74+
const sPayload = JSON.stringify(payload);
75+
76+
const keyObj = {};
77+
keyObj[encoding] = secret;
78+
79+
const jwt = KJUR.jws.JWS.sign(header.alg || 'HS256', sHeader, sPayload, keyObj);
80+
81+
jwtInput.value = jwt;
82+
showStatus('', '✅ JWT encoded successfully');
83+
} catch (err) {
84+
showError('Encoding failed: ' + err.message);
85+
}
86+
}
87+
88+
function b64urlDecode(str) {
89+
str = str.replace(/-/g, '+').replace(/_/g, '/');
90+
while (str.length % 4 !== 0) str += '=';
91+
return atob(str);
92+
}
93+
94+
function showError(msg) {
95+
verifyStatus.innerText = '❌ ' + msg;
96+
verifyStatus.className = 'text-danger font-weight-bold';
97+
}
98+
99+
function showStatus(valid, msg) {
100+
verifyStatus.innerText = valid === true
101+
? '✅ Signature is valid'
102+
: valid === false
103+
? '❌ Signature is invalid'
104+
: msg || '';
105+
verifyStatus.className = valid === true
106+
? 'text-success font-weight-bold'
107+
: 'text-danger font-weight-bold';
108+
}
109+
});
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// This script is used to inspect and extract public keys from private keys.
2+
3+
document.addEventListener('DOMContentLoaded', function () {
4+
const input = document.getElementById('keyInput');
5+
const output = document.getElementById('keyOutput');
6+
7+
const inspectBtn = document.getElementById('inspect-key');
8+
const pubKeyBtn = document.getElementById('extract-pubkey');
9+
const resetBtn = document.getElementById('reset-btn');
10+
const toPKCS8Btn = document.getElementById('convert-to-pkcs8');
11+
const toRSA1Btn = document.getElementById('convert-to-rsa');
12+
13+
if (inspectBtn) inspectBtn.addEventListener('click', inspectPrivateKey);
14+
if (pubKeyBtn) pubKeyBtn.addEventListener('click', extractPublicKey);
15+
if (resetBtn) resetBtn.addEventListener('click', () => {
16+
input.value = '';
17+
output.innerText = '';
18+
});
19+
if (toPKCS8Btn) toPKCS8Btn.addEventListener('click', () => convertFormat('pkcs8'));
20+
if (toRSA1Btn) toRSA1Btn.addEventListener('click', () => convertFormat('rsa1'));
21+
22+
function getOutputFormat() {
23+
return document.querySelector('input[name="keyOutputFormat"]:checked')?.value || 'pem';
24+
}
25+
26+
function parsePrivateKey(raw) {
27+
try {
28+
const cleaned = raw.trim();
29+
30+
if (cleaned.includes('EC PRIVATE KEY')) {
31+
throw new Error('EC keys are not supported. Please use an RSA key.');
32+
}
33+
34+
if (cleaned.includes('PRIVATE KEY')) {
35+
const key = forge.pki.privateKeyFromPem(cleaned);
36+
if (!key || !key.n || !key.e) {
37+
throw new Error('Not a valid RSA private key.');
38+
}
39+
return key;
40+
}
41+
42+
const isHex = /^[0-9a-f\s]+$/i.test(cleaned);
43+
const derBytes = isHex
44+
? forge.util.hexToBytes(cleaned.replace(/\s+/g, ''))
45+
: forge.util.decode64(cleaned);
46+
47+
const asn1 = forge.asn1.fromDer(derBytes);
48+
const key = forge.pki.privateKeyFromAsn1(asn1);
49+
if (!key || !key.n || !key.e) {
50+
throw new Error('Not a valid RSA private key.');
51+
}
52+
53+
return key;
54+
} catch (e) {
55+
throw new Error('Unable to parse private key: ' + e.message);
56+
}
57+
}
58+
59+
function inspectPrivateKey() {
60+
output.innerText = '';
61+
try {
62+
const key = parsePrivateKey(input.value.trim());
63+
64+
const details = {
65+
type: 'RSA',
66+
bits: key.n.bitLength(),
67+
publicExponent: key.e.toString(10),
68+
};
69+
70+
output.innerText = JSON.stringify(details, null, 2);
71+
} catch (err) {
72+
output.innerText = '❌ Error: ' + err.message;
73+
}
74+
}
75+
76+
function extractPublicKey() {
77+
output.innerText = '';
78+
try {
79+
const privateKey = parsePrivateKey(input.value.trim());
80+
81+
if (!privateKey || !privateKey.n || !privateKey.e) {
82+
throw new Error('Invalid RSA private key — cannot extract public key.');
83+
}
84+
85+
const publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
86+
const format = getOutputFormat();
87+
let result;
88+
89+
if (format === 'pem') {
90+
result = forge.pki.publicKeyToPem(publicKey);
91+
} else {
92+
let asn1;
93+
try {
94+
asn1 = forge.pki.publicKeyToAsn1(publicKey);
95+
} catch (err) {
96+
throw new Error('Failed to convert public key to ASN.1: ' + err.message);
97+
}
98+
99+
if (!asn1) {
100+
throw new Error('publicKeyToAsn1() returned undefined.');
101+
}
102+
103+
const der = forge.asn1.toDer(asn1).getBytes();
104+
105+
if (format === 'base64') {
106+
result = forge.util.encode64(der).match(/.{1,64}/g).join('\n');
107+
} else if (format === 'hex') {
108+
result = forge.util.bytesToHex(der).match(/.{1,2}/g).join(' ');
109+
} else {
110+
throw new Error('Unknown output format: ' + format);
111+
}
112+
}
113+
114+
output.innerText = result;
115+
} catch (err) {
116+
output.innerText = '❌ Error: ' + err.message;
117+
}
118+
}
119+
120+
121+
function convertFormat(targetFormat) {
122+
output.innerText = '';
123+
try {
124+
const privateKey = parsePrivateKey(input.value.trim());
125+
126+
if (!privateKey.n || !privateKey.e) {
127+
throw new Error('Invalid RSA private key structure.');
128+
}
129+
130+
let result;
131+
132+
if (targetFormat === 'pkcs8') {
133+
const pkcs8 = forge.pki.privateKeyToAsn1(privateKey);
134+
const wrapped = forge.pki.wrapRsaPrivateKey(pkcs8);
135+
result = forge.pki.privateKeyInfoToPem(wrapped);
136+
} else if (targetFormat === 'rsa1') {
137+
result = forge.pki.privateKeyToPem(privateKey);
138+
}
139+
140+
output.innerText = result;
141+
} catch (err) {
142+
output.innerText = '❌ Error: ' + err.message;
143+
}
144+
}
145+
});

docs/assets/js/tls-cert-common.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
// This script is used to load the forge library if it is not already loaded.
3+
(function loadForgeIfNeeded() {
4+
if (typeof forge === 'undefined') {
5+
const script = document.createElement('script');
6+
script.src = 'https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js';
7+
document.head.appendChild(script);
8+
}
9+
})();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// This script is used to convert TLS certificates between different formats
2+
3+
document.addEventListener('DOMContentLoaded', function () {
4+
const input = document.getElementById('certInput');
5+
const output = document.getElementById('certOutput');
6+
7+
const toDERBtn = document.getElementById('convert-der');
8+
const toB64Btn = document.getElementById('convert-base64');
9+
const toPEMBtn = document.getElementById('convert-pem');
10+
const pubKeyBtn = document.getElementById('extract-pubkey');
11+
const resetBtn = document.getElementById('reset-btn');
12+
13+
if (toDERBtn) toDERBtn.addEventListener('click', () => convertCert('der'));
14+
if (toB64Btn) toB64Btn.addEventListener('click', () => convertCert('base64'));
15+
if (toPEMBtn) toPEMBtn.addEventListener('click', convertToPEM);
16+
if (pubKeyBtn) pubKeyBtn.addEventListener('click', extractPublicKey);
17+
if (resetBtn) resetBtn.addEventListener('click', () => {
18+
input.value = '';
19+
output.innerText = '';
20+
});
21+
22+
function getSelectedFormat() {
23+
return document.querySelector('input[name="certFormat"]:checked')?.value || 'pem';
24+
}
25+
26+
function parseCertificate() {
27+
const format = getSelectedFormat();
28+
const raw = input.value.trim();
29+
30+
try {
31+
if (format === 'pem') {
32+
return forge.pki.certificateFromPem(raw);
33+
}
34+
35+
let bytes;
36+
if (format === 'der') {
37+
bytes = forge.util.decode64(raw);
38+
} else if (format === 'hex') {
39+
bytes = forge.util.hexToBytes(raw.replace(/\s+/g, ''));
40+
}
41+
42+
const asn1 = forge.asn1.fromDer(bytes);
43+
return forge.pki.certificateFromAsn1(asn1);
44+
} catch (e) {
45+
throw new Error('Could not parse certificate: ' + e.message);
46+
}
47+
}
48+
49+
function convertCert(type) {
50+
output.innerText = '';
51+
try {
52+
const cert = parseCertificate();
53+
const asn1 = forge.pki.certificateToAsn1(cert);
54+
const derBytes = forge.asn1.toDer(asn1).getBytes();
55+
56+
if (type === 'der') {
57+
const hex = forge.util.bytesToHex(derBytes);
58+
output.innerText = 'DER (hex):\n' + hex.match(/.{1,2}/g).join(' ');
59+
} else if (type === 'base64') {
60+
const b64 = forge.util.encode64(derBytes);
61+
output.innerText = 'Base64:\n' + b64.match(/.{1,64}/g).join('\n');
62+
}
63+
} catch (e) {
64+
output.innerText = '❌ Error: ' + e.message;
65+
}
66+
}
67+
68+
function convertToPEM() {
69+
output.innerText = '';
70+
try {
71+
const cert = parseCertificate();
72+
const pem = forge.pki.certificateToPem(cert);
73+
output.innerText = pem;
74+
} catch (e) {
75+
output.innerText = '❌ Error: ' + e.message;
76+
}
77+
}
78+
79+
function extractPublicKey() {
80+
output.innerText = '';
81+
try {
82+
const cert = parseCertificate();
83+
const publicKeyPem = forge.pki.publicKeyToPem(cert.publicKey);
84+
output.innerText = publicKeyPem;
85+
} catch (e) {
86+
output.innerText = '❌ Error: ' + e.message;
87+
}
88+
}
89+
});
90+

0 commit comments

Comments
 (0)