Skip to content

Commit a971be3

Browse files
author
Bruno Oliveira da Silva
committed
[KEYCLOAK-9996] Introduction of signature utility
- 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
1 parent 263fb1e commit a971be3

File tree

5 files changed

+2753
-981
lines changed

5 files changed

+2753
-981
lines changed

middleware/admin.js

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
'use strict';
1717

18+
var Token = require('./auth-utils/token');
19+
var Signature = require('./auth-utils/signature');
20+
1821
function Admin (keycloak, url) {
1922
this._keycloak = keycloak;
2023
if (url[ url.length - 1 ] !== '/') {
@@ -35,50 +38,63 @@ function adminLogout (request, response, keycloak) {
3538
});
3639

3740
request.on('end', function () {
38-
let payload;
39-
let parts = data.split('.');
41+
let token = new Token(data);
42+
let signature;
4043
try {
41-
payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
42-
} catch (e) {
43-
response.status(400).end();
44-
return;
45-
}
46-
if (payload.action === 'LOGOUT') {
47-
let sessionIDs = payload.adapterSessionIds;
48-
if (!sessionIDs) {
49-
keycloak.grantManager.notBefore = payload.notBefore;
50-
response.send('ok');
51-
return;
52-
}
53-
if (sessionIDs && sessionIDs.length > 0) {
54-
let seen = 0;
55-
sessionIDs.forEach(id => {
56-
keycloak.unstoreGrant(id);
57-
++seen;
58-
if (seen === sessionIDs.length) {
44+
signature = new Signature(keycloak.config);
45+
signature.verify(token).then(token => {
46+
if (token.content.action === 'LOGOUT') {
47+
let sessionIDs = token.content.adapterSessionIds;
48+
if (!sessionIDs) {
49+
keycloak.grantManager.notBefore = token.content.notBefore;
50+
response.send('ok');
51+
return;
52+
}
53+
if (sessionIDs && sessionIDs.length > 0) {
54+
let seen = 0;
55+
sessionIDs.forEach(id => {
56+
keycloak.unstoreGrant(id);
57+
++seen;
58+
if (seen === sessionIDs.length) {
59+
response.send('ok');
60+
}
61+
});
62+
} else {
5963
response.send('ok');
6064
}
61-
});
62-
} else {
63-
response.send('ok');
64-
}
65+
} else {
66+
response.status(400).end();
67+
}
68+
}).catch((err) => {
69+
response.status(401).end(err.message);
70+
});
71+
} catch (err) {
72+
response.status(400).end(err.message);
6573
}
6674
});
6775
}
6876

6977
function adminNotBefore (request, response, keycloak) {
7078
let data = '';
71-
7279
request.on('data', d => {
7380
data += d.toString();
7481
});
7582

7683
request.on('end', function () {
77-
let parts = data.split('.');
78-
let payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
79-
if (payload.action === 'PUSH_NOT_BEFORE') {
80-
keycloak.grantManager.notBefore = payload.notBefore;
81-
response.send('ok');
84+
let token = new Token(data);
85+
let signature;
86+
try {
87+
signature = new Signature(keycloak.config);
88+
signature.verify(token).then(token => {
89+
if (token.content.action === 'PUSH_NOT_BEFORE') {
90+
keycloak.grantManager.notBefore = token.content.notBefore;
91+
response.send('ok');
92+
}
93+
}).catch((err) => {
94+
response.status(401).end(err.message);
95+
});
96+
} catch (err) {
97+
response.status(400).end(err.message);
8298
}
8399
});
84100
}

middleware/auth-utils/signature.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*!
2+
* Copyright 2014 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
const Rotation = require('./rotation');
19+
const crypto = require('crypto');
20+
21+
/**
22+
* Construct a signature.
23+
*
24+
* @param {Config} config Config object.
25+
*
26+
* @constructor
27+
*/
28+
function Signature (config) {
29+
this.publicKey = config.publicKey;
30+
this.rotation = new Rotation(config);
31+
}
32+
33+
/**
34+
* Verify signed data using the token information provided
35+
* @TODO in the future provide more alternatives like HS256 support
36+
* @param {Token} the Token object
37+
*/
38+
Signature.prototype.verify = function verify (token, callback) {
39+
return new Promise((resolve, reject) => {
40+
const verify = crypto.createVerify('RSA-SHA256');
41+
42+
this.rotation.getJWK(token.header.kid).then(key => {
43+
verify.update(token.signed);
44+
if (!verify.verify(key, token.signature, 'base64')) {
45+
reject(new Error('admin request failed: invalid token (signature)'));
46+
} else {
47+
resolve(token);
48+
}
49+
}).catch((err) => {
50+
reject(new Error('failed to load public key to verify token. Reason: ' + err.message));
51+
});
52+
});
53+
};
54+
55+
module.exports = Signature;

0 commit comments

Comments
 (0)