Skip to content

Commit

Permalink
Merge pull request IdentityModel#775 from ivantm/rsa
Browse files Browse the repository at this point in the history
Add swappable RSA-only crypto impl
  • Loading branch information
brockallen authored Jun 5, 2019
2 parents dca4344 + b7844a1 commit fddf1af
Show file tree
Hide file tree
Showing 11 changed files with 776 additions and 341 deletions.
86 changes: 85 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,93 @@ function copy_ts(){
.pipe(gulp.dest('./dist/'));
}

// Replace the babel-polyfill with specific core-js polyfills.
function slimBuildTarget() {
return {
mode: 'production',
entry: ['./polyfills.js', './index.js'],
output: {
filename: 'oidc-client.slim.min.js',
libraryTarget: 'var',
library: 'Oidc'
},
plugins: [],
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
keep_fnames: true
}
}
})
]
}
};
}
function slimBuildTargetSourceMap() {
return {
mode: 'development',
entry: ['./polyfills.js', './index.js'],
output: {
filename: 'oidc-client.slim.js',
libraryTarget: 'var',
library: 'Oidc'
},
plugins: [],
devtool:'inline-source-map'
};
}

// Adds a configuration for slimming down the production build. This build
// does not contain the full babel-polyfill. Instead it imports specific
// core-js polyfills
function build_dist_slim() {
return gulp.src('index.js')
.pipe(webpackStream(createWebpackConfig(slimBuildTarget()), webpack))
.pipe(gulp.dest('dist/'));
};
function build_dist_slim_sourcemap() {
return gulp.src('index.js')
.pipe(webpackStream(createWebpackConfig(slimBuildTargetSourceMap()), webpack))
.pipe(gulp.dest('dist/'));
};

// Creates a build with only RSA256 exponent+modulus support (no X509)
function build_dist_slim_rsa() {
var conf = slimBuildTarget();
conf.output.filename = 'oidc-client.rsa256.slim.min.js';

// This plugin should always be first in the chain
conf.plugins.unshift(
new webpack.NormalModuleReplacementPlugin(/(.*)JoseUtil(\.js)?$/, (resource) => {
resource.request = resource.request.replace(/JoseUtil/, 'JoseUtilRsa');
})
);

return gulp.src('index.js')
.pipe(webpackStream(createWebpackConfig(conf), webpack))
.pipe(gulp.dest('dist/'));
};
function build_dist_slim_rsa_sourcemap() {
var conf = slimBuildTargetSourceMap();
conf.output.filename = 'oidc-client.rsa256.slim.js';

// This plugin should always be first in the chain
conf.plugins.unshift(
new webpack.NormalModuleReplacementPlugin(/(.*)JoseUtil(\.js)?$/, (resource) => {
resource.request = resource.request.replace(/JoseUtil/, 'JoseUtilRsa');
})
);

return gulp.src('index.js')
.pipe(webpackStream(createWebpackConfig(conf), webpack))
.pipe(gulp.dest('dist/'));
};

// putting it all together
exports.default = gulp.series(
build_jsrsasign,
gulp.parallel(build_lib_sourcemap, build_lib_min, build_dist_sourcemap, build_dist_min),
gulp.parallel(build_lib_sourcemap, build_lib_min, build_dist_sourcemap, build_dist_min, build_dist_slim, build_dist_slim_rsa, build_dist_slim_sourcemap, build_dist_slim_rsa_sourcemap),
copy_ts
);
24 changes: 21 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
"babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.7.2",
"base64-js": "^1.3.0",
"chai": "^4.2.0",
"core-js": "^2.6.4",
"crypto-js": "^3.1.9-1",
"express": "^4.16.4",
"gulp": "^4.0.0",
"gulp-concat": "^2.6.1",
"gulp-rename": "^1.4.0",
"jsbn": "^1.1.0",
"jsrsasign": "^8.0.12",
"mocha": "^5.2.0",
"natives": "^1.1.6",
Expand Down
14 changes: 14 additions & 0 deletions polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Declare the ES6 features we're using

// TODO: Consider using the local function versions of these, so that we
// avoid modifying browser globals (potential for interop bugs with other libraries
// on the page that might be polyfilling ES6 features)

import 'core-js/es6/promise';
import 'core-js/fn/function/bind';
import 'core-js/fn/object/assign';
import 'core-js/fn/object/assign';
import 'core-js/fn/array/find';
import 'core-js/fn/array/some';
import 'core-js/fn/array/is-array';
import 'core-js/fn/array/splice';
167 changes: 3 additions & 164 deletions src/JoseUtil.js
Original file line number Diff line number Diff line change
@@ -1,165 +1,4 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
import { jws, KeyUtil, X509, crypto, hextob64u, b64tohex, AllowedSigningAlgs } from './crypto/jsrsasign';
import getJoseUtil from './JoseUtilImpl';

import { jws, KEYUTIL as KeyUtil, X509, crypto, hextob64u, b64tohex } from '../jsrsasign/dist/jsrsasign.js';
//import { jws, KEYUTIL as KeyUtil, X509, crypto, hextob64u, b64tohex } from 'jsrsasign';
import { Log } from './Log.js';

