New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

brightspace-auth-keys

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

brightspace-auth-keys - npm Package Compare versions

Comparing version 6.0.2 to 7.0.0

19

package.json
{
"name": "brightspace-auth-keys",
"version": "6.0.2",
"version": "7.0.0",
"description": "Library for generating, storing, and retrieving keypairs for use in Brightspace's auth framework.",
"files": [
"src",
"errors",
"README.md",
"LICENSE"
],
"main": "src/index.js",
"scripts": {
"test": "nyc --all --produce-source-map --require source-map-support mocha --recursive ./test"
"test": "nyc --all --produce-source-map --require source-map-support/register mocha --recursive ./test"
},

@@ -17,9 +23,12 @@ "author": "D2L Corporation",

"type": "git",
"url": "git+ssh://git@github.com/Brightspace/node-auth.git"
"url": "git+ssh://git@github.com/Brightspace/node-auth.git",
"directory": "packages/node_modules/brightspace-auth-keys"
},
"engines": {
"node": ">=10.12.0"
},
"dependencies": {
"jwk-to-pem": "^2.0.0",
"native-crypto": "^1.7.2",
"@trust/keyto": "^0.3.5",
"uuid": "^3.1.0"
}
}

@@ -95,10 +95,6 @@ # brightspace-auth-keys

