Skip to content

Commit

Permalink
[KEYCLOAK-9996] Introduction of signature utility
Browse files Browse the repository at this point in the history
- Prepare to support other algorithms in the future like HS256
- Migrate from roi to axios
- Update all the integration tests
- Add testing script to prevent concurrency issues
  • Loading branch information
Bruno Oliveira da Silva committed Jul 29, 2019
1 parent 263fb1e commit a971be3
Show file tree
Hide file tree
Showing 5 changed files with 2,753 additions and 981 deletions.
76 changes: 46 additions & 30 deletions middleware/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
'use strict';

var Token = require('./auth-utils/token');
var Signature = require('./auth-utils/signature');

function Admin (keycloak, url) {
this._keycloak = keycloak;
if (url[ url.length - 1 ] !== '/') {
Expand All @@ -35,50 +38,63 @@ function adminLogout (request, response, keycloak) {
});

request.on('end', function () {
let payload;
let parts = data.split('.');
let token = new Token(data);
let signature;
try {
payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
} catch (e) {
response.status(400).end();
return;
}
if (payload.action === 'LOGOUT') {
let sessionIDs = payload.adapterSessionIds;
if (!sessionIDs) {
keycloak.grantManager.notBefore = payload.notBefore;
response.send('ok');
return;
}
if (sessionIDs && sessionIDs.length > 0) {
let seen = 0;
sessionIDs.forEach(id => {
keycloak.unstoreGrant(id);
++seen;
if (seen === sessionIDs.length) {
signature = new Signature(keycloak.config);
signature.verify(token).then(token => {
if (token.content.action === 'LOGOUT') {
let sessionIDs = token.content.adapterSessionIds;
if (!sessionIDs) {
keycloak.grantManager.notBefore = token.content.notBefore;
response.send('ok');
return;
}
if (sessionIDs && sessionIDs.length > 0) {
let seen = 0;
sessionIDs.forEach(id => {
keycloak.unstoreGrant(id);
++seen;
if (seen === sessionIDs.length) {
response.send('ok');
}
});
} else {
response.send('ok');
}
});
} else {
response.send('ok');
}
} else {
response.status(400).end();
}
}).catch((err) => {
response.status(401).end(err.message);
});
} catch (err) {
response.status(400).end(err.message);
}
});
}

function adminNotBefore (request, response, keycloak) {
let data = '';

request.on('data', d => {
data += d.toString();
});

request.on('end', function () {
let parts = data.split('.');
let payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
if (payload.action === 'PUSH_NOT_BEFORE') {
keycloak.grantManager.notBefore = payload.notBefore;
response.send('ok');
let token = new Token(data);
let signature;
try {
signature = new Signature(keycloak.config);
signature.verify(token).then(token => {
if (token.content.action === 'PUSH_NOT_BEFORE') {
keycloak.grantManager.notBefore = token.content.notBefore;
response.send('ok');
}
}).catch((err) => {
response.status(401).end(err.message);
});
} catch (err) {
response.status(400).end(err.message);
}
});
}
Expand Down
55 changes: 55 additions & 0 deletions middleware/auth-utils/signature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*!
* Copyright 2014 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

const Rotation = require('./rotation');
const crypto = require('crypto');

/**
* Construct a signature.
*
* @param {Config} config Config object.
*
* @constructor
*/
function Signature (config) {
this.publicKey = config.publicKey;
this.rotation = new Rotation(config);
}

/**
* Verify signed data using the token information provided
* @TODO in the future provide more alternatives like HS256 support
* @param {Token} the Token object
*/
Signature.prototype.verify = function verify (token, callback) {
return new Promise((resolve, reject) => {
const verify = crypto.createVerify('RSA-SHA256');

this.rotation.getJWK(token.header.kid).then(key => {
verify.update(token.signed);
if (!verify.verify(key, token.signature, 'base64')) {
reject(new Error('admin request failed: invalid token (signature)'));
} else {
resolve(token);
}
}).catch((err) => {
reject(new Error('failed to load public key to verify token. Reason: ' + err.message));
});
});
};

module.exports = Signature;
Loading

0 comments on commit a971be3

Please sign in to comment.