Skip to content
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

support unsecured jwt #593

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions src/dom-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ export const secretInput =
document.querySelector('.jwt-signature input[name="secret"]');
export const secretBase64Checkbox =
document.getElementById('is-base64-encoded');
export const signatureArea =
document.querySelector('.jwt-explained.jwt-signature')
5 changes: 5 additions & 0 deletions src/editor/default-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,10 @@ export default {
privateKey: rsaPrivateKey,
jwk: rsaJwk,
publicKey: rsaPublicKey
},
none: {
token: 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.',
privateKey: '',
publicKey: ''
}
};
21 changes: 19 additions & 2 deletions src/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
encodedTabElement,
decodedTabElement,
editorWarnings,
signatureArea,
} from "../dom-elements.js";

import log from "loglevel";
Expand Down Expand Up @@ -74,6 +75,12 @@ function markAsInvalid(errorMessages = []) {
}
}

function markAsUnsecuredJwt() {
signatureStatusElement.classList.remove("valid-token");
signatureStatusElement.classList.add("invalid-token");
signatureStatusElement.innerHTML = `<i class="icon-budicon-501"></i> ${strings.editor.unsecuredJwt}`;
}

function markJWTAsInvalid() {
signatureStatusElement.classList.remove("valid-token");
signatureStatusElement.classList.add("invalid-token");
Expand All @@ -99,7 +106,11 @@ function displaySecretOrKeys(algorithm) {
hmacShaTextSpan.firstChild.textContent = `HMACSHA${algoSize}`;
secretEditorContainer.style.display = "";
keyEditorContainer.style.display = "none";
signatureArea.style.display = ""
} else if (algorithm === 'none') {
signatureArea.style.display = "none"
} else {
signatureArea.style.display = ""
const texts = {
RS: "RSASHA",
PS: "RSAPSSSHA",
Expand Down Expand Up @@ -157,7 +168,11 @@ export function useDefaultToken(algorithm) {
privateKeyTextArea.value = defaults.privateKey;
}

markAsValid();
if (algorithm !== 'none') {
markAsValid();
} else {
markAsUnsecuredJwt();
}
});
}

Expand Down Expand Up @@ -333,7 +348,7 @@ function verifyToken() {
const jwt = getTrimmedValue(tokenEditor);
const decoded = decode(jwt);

if (!decoded.header.alg || decoded.header.alg === "none") {
if (!decoded.header.alg) {
markAsInvalid();
return;
}
Expand All @@ -349,6 +364,8 @@ function verifyToken() {
} else {
markAsValid();
}
} else if (decoded.header.alg === 'none') {
markAsUnsecuredJwt();
} else {
markAsInvalid();
}
Expand Down
22 changes: 17 additions & 5 deletions src/editor/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ export function sign(header,
return Promise.reject(new Error('Missing "alg" claim in header'));
}

if (!(typeof payload === 'string' || payload instanceof String)) {
payload = JSON.stringify(payload);
}

if (header.alg === 'none') {
return Promise.resolve(`${jose.base64url.encode(JSON.stringify(header))}.${jose.base64url.encode(new TextEncoder().encode(payload))}.`)
}

return getJoseKey(header, secretOrPrivateKeyString, base64Secret, types.PRIVATE).then(
key => {
if (!(typeof payload === 'string' || payload instanceof String)) {
payload = JSON.stringify(payload);
}

return new jose.CompactSign(new TextEncoder().encode(payload))
.setProtectedHeader(header)
.sign(key);
Expand All @@ -89,12 +93,20 @@ export function verify(jwt, secretOrPublicKeyString, base64Secret = false) {
return Promise.resolve({ validSignature: false });
}

if (decoded.header.alg === 'none') {
return Promise.resolve({
validSignature: false,
unsecuredJwt: true,
validBase64: jwt.split('.').every((s) => isValidBase64String(s))
})
}

return getJoseKey(decoded.header, secretOrPublicKeyString, base64Secret, types.PUBLIC).then(
key => {
return jose.compactVerify(jwt, key)
.then(() => ({
validSignature: true,
validBase64: jwt.split('.').reduce((valid, s) => valid = valid && isValidBase64String(s), true)
validBase64: jwt.split('.').every((s) => isValidBase64String(s))
}), (e) => {
log.warn('Could not verify token: ', e);
return { validSignature: false }
Expand Down
1 change: 1 addition & 0 deletions src/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default {
editor: {
signatureVerified: 'signature verified',
signatureInvalid: 'invalid signature',
unsecuredJwt: 'Unsecured JWT',
jwtInvalid: 'Invalid JWT'
},
warnings: {
Expand Down
73 changes: 50 additions & 23 deletions test/unit/editor/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,57 @@ describe('JWT', function() {
privateKey = publicKey = vector.secret;
}

it(`signs/verifies ${alg.toUpperCase()}`, function () {
const header = { alg: alg.toUpperCase(), iat: Date.now() };
const payload = { sub: 'test' };

// test the default token
return jwt.verify(vector.token, publicKey).should.eventually.include({validSignature: true})
.then(() => {
// test signing
return jwt.sign(header, payload, privateKey).then(token => {
token.should.be.a('string');

const split = token.split('.');
split.should.have.lengthOf(3);

const decoded = jwt.decode(token);
decoded.header.should.deep.equal(header);
decoded.payload.should.deep.equal(payload);

// test verifying just signed token
return jwt.verify(token, publicKey)
.should.eventually.include({validSignature: true});
if (alg !== 'none') {
it(`signs/verifies ${alg.toUpperCase()}`, function () {
const header = { alg: alg.toUpperCase(), iat: Date.now() };
const payload = { sub: 'test' };

// test the default token
return jwt.verify(vector.token, publicKey).should.eventually.include({validSignature: true})
.then(() => {
// test signing
return jwt.sign(header, payload, privateKey).then(token => {
token.should.be.a('string');

const split = token.split('.');
split.should.have.lengthOf(3);

const decoded = jwt.decode(token);
decoded.header.should.deep.equal(header);
decoded.payload.should.deep.equal(payload);

// test verifying just signed token
return jwt.verify(token, publicKey)
.should.eventually.include({validSignature: true});
});
});
});
});
});
} else {
it(`encodes/decodes ${alg}`, function () {
const header = { alg, iat: Date.now() };
const payload = { sub: 'test' };

// test the default token
return jwt.verify(vector.token).should.eventually.include({validSignature: false, unsecuredJwt: true})
.then(() => {
// test signing
return jwt.sign(header, payload).then(token => {
token.should.be.a('string');

const split = token.split('.');
split.should.have.lengthOf(3);

const decoded = jwt.decode(token);
decoded.header.should.deep.equal(header);
decoded.payload.should.deep.equal(payload);

// test verifying unsecured jwt
return jwt.verify(token, publicKey)
.should.eventually.include({validSignature: false, unsecuredJwt: true});
});
});
});
}

if (jwk) {
it(`signs/verifies ${alg.toUpperCase()} with a JWK`, function () {
Expand Down
1 change: 1 addition & 0 deletions views/token-editor-algorithms.pug
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ select#algorithm-select
option(name='algorithm',value='PS256') PS256
option(name='algorithm',value='PS384') PS384
option(name='algorithm',value='PS512') PS512
option(name='algorithm',value='none') none