const AllowedSigningAlgs = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512'];

export class JoseUtil {

static parseJwt(jwt) {
Log.debug("JoseUtil.parseJwt");
try {
var token = jws.JWS.parse(jwt);
return {
header: token.headerObj,
payload: token.payloadObj
}
}
catch (e) {
Log.error(e);
}
}

static validateJwt(jwt, key, issuer, audience, clockSkew, now, timeInsensitive) {
Log.debug("JoseUtil.validateJwt");

try {
if (key.kty === "RSA") {
if (key.e && key.n) {
key = KeyUtil.getKey(key);
}
else if (key.x5c && key.x5c.length) {
var hex = b64tohex(key.x5c[0]);
key = X509.getPublicKeyFromCertHex(hex);
}
else {
Log.error("JoseUtil.validateJwt: RSA key missing key material", key);
return Promise.reject(new Error("RSA key missing key material"));
}
}
else if (key.kty === "EC") {
if (key.crv && key.x && key.y) {
key = KeyUtil.getKey(key);
}
else {
Log.error("JoseUtil.validateJwt: EC key missing key material", key);
return Promise.reject(new Error("EC key missing key material"));
}
}
else {
Log.error("JoseUtil.validateJwt: Unsupported key type", key && key.kty);
return Promise.reject(new Error("Unsupported key type: " + key && key.kty));
}

return JoseUtil._validateJwt(jwt, key, issuer, audience, clockSkew, now, timeInsensitive);
}
catch (e) {
Log.error(e && e.message || e);
return Promise.reject("JWT validation failed");
}
}

static validateJwtAttributes(jwt, issuer, audience, clockSkew, now, timeInsensitive) {
if (!clockSkew) {
clockSkew = 0;
}

if (!now) {
now = parseInt(Date.now() / 1000);
}

var payload = JoseUtil.parseJwt(jwt).payload;

if (!payload.iss) {
Log.error("JoseUtil._validateJwt: issuer was not provided");
return Promise.reject(new Error("issuer was not provided"));
}
if (payload.iss !== issuer) {
Log.error("JoseUtil._validateJwt: Invalid issuer in token", payload.iss);
return Promise.reject(new Error("Invalid issuer in token: " + payload.iss));
}

if (!payload.aud) {
Log.error("JoseUtil._validateJwt: aud was not provided");
return Promise.reject(new Error("aud was not provided"));
}
var validAudience = payload.aud === audience || (Array.isArray(payload.aud) && payload.aud.indexOf(audience) >= 0);
if (!validAudience) {
Log.error("JoseUtil._validateJwt: Invalid audience in token", payload.aud);
return Promise.reject(new Error("Invalid audience in token: " + payload.aud));
}
if (payload.azp && payload.azp !== audience) {
Log.error("JoseUtil._validateJwt: Invalid azp in token", payload.azp);
return Promise.reject(new Error("Invalid azp in token: " + payload.azp));
}

if (!timeInsensitive) {
var lowerNow = now + clockSkew;
var upperNow = now - clockSkew;

if (!payload.iat) {
Log.error("JoseUtil._validateJwt: iat was not provided");
return Promise.reject(new Error("iat was not provided"));
}
if (lowerNow < payload.iat) {
Log.error("JoseUtil._validateJwt: iat is in the future", payload.iat);
return Promise.reject(new Error("iat is in the future: " + payload.iat));
}

if (payload.nbf && lowerNow < payload.nbf) {
Log.error("JoseUtil._validateJwt: nbf is in the future", payload.nbf);
return Promise.reject(new Error("nbf is in the future: " + payload.nbf));
}

if (!payload.exp) {
Log.error("JoseUtil._validateJwt: exp was not provided");
return Promise.reject(new Error("exp was not provided"));
}
if (payload.exp < upperNow) {
Log.error("JoseUtil._validateJwt: exp is in the past", payload.exp);
return Promise.reject(new Error("exp is in the past:" + payload.exp));
}
}

return Promise.resolve(payload);
}

static _validateJwt(jwt, key, issuer, audience, clockSkew, now, timeInsensitive) {

return JoseUtil.validateJwtAttributes(jwt, issuer, audience, clockSkew, now, timeInsensitive).then(payload => {
try {
if (!jws.JWS.verify(jwt, key, AllowedSigningAlgs)) {
Log.error("JoseUtil._validateJwt: signature validation failed");
return Promise.reject(new Error("signature validation failed"));
}

return payload;
}
catch (e) {
Log.error(e && e.message || e);
return Promise.reject(new Error("signature validation failed"));
}
});
}

static hashString(value, alg) {
try {
return crypto.Util.hashString(value, alg);
}
catch (e) {
Log.error(e);
}
}

static hexToBase64Url(value) {
try {
return hextob64u(value);
}
catch (e) {
Log.error(e);
}
}
}
export const JoseUtil = getJoseUtil({ jws, KeyUtil, X509, crypto, hextob64u, b64tohex, AllowedSigningAlgs });
Loading

0 comments on commit fddf1af

Please sign in to comment.