pax_global_header00006660000000000000000000000064134644405550014524gustar00rootroot0000000000000052 comment=8c1174d6aab9e2284a5305ab4b7a417984e65dda keygrip-1.1.0/000077500000000000000000000000001346444055500131755ustar00rootroot00000000000000keygrip-1.1.0/.gitignore000066400000000000000000000001051346444055500151610ustar00rootroot00000000000000.nyc_output/ coverage/ node_modules/ npm-debug.log package-lock.json keygrip-1.1.0/.travis.yml000066400000000000000000000054211346444055500153100ustar00rootroot00000000000000language: node_js node_js: - "0.6" - "0.8" - "0.10" - "0.12" - "1.8" - "2.5" - "3.3" - "4.9" - "5.12" - "6.17" - "7.10" - "8.15" - "9.11" - "10.15" - "11.12" - "12.2" sudo: false dist: trusty env: global: # Suppress Node.js 0.6 compile warnings - "CXXCOM='$CXX -o $TARGET -c $CXXFLAGS $CCFLAGS -Wno-unused-local-typedefs -Wno-maybe-uninitialized -Wno-narrowing -Wno-strict-overflow $_CCCOMCOM $SOURCES'" cache: directories: - node_modules before_install: - | # Setup utility functions function node_version_lt () { [[ "$(v "$TRAVIS_NODE_VERSION")" -lt "$(v "${1}")" ]] } function npm_module_installed () { npm -lsp ls | grep -Fq "$(pwd)/node_modules/${1}:${1}@" } function npm_remove_module_re () { node -e ' fs = require("fs"); p = JSON.parse(fs.readFileSync("package.json", "utf8")); r = RegExp(process.argv[1]); for (k in p.devDependencies) { if (r.test(k)) delete p.devDependencies[k]; } fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); ' "$@" } function npm_use_module () { node -e ' fs = require("fs"); p = JSON.parse(fs.readFileSync("package.json", "utf8")); p.devDependencies[process.argv[1]] = process.argv[2]; fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); ' "$@" } function v () { tr '.' '\n' <<< "${1}" \ | awk '{ printf "%03d", $0 }' \ | sed 's/^0*//' } # Configure npm - | # Skip updating shrinkwrap / lock npm config set shrinkwrap false # Setup Node.js version-specific dependencies - | # Configure mocha for testing if node_version_lt '0.8' ; then npm_use_module 'mocha' '1.21.5' elif node_version_lt '0.10'; then npm_use_module 'mocha' '2.5.3' elif node_version_lt '4.0' ; then npm_use_module 'mocha' '3.5.3' elif node_version_lt '6.0' ; then npm_use_module 'mocha' '5.2.0' fi - | # Configure nyc for testing if node_version_lt '0.10'; then npm_remove_module_re '^nyc$' elif node_version_lt '4.0' ; then npm_use_module 'nyc' '10.3.2' elif node_version_lt '6.0' ; then npm_use_module 'nyc' '11.9.0' fi # Update Node.js modules - | # Prune & rebuild node_modules if [[ -d node_modules ]]; then npm prune npm rebuild fi before_scrpt: - | # Contents of node_modules npm -s ls ||: script: - | # Run test script, depending on nyc install if npm_module_installed 'nyc'; then npm run-script test-travis else npm test fi after_script: - | # Upload coverage to coveralls if exists if [[ -d .nyc_output ]]; then npm install --save-dev coveralls@2 nyc report --reporter=text-lcov | coveralls fi keygrip-1.1.0/HISTORY.md000066400000000000000000000007031346444055500146600ustar00rootroot000000000000001.1.0 / 2019-05-07 ================== * Use `tsscmp` module for timing-safe signature verification 1.0.3 / 2018-09-12 ================== * perf: enable strict mode 1.0.2 / 2017-08-26 ================== * perf: improve comparison speed 1.0.1 / 2014-05-07 ================== * Readme changes * Update repository for organization move 1.0.0 / 2013-12-21 ================== * Remove default key generation and associated expectations keygrip-1.1.0/LICENSE000066400000000000000000000021421346444055500142010ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2011-2014 Jed Schmidt (http://jedschmidt.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. keygrip-1.1.0/README.md000066400000000000000000000100751346444055500144570ustar00rootroot00000000000000# keygrip [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-version-image]][node-version-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] Keygrip is a [node.js](http://nodejs.org/) module for signing and verifying data (such as cookies or URLs) through a rotating credential system, in which new server keys can be added and old ones removed regularly, without invalidating client credentials. ## Install $ npm install keygrip ## API ### keys = new Keygrip([keylist], [hmacAlgorithm], [encoding]) This creates a new Keygrip based on the provided keylist, an array of secret keys used for SHA1 HMAC digests. `keylist` is obligatory. `hmacAlgorithm` defaults to `'sha1'` and `encoding` defaults to `'base64'`. Note that the `new` operator is also optional, so all of the following will work when `Keygrip = require("keygrip")`: ```javascript keys = new Keygrip(["SEKRIT2", "SEKRIT1"]) keys = Keygrip(["SEKRIT2", "SEKRIT1"]) keys = require("keygrip")() keys = Keygrip(["SEKRIT2", "SEKRIT1"], 'sha256', 'hex') keys = Keygrip(["SEKRIT2", "SEKRIT1"], 'sha256') keys = Keygrip(["SEKRIT2", "SEKRIT1"], undefined, 'hex') ``` The keylist is an array of all valid keys for signing, in descending order of freshness; new keys should be `unshift`ed into the array and old keys should be `pop`ped. The tradeoff here is that adding more keys to the keylist allows for more granular freshness for key validation, at the cost of a more expensive worst-case scenario for old or invalid hashes. Keygrip keeps a reference to this array to automatically reflect any changes. This reference is stored using a closure to prevent external access. ### keys.sign(data) This creates a SHA1 HMAC based on the _first_ key in the keylist, and outputs it as a 27-byte url-safe base64 digest (base64 without padding, replacing `+` with `-` and `/` with `_`). ### keys.index(data, digest) This loops through all of the keys currently in the keylist until the digest of the current key matches the given digest, at which point the current index is returned. If no key is matched, `-1` is returned. The idea is that if the index returned is greater than `0`, the data should be re-signed to prevent premature credential invalidation, and enable better performance for subsequent challenges. ### keys.verify(data, digest) This uses `index` to return `true` if the digest matches any existing keys, and `false` otherwise. ## Example ```javascript // ./test.js var assert = require("assert") , Keygrip = require("keygrip") , keylist, keys, hash, index // but we're going to use our list. // (note that the 'new' operator is optional) keylist = ["SEKRIT3", "SEKRIT2", "SEKRIT1"] keys = Keygrip(keylist) // .sign returns the hash for the first key // all hashes are SHA1 HMACs in url-safe base64 hash = keys.sign("bieberschnitzel") assert.ok(/^[\w\-]{27}$/.test(hash)) // .index returns the index of the first matching key index = keys.index("bieberschnitzel", hash) assert.equal(index, 0) // .verify returns the a boolean indicating a matched key matched = keys.verify("bieberschnitzel", hash) assert.ok(matched) index = keys.index("bieberschnitzel", "o_O") assert.equal(index, -1) // rotate a new key in, and an old key out keylist.unshift("SEKRIT4") keylist.pop() // if index > 0, it's time to re-sign index = keys.index("bieberschnitzel", hash) assert.equal(index, 1) hash = keys.sign("bieberschnitzel") ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/keygrip.svg [npm-url]: https://npmjs.org/package/keygrip [travis-image]: https://img.shields.io/travis/crypto-utils/keygrip/master.svg [travis-url]: https://travis-ci.org/crypto-utils/keygrip [coveralls-image]: https://img.shields.io/coveralls/crypto-utils/keygrip/master.svg [coveralls-url]: https://coveralls.io/r/crypto-utils/keygrip [downloads-image]: https://img.shields.io/npm/dm/keygrip.svg [downloads-url]: https://npmjs.org/package/keygrip [node-version-image]: https://img.shields.io/node/v/keygrip.svg [node-version-url]: https://nodejs.org/en/download/ keygrip-1.1.0/index.js000066400000000000000000000022061346444055500146420ustar00rootroot00000000000000/*! * keygrip * Copyright(c) 2011-2014 Jed Schmidt * MIT Licensed */ 'use strict' var compare = require('tsscmp') var crypto = require("crypto") function Keygrip(keys, algorithm, encoding) { if (!algorithm) algorithm = "sha1"; if (!encoding) encoding = "base64"; if (!(this instanceof Keygrip)) return new Keygrip(keys, algorithm, encoding) if (!keys || !(0 in keys)) { throw new Error("Keys must be provided.") } function sign(data, key) { return crypto .createHmac(algorithm, key) .update(data).digest(encoding) .replace(/\/|\+|=/g, function(x) { return ({ "/": "_", "+": "-", "=": "" })[x] }) } this.sign = function(data){ return sign(data, keys[0]) } this.verify = function(data, digest) { return this.index(data, digest) > -1 } this.index = function(data, digest) { for (var i = 0, l = keys.length; i < l; i++) { if (compare(digest, sign(data, keys[i]))) { return i } } return -1 } } Keygrip.sign = Keygrip.verify = Keygrip.index = function() { throw new Error("Usage: require('keygrip')()") } module.exports = Keygrip keygrip-1.1.0/package.json000066400000000000000000000011251346444055500154620ustar00rootroot00000000000000{ "name": "keygrip", "version": "1.1.0", "description": "Key signing and verification for rotated credentials", "license": "MIT", "repository": "crypto-utils/keygrip", "dependencies": { "tsscmp": "1.0.6" }, "devDependencies": { "mocha": "6.1.4", "nyc": "14.0.0" }, "files": [ "HISTORY.md", "LICENSE", "README.md", "index.js" ], "engines": { "node": ">= 0.6" }, "scripts": { "test": "mocha --reporter spec test/", "test-cov": "nyc --reporter=html --reporter=text npm test", "test-travis": "nyc --reporter=text npm test" } } keygrip-1.1.0/test/000077500000000000000000000000001346444055500141545ustar00rootroot00000000000000keygrip-1.1.0/test/keygrip.js000066400000000000000000000116641346444055500161740ustar00rootroot00000000000000"use strict"; var assert = require("assert") var Keygrip = require('..') describe('Keygrip', function () { describe('constructor', function () { it('should construct new instance', function () { var keys = new Keygrip(['SEKRIT1']) assert.ok(keys) assert.ok(keys instanceof Keygrip) }) it('should work without new keyword', function () { var keys = Keygrip(['SEKRIT1']) assert.ok(keys) assert.ok(keys instanceof Keygrip) }) }) describe('"keys" argument', function () { describe('when undefined', function () { it('should throw an Error', function () { var keys assert.throws(function () { keys = new Keygrip() }, /Keys must be provided/) assert.ok(!keys) }) }) describe('when empty array', function () { it('should throw an Error', function () { var keys assert.throws(function () { keys = new Keygrip([]) }, /Keys must be provided/) assert.ok(!keys) }) }) describe('when array of strings', function () { it('should construct object', function () { var keys = new Keygrip(['SEKRIT1']) assert.ok(keys) assert.ok(keys instanceof Keygrip) }) }) }) describe('.index(data)', function () { it('should return key index that signed data', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var data = 'Keyboard Cat has a hat.' assert.strictEqual(keys.index(data, '_jl9qXYgk5AgBiKFOPYK073FMEQ'), 0) assert.strictEqual(keys.index(data, '34Sr3OIsheUYWKL5_w--zJsdSNk'), 1) }) it('should return -1 when no key matches', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var data = 'Keyboard Cat has a hat.' assert.strictEqual(keys.index(data, 'xmM8HQl2eBtPP9nmZ7BK_wpqoxQ'), -1) }) describe('with "algorithm"', function () { it('should return key index using algorithm', function () { var keys = new Keygrip(['SEKRIT1'], 'sha256') var data = 'Keyboard Cat has a hat.' assert.strictEqual(keys.index(data, 'pu97aPRZRLKi3-eANtIlTG_CwSc39mAcIZ1c6FxsGCk'), 0) }) }) describe('with "encoding"', function () { it('should return key index using encoding', function () { var keys = new Keygrip(['SEKRIT1'], null, 'hex') var data = 'Keyboard Cat has a hat.' assert.strictEqual(keys.index(data, 'df84abdce22c85e51858a2f9ff0fbecc9b1d48d9'), 0) }) }) }) describe('.sign(data)', function () { it('should sign a string', function () { var keys = new Keygrip(['SEKRIT1']) var hash = keys.sign('Keyboard Cat has a hat.') assert.strictEqual(hash, '34Sr3OIsheUYWKL5_w--zJsdSNk') }) it('should sign with first secret', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var hash = keys.sign('Keyboard Cat has a hat.') assert.strictEqual(hash, '_jl9qXYgk5AgBiKFOPYK073FMEQ') }) describe('with "algorithm"', function () { it('should return signature using algorithm', function () { var keys = new Keygrip(['SEKRIT1'], 'sha256') var hash = keys.sign('Keyboard Cat has a hat.') assert.strictEqual(hash, 'pu97aPRZRLKi3-eANtIlTG_CwSc39mAcIZ1c6FxsGCk') }) }) describe('with "encoding"', function () { it('should return signature in encoding', function () { var keys = new Keygrip(['SEKRIT1'], null, 'hex') var hash = keys.sign('Keyboard Cat has a hat.') assert.strictEqual(hash, 'df84abdce22c85e51858a2f9ff0fbecc9b1d48d9') }) }) }) describe('.verify(data)', function () { it('should validate against any key', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var data = 'Keyboard Cat has a hat.' assert.ok(keys.verify(data, '_jl9qXYgk5AgBiKFOPYK073FMEQ')) assert.ok(keys.verify(data, '34Sr3OIsheUYWKL5_w--zJsdSNk')) }) it('should fail with bogus data', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var data = 'Keyboard Cat has a hat.' assert.ok(!keys.verify(data, 'bogus data')) }) it('should fail when key not present', function () { var keys = new Keygrip(['SEKRIT2', 'SEKRIT1']) var data = 'Keyboard Cat has a hat.' assert.ok(!keys.verify(data, 'xmM8HQl2eBtPP9nmZ7BK_wpqoxQ')) }) describe('with "algorithm"', function () { it('should validate using algorithm', function () { var keys = new Keygrip(['SEKRIT1'], 'sha256') var data = 'Keyboard Cat has a hat.' assert.ok(keys.verify(data, 'pu97aPRZRLKi3-eANtIlTG_CwSc39mAcIZ1c6FxsGCk')) }) }) describe('with "encoding"', function () { it('should validate using encoding', function () { var keys = new Keygrip(['SEKRIT1'], null, 'hex') var data = 'Keyboard Cat has a hat.' assert.ok(keys.verify(data, 'df84abdce22c85e51858a2f9ff0fbecc9b1d48d9')) }) }) }) })