Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Apr 30, 2019
1 parent 28d6b5d commit 1ee06c7
Show file tree
Hide file tree
Showing 18 changed files with 2,740 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
**/node_modules
**/package-lock.json

coverage.*

**/.DS_Store
**/._*

**/*.pem

**/.vs
**/.vscode
**/.idea
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
language: node_js

node_js:
- "8"
- "10"
- "12"
- "node"

sudo: false

install:
- "npm install"
- "npm install hapi@$HAPI_VERSION"

env:
- HAPI_VERSION="17"
- HAPI_VERSION="18"

os:
- "linux"
- "osx"
- "windows"
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/jwt/issues?q=is%3Aissue+label%3A%22release+notes%22).

If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/jwt/milestones?state=closed&direction=asc&sort=due_date).
5 changes: 5 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
COMMERCIAL LICENSE

Copyright (c) 2019 Sideway Inc.

This package requires a commercial license. You may not use, copy, or distribute it without first acquiring a commercial license from Sideway Inc. Using this software without a license is a violation of US and international law. To obtain a license, please contact [[email protected]](mailto:[email protected]).
19 changes: 19 additions & 0 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,22 @@
JWT (JSON Web Token) Authentication.

[![Build Status](https://travis-ci.org/hapijs/jwt.svg?branch=master)](https://travis-ci.org/hapijs/jwt)

## License

This package requires a commercial license. You may not use, copy, or distribute it without first
acquiring a commercial license from Sideway Inc. Using this software without a license is a
violation of US and international law. To obtain a license, please contact [[email protected]](mailto:[email protected]).

## Acknowledgements

Portions of this module were adapted from:

- [node-jwa](https://github.com/brianloveswords/node-jwa), copyright (c) 2013 Brian J. Brennan, MIT License
- [node-jws](https://github.com/brianloveswords/node-jws), copyright (c) 2013 Brian J. Brennan, MIT License
- [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken), copyright (c) 2015 Auth0, Inc., MIT License
- [node-jwks-rsa](https://github.com/auth0/node-jwks-rsa), copyright (c) 2016 Sandrino Di Mattia, MIT License
- [hapi-auth-jwt2](https://github.com/dwyl/hapi-auth-jwt2), copyright (c) 2015-2016, dwyl ltd, ISC License
- [mock-jwks](https://github.com/Levino/mock-jwks), copyright (c) 2018-2019 Levin Keller, MIT License
- [Stack Overflow answer](http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js)
- [node-rsa-pem-from-mod-exp](https://github.com/tracker1/node-rsa-pem-from-mod-exp), copyright (c) 2014 Michael J. Ryan, MIT License
218 changes: 218 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
'use strict';

const Crypto = require('crypto');

const EcdsaSigFormatter = require('ecdsa-sig-formatter');

const Utils = require('./utils');


const internals = {
algorithms: {
RS256: 'RSA-SHA256',
RS384: 'RSA-SHA384',
RS512: 'RSA-SHA512',

PS256: 'RSA-SHA256',
PS384: 'RSA-SHA384',
PS512: 'RSA-SHA512',

ES256: 'RSA-SHA256',
ES384: 'RSA-SHA384',
ES512: 'RSA-SHA512',

HS256: 'sha256',
HS384: 'sha384',
HS512: 'sha512'
}
};


exports.generate = function (value, algorithm, key) {

algorithm = algorithm.toUpperCase();

if (algorithm === 'NONE') {
return '';
}

const algo = internals.algorithms[algorithm];
if (!algo) {
throw new Error('Unsupported algorithm');
}

switch (algorithm) {
case 'RS256':
case 'RS384':
case 'RS512': {

const signer = Crypto.createSign(algo);
signer.update(value);
const sig = signer.sign(key, 'base64');
return internals.b64urlEncode(sig);
}

case 'PS256':
case 'PS384':
case 'PS512': {

const signer = Crypto.createSign(algo);
signer.update(value);
const sig = signer.sign({ key, padding: Crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: Crypto.constants.RSA_PSS_SALTLEN_DIGEST }, 'base64');
return internals.b64urlEncode(sig);
}

case 'ES256':
case 'ES384':
case 'ES512': {

const signer = Crypto.createSign(algo);
signer.update(value);
const sig = signer.sign(key, 'base64');
return EcdsaSigFormatter.derToJose(sig, algorithm);
}

case 'HS256':
case 'HS384':
case 'HS512': {

const hmac = Crypto.createHmac(algo, key);
hmac.update(value);
const digest = hmac.digest('base64');
return internals.b64urlEncode(digest);
}
}
};


exports.verify = function (raw, algorithm, key) {

algorithm = algorithm.toUpperCase();

if (algorithm === 'NONE') {
return raw.signature === '';
}

const algo = internals.algorithms[algorithm];
if (!algo) {
throw new Error('Unsupported algorithm');
}

const value = `${raw.header}.${raw.payload}`;
const signature = raw.signature;

switch (algorithm) {
case 'RS256':
case 'RS384':
case 'RS512': {

const verifier = Crypto.createVerify(algo);
verifier.update(value);
return verifier.verify(key, internals.b64urlDecode(signature), 'base64');
}

case 'PS256':
case 'PS384':
case 'PS512': {

const verifier = Crypto.createVerify(algo);
verifier.update(value);
return verifier.verify({ key, padding: Crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: Crypto.constants.RSA_PSS_SALTLEN_DIGEST }, internals.b64urlDecode(signature), 'base64');
}

case 'ES256':
case 'ES384':
case 'ES512': {

const sig = EcdsaSigFormatter.joseToDer(signature, algorithm).toString('base64');
const verifier = Crypto.createVerify(algo);
verifier.update(value);
return verifier.verify(key, internals.b64urlDecode(sig), 'base64');
}

case 'HS256':
case 'HS384':
case 'HS512': {

const compare = exports.generate(value, algorithm, key);
return Crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(compare));
}
}
};


internals.b64urlEncode = function (b64) {

return b64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
};


internals.b64urlDecode = function (b64url) {

b64url = b64url.toString();

const padding = 4 - b64url.length % 4;
if (padding !== 4) {
for (let i = 0; i < padding; ++i) {
b64url += '=';
}
}

return b64url.replace(/\-/g, '+').replace(/_/g, '/');
};


exports.certToPEM = function (cert) {

return `-----BEGIN CERTIFICATE-----\n${internals.chop(cert)}\n-----END CERTIFICATE-----\n`;
};


exports.rsaPublicKeyToPEM = function (modulusB64, exponentB64) {

const modulusHex = internals.prepadSigned(Buffer.from(modulusB64, 'base64').toString('hex'));
const exponentHex = internals.prepadSigned(Buffer.from(exponentB64, 'base64').toString('hex'));

const modlen = modulusHex.length / 2;
const explen = exponentHex.length / 2;

const encodedModlen = internals.encodeLengthHex(modlen);
const encodedExplen = internals.encodeLengthHex(explen);

const encodedPubkey = '30' +
internals.encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
'02' + encodedModlen + modulusHex +
'02' + encodedExplen + exponentHex;

const der = internals.chop(Buffer.from(encodedPubkey, 'hex').toString('base64'));
return `-----BEGIN RSA PUBLIC KEY-----\n${der}\n-----END RSA PUBLIC KEY-----\n`;
};


internals.prepadSigned = function (hexStr) {

const msb = hexStr[0];
if (msb > '7') {
return `00${hexStr}`;
}

return hexStr;
};


internals.encodeLengthHex = function (n) {

if (n <= 127) {
return Utils.toHex(n);
}

const nHex = Utils.toHex(n);
const lengthOfLengthByte = 128 + nHex.length / 2;
return Utils.toHex(lengthOfLengthByte) + nHex;
};


internals.chop = function (cert) {

return cert.match(/.{1,64}/g).join('\n');
};
32 changes: 32 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const Crypto = require('./crypto');
const Plugin = require('./plugin');
const Token = require('./token');
const Utils = require('./utils');


const internals = {};


module.exports = {
plugin: Plugin.plugin,

token: {
generate: Token.generate,
decode: Token.decode,
verify: Token.verify,
verifyTime: Token.verifyTime,

signature: {
generate: Crypto.generate,
verify: Crypto.verify
}
},

crypto: {
rsaPublicKeyToPEM: Crypto.rsaPublicKeyToPEM
},

utils: Utils
};
Loading

0 comments on commit 1ee06c7

Please sign in to comment.