signingKeyType: 'EC', // A type of signing keys to generate. 'RSA' or 'EC'. REQUIRED
signingKeyAge: 3600, // Length of time, in seconds, for a private key to remain in use
signingKeyOverlap: 300, // Length of time, in seconds, for a public key to remain valid
// after its private key has been rotated out. This is effectively
// the maximum lifetime of a signed token.
// RSA-specific settings:
rsa: {
signingKeySize: 2048 // RSA key size, in bits
lifetimes: {
keyUse: 3600, // Length of time, in seconds, for a private key to remain in use
token: 300 // Max length of time, in seconds, that a signed token will remain valid
},

@@ -111,2 +107,7 @@

// RSA-specific settings:
rsa: {
signingKeySize: 2048 // RSA key size, in bits
},
publicKeyStore: new RedisPublicKeyStore(...) // A backend for storing public keys.

@@ -113,0 +114,0 @@ // Can be anything: Redis, MSSQL, PostgreSQL, etc.

@@ -8,16 +8,2 @@ 'use strict';

function arrayOrEmpty(arr) {
return Array.isArray(arr)
? arr
: [];
}
function parseKeys(keys) {
return keys.map(JSON.parse);
}
function filterExpiredKeys(keys) {
return keys.filter(isNotExpired);
}
function isNotExpired(key) {

@@ -33,29 +19,26 @@ return key.exp >= clock();

lookupPublicKeys() {
return Promise
.resolve()
.then(() => this._lookupPublicKeys())
.then(arrayOrEmpty)
.then(parseKeys)
.then(filterExpiredKeys);
async lookupPublicKeys() {
let keys = await this._lookupPublicKeys();
keys = Array.isArray(keys) ? keys : [];
keys = keys.map(JSON.parse);
keys = keys.filter(isNotExpired);
return keys;
}
lookupPublicKey(kid) {
return this
.lookupPublicKeys()
.then(keys => {
for (const key of keys) {
if (key.kid === kid) {
return key;
}
}
async lookupPublicKey(kid) {
const keys = await this.lookupPublicKeys();
throw new PublicKeyNotFoundError(kid);
});
for (const key of keys) {
if (key.kid === kid) {
return key;
}
}
throw new PublicKeyNotFoundError(kid);
}
storePublicKey(key) {
return Promise
.resolve()
.then(() => this._storePublicKey(JSON.stringify(key), key.exp));
async storePublicKey(key) {
return this._storePublicKey(JSON.stringify(key), key.exp);
}

@@ -62,0 +45,0 @@ }

@@ -14,86 +14,42 @@ 'use strict';

const DEFAULT_SIGNING_KEY_AGE = 60 * 60;
const MINIMUM_SIGNING_KEY_AGE = 60 * 60;
const MAXIMUM_SIGNING_KEY_AGE = 24 * 60 * 60;
const DEFAULT_SIGNING_KEY_OVERLAP = 5 * 60;
const MINIMUM_SIGNING_KEY_OVERLAP = 5 * 60;
/* @this */
function parseOpts(opts) {
if (typeof opts !== 'object') {
throw new TypeError(`"opts" should be an Object. Got "${typeof opts}".`);
}
if (!(opts.publicKeyStore instanceof AbstractPublicKeyStore)) {
throw new TypeError('"opts.publicKeyStore" should be an implementation of AbstractPublicKeyStore');
}
this._publicKeyStore = opts.publicKeyStore;
if (typeof opts.signingKeyAge !== 'undefined') {
const age = opts.signingKeyAge;
if (typeof age !== 'number' || age !== Math.round(age)) {
throw new TypeError(`"opts.signingKeyAge" should be a integer. Got "${age}" (${typeof age}).`);
class CoreKeyGenerator {
constructor(opts) {
if (typeof opts !== 'object') {
throw new TypeError(`"opts" should be an Object. Got "${typeof opts}".`);
}
if (age < MINIMUM_SIGNING_KEY_AGE || MAXIMUM_SIGNING_KEY_AGE < age) {
throw new Error(`"opts.signingKeyAge" must be between ${MINIMUM_SIGNING_KEY_AGE} and ${MAXIMUM_SIGNING_KEY_AGE}. Got "${age}".`);
if (!(opts.publicKeyStore instanceof AbstractPublicKeyStore)) {
throw new TypeError('"opts.publicKeyStore" should be an implementation of AbstractPublicKeyStore');
}
this._signingKeyAge = age;
}
this._publicKeyStore = opts.publicKeyStore;
if (typeof opts.signingKeyOverlap !== 'undefined') {
const overlap = opts.signingKeyOverlap;
if (typeof overlap !== 'number' || overlap !== Math.round(overlap)) {
throw new TypeError(`"opts.signingKeyOverlap" should be a integer. Got "${overlap}" (${typeof overlap}).`);
switch (opts.signingKeyType) {
case SIGNING_KEY_TYPE_RSA: {
this.keygen = rsaKeygen.bind(null, rsaKeygen.normalize(opts.rsa));
break;
}
case SIGNING_KEY_TYPE_EC: {
this.keygen = ecKeygen.bind(null, ecKeygen.normalize(opts.ec));
break;
}
default: {
throw new Error(`signingKeyType must be one of: "${SIGNING_KEY_TYPE_RSA}", "${SIGNING_KEY_TYPE_EC}"`);
}
}
}
if (overlap < MINIMUM_SIGNING_KEY_OVERLAP) {
throw new Error(`"opts.signingKeyOverlap" must be at least ${MINIMUM_SIGNING_KEY_OVERLAP}. Got "${overlap}".`);
}
async generateNewKey(exp = clock() + DEFAULT_SIGNING_KEY_AGE) {
const key = await this.keygen(uuid());
if (this._signingKeyAge < overlap) {
throw new Error(`"opts.signingKeyOverlap" must be less than "opts.signingKeyAge" (${this._signingKeyAge}). Got "${overlap}".`);
}
key.jwk.exp = exp + EXPIRY_CLOCK_SKEW;
this._signingKeyOverlap = overlap;
}
await this._publicKeyStore.storePublicKey(key.jwk);
switch (opts.signingKeyType) {
case SIGNING_KEY_TYPE_RSA: {
this.keygen = rsaKeygen.bind(null, rsaKeygen.normalize(opts.rsa));
break;
}
case SIGNING_KEY_TYPE_EC: {
this.keygen = ecKeygen.bind(null, ecKeygen.normalize(opts.ec));
break;
}
default: {
throw new Error(`signingKeyType must be one of: "${SIGNING_KEY_TYPE_RSA}", "${SIGNING_KEY_TYPE_EC}"`);
}
return key.signingKey;
}
}
function CoreKeyGenerator(opts) {
this._signingKeyAge = DEFAULT_SIGNING_KEY_AGE;
this._signingKeyOverlap = DEFAULT_SIGNING_KEY_OVERLAP;
this._publicKeyStore = null;
parseOpts.call(this, opts);
}
CoreKeyGenerator.prototype.generateNewKeys = function generateNewKeys() {
return this
.keygen(uuid())
.then(key => {
key.jwk.exp = clock() + this._signingKeyAge + this._signingKeyOverlap + EXPIRY_CLOCK_SKEW;
return this
._publicKeyStore
.storePublicKey(key.jwk)
.then(() => key.signingKey);
});
};
module.exports = CoreKeyGenerator;
'use strict';
const jwkToPem = require('jwk-to-pem');
const generate = require('native-crypto/generate');
const { generateKeyPair, createPublicKey } = require('crypto');
const keyto = require('@trust/keyto');
const DEFAULT_CRV = 'P-256';

@@ -14,25 +15,41 @@

const SUPPORTS_KEY_OBJECTS = typeof createPublicKey === 'function';
const PUBLIC_EXPORT_OPTIONS = {
type: 'spki',
format: 'pem'
};
const PRIVATE_EXPORT_OPTIONS = SUPPORTS_KEY_OBJECTS
? null
: {
type: 'pkcs8',
format: 'pem'
};
function keygen(opts, kid) {
return generate(opts.crv)
.then(keypair => {
return Promise
.all([
keypair.publicKey,
jwkToPem(keypair.privateKey, { private: true })
]);
})
return new Promise((resolve, reject) => {
generateKeyPair('ec', {
namedCurve: opts.crv,
publicKeyEncoding: PUBLIC_EXPORT_OPTIONS,
privateKeyEncoding: PRIVATE_EXPORT_OPTIONS
}, (err, publicKey, privateKey) => {
if (err) {
return reject(err);
}
resolve([
publicKey,
privateKey
]);
});
})
.then(res => {
return {
jwk: {
jwk: Object.assign(keyto.from(res[0], 'pem').toJwk('public'), {
kid,
kty: res[0].kty,
crv: res[0].crv,
x: res[0].x,
y: res[0].y,
alg: CRV_TO_ALG[opts.crv],
use: 'sig'
},
}),
signingKey: {
kid,
pem: res[1],
key: res[1],
alg: CRV_TO_ALG[opts.crv]

@@ -39,0 +56,0 @@ }

'use strict';
const EventEmitter = require('events');
const CoreKeyGenerator = require('./core-key-generator');
const clock = require('./clock');
function KeyGenerator(opts) {
if (typeof opts !== 'object') {
throw new TypeError(`"opts" should be an Object. Got "${typeof opts}".`);
}
this._coreKeyGenerator = new CoreKeyGenerator(opts);
this._keyGenerationTask = null;
this._currentPrivateKey = null;
this._generateNewKeys = this._generateNewKeys.bind(this);
this._generateNewKeys();
setInterval(this._generateNewKeys, this._coreKeyGenerator._signingKeyAge * 1000);
const delay = ms => new Promise(resolve => setTimeout(resolve, ms).unref());
function createBackoff() {
let current = 100;
const max = current * (2 ** 8);
return () => {
const tmp = current;
if (current < max) {
current *= 2;
}
return delay(tmp);
};
}
KeyGenerator.prototype._generateNewKeys = function _generateNewKeys() {
this._keyGenerationTask = this
._coreKeyGenerator.generateNewKeys()
.then(privateKey => {
this._currentPrivateKey = privateKey;
this._keyGenerationTask = undefined;
return privateKey;
})
.catch(() => {
return new Promise(resolve => setTimeout(resolve, 100).unref())
.then(this._generateNewKeys);
});
const DEFAULT_SIGNING_KEY_USE = 60 * 60;
const MINIMUM_SIGNING_KEY_USE = 60 * 60;
const MAXIMUM_SIGNING_KEY_USE = 23 * 60 * 60;
return this._keyGenerationTask;
};
const DEFAULT_MAX_TOKEN_LIFETIME = 5 * 60;
const MINIMUM_MAX_TOKEN_LIFETIME = 5 * 60;
KeyGenerator.prototype.getCurrentPrivateKey = function getCurrentPrivateKey() {
return this._keyGenerationTask
? this._keyGenerationTask
: Promise.resolve(this._currentPrivateKey);
};
class KeyGenerator extends EventEmitter {
constructor(opts) {
if (typeof opts !== 'object') {
throw new TypeError(`"opts" should be an Object. Got "${typeof opts}".`);
}
super();
this._core = new CoreKeyGenerator(opts);
this._keyUseLifetime = DEFAULT_SIGNING_KEY_USE;
this._tokenLifetime = DEFAULT_MAX_TOKEN_LIFETIME;
if (typeof opts.lifetimes !== 'undefined') {
const lifetimes = opts.lifetimes;
if (typeof lifetimes !== 'object') {
throw new TypeError(`"opts.lifetimes" should be an object. Got "${lifetimes}" (${typeof lifetimes}).`);
}
if (typeof lifetimes.keyUse !== 'undefined') {
const keyUse = opts.keyUse;
if (typeof keyUse !== 'number' || keyUse !== Math.round(keyUse)) {
throw new TypeError(`"opts.lifetimes.keyUse" should be an integer. Got "${keyUse}" (${typeof keyUse}).`);
}
if (keyUse < MINIMUM_SIGNING_KEY_USE || MAXIMUM_SIGNING_KEY_USE < keyUse) {
throw new Error(`"opts.lifetimes.keyUse" must be between ${MINIMUM_SIGNING_KEY_USE} and ${MAXIMUM_SIGNING_KEY_USE}. Got "${keyUse}".`);
}
this._keyUseLifetime = keyUse;
}
if (typeof lifetimes.token !== 'undefined') {
const token = lifetimes.token;
if (typeof token !== 'number' || token !== Math.round(token)) {
throw new TypeError(`"opts.lifetimes.token" should be an integer. Got "${token}" (${typeof token}).`);
}
if (token < MINIMUM_MAX_TOKEN_LIFETIME) {
throw new Error(`"opts.lifetimes.token" must be at least ${MINIMUM_MAX_TOKEN_LIFETIME}. Got "${token}".`);
}
this._tokenLifetime = token;
}
}
this._current = null;
this._next = this._ensureGenerated(this._keyUseLifetime);
this._rotate();
}
async _rotate() {
this._current = this._next;
const { keyUseExpiry } = await this._current;
this._next = this._ensureGenerated(this._keyUseLifetime * 2);
await this._next;
setTimeout(() => this._rotate(), (keyUseExpiry - clock()) * 1000).unref();
}
async _ensureGenerated(keyUseLifetime) {
const backoff = createBackoff();
for (;;) {
try {
const exp = clock() + keyUseLifetime + this._tokenLifetime;
const keyUseExpiry = exp - this._tokenLifetime;
const key = await this._core.generateNewKey(exp);
return { key, keyUseExpiry };
} catch (err) {
this.emit('error', err);
await backoff();
}
}
}
async getCurrentPrivateKey() {
const { key, keyUseExpiry } = await this._current;
if (clock() > keyUseExpiry) {
throw new Error('Failed to generate a key before it expired');
}
return key;
}
}
module.exports = KeyGenerator;
'use strict';
const jwkToPem = require('jwk-to-pem');
const generate = require('native-crypto/generate');
const { generateKeyPair, createPublicKey } = require('crypto');
const keyto = require('@trust/keyto');
const DEFAULT_SIZE = 2048;
const MINIMUM_SIZE = 2048;
const SUPPORTS_KEY_OBJECTS = typeof createPublicKey === 'function';
const PUBLIC_EXPORT_OPTIONS = {
type: 'spki',
format: 'pem'
};
const PRIVATE_EXPORT_OPTIONS = SUPPORTS_KEY_OBJECTS
? null
: {
type: 'pkcs8',
format: 'pem'
};
function keygen(opts, kid) {
return generate('RS256', opts.size)
.then(keypair => {
return Promise
.all([
keypair.publicKey,
jwkToPem(keypair.privateKey, { private: true })
]);
})
return new Promise((resolve, reject) => {
generateKeyPair('rsa', {
modulusLength: opts.size,
publicKeyEncoding: PUBLIC_EXPORT_OPTIONS,
privateKeyEncoding: PRIVATE_EXPORT_OPTIONS
}, (err, publicKey, privateKey) => {
if (err) {
return reject(err);
}
resolve([
publicKey,
privateKey
]);
});
})
.then(res => {
return {
jwk: {
jwk: Object.assign(keyto.from(res[0], 'pem').toJwk('public'), {
kid,
kty: res[0].kty,
n: res[0].n,
e: res[0].e,
alg: 'RS256',
use: 'sig'
},
}),
signingKey: {
kid,
pem: res[1],
key: res[1],
alg: 'RS256'

@@ -32,0 +50,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc