argon2-pass
Advanced tools
Comparing version 0.1.3 to 0.2.0
# Changelog | ||
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
## 0.1.3 | ||
## [0.2.0] - 2018-09-08 | ||
### Added | ||
- Added JSDoc comments to all functions and important objects. This was then used with the TypeDoc module to generate a documentation site for the package. | ||
- Added synchronous versions of hash and verify. `hashPasswordSync()` and `verifyHashSync()`. | ||
- Added get and set functions for MemLimit. `MemLimit()` and `MemLimit(newValue)`. | ||
- Added get and set functions for OpsLimit. `OpsLimit()` and `OpsLimit(newValue)`. | ||
### Changed | ||
- `static readonly` configuration default values are now set to their value manually. Previously they were just "re-exports" of sodium-native's constants. | ||
### Removed | ||
- **Possibly Breaking Change:** Removed `getMemLimit()` and `getOpsLimit()` functions, replaced with getters and setters as detailed above. | ||
## [0.1.3] - 2018-09-06 | ||
### Added | ||
- Added internal functions to convert buffers to url-safe base64 and back again. | ||
@@ -15,3 +30,3 @@ - Added functions to generate and verify one time authentication buffers. Generates a mac from a supplied message using a random key. | ||
## 0.1.2 - 2018-09-06 | ||
## [0.1.2] - 2018-09-06 | ||
### Fixed | ||
@@ -21,3 +36,3 @@ | ||
## 0.1.1 - 2018-09-06 | ||
## [0.1.1] - 2018-09-06 | ||
### Fixed | ||
@@ -32,7 +47,11 @@ | ||
- Added get functions for the currently configured memory limit and operations limit. | ||
- Added a custom error class <code>SecurePassOptionsError</code> that is thrown if an error occurs during options validation. | ||
- Added <code>hashPassword()</code> function, the function takes a password in as a buffer and provides the hashed output. The function can work with any of the following return methods; async/await, promise or callback. | ||
- Added <code>VerificationResult</code> enumeration to serve as the response to the hash verification function. | ||
- Added <code>verifyHash()</code> function, the function takes a password and a hash as buffers and provides a <code>VerificationResult</code> as an output. The function can work with any of the following return methods; async/await, promise or callback. | ||
- Added a custom error class `SecurePassOptionsError` that is thrown if an error occurs during options validation. | ||
- Added `hashPassword()` function, the function takes a password in as a buffer and provides the hashed output. The function can work with any of the following return methods; async/await, promise or callback. | ||
- Added `VerificationResult` enumeration to serve as the response to the hash verification function. | ||
- Added `verifyHash()` function, the function takes a password and a hash as buffers and provides a `VerificationResult` as an output. The function can work with any of the following return methods; async/await, promise or callback. | ||
<!-- Links --> | ||
[Unreleased]: https://github.com/DrBarnabus/secure-pass/compare/v0.1.3...HEAD | ||
[0.1.3]: https://github.com/DrBarnabus/secure-pass/compare/v0.1.2...v0.1.3 | ||
[0.1.2]: https://github.com/DrBarnabus/secure-pass/compare/v0.1.1...v0.1.2 | ||
[0.1.1]: https://github.com/DrBarnabus/secure-pass/compare/v0.1.0...v0.1.1 |
{ | ||
"name": "argon2-pass", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "State of the art password hashing and one time password reset token generation module written in TypeScript for nodejs.", | ||
@@ -23,2 +23,4 @@ "main": "./dist/index.js", | ||
"tslint": "^5.11.0", | ||
"typedoc": "^0.12.0", | ||
"typedoc-plugin-external-module-name": "^1.1.3", | ||
"typescript": "^3.0.3" | ||
@@ -25,0 +27,0 @@ }, |
@@ -1,2 +0,2 @@ | ||
<p align="center"><img width="60%" src="https://github.com/DrBarnabus/secure-pass/blob/master/media/logo_transparent_background.png"></p> | ||
<p align="center"><a href="https://drbarnabus.github.io/secure-pass/"><img width="60%" src="https://raw.github.com/DrBarnabus/secure-pass/master/media/logo_transparent_background.png"></a></p> | ||
@@ -10,5 +10,6 @@ [![NPM Version][npm-badge]][npm-url] | ||
[![Known Vulnerabilities][snyk-badge]][snyk-url] | ||
[![Code Quality][codacy-badge]][codacy-url] | ||
# Introduction | ||
SecurePass (argon2-pass) is a module for the creation of hashes from passwords, allowing you to store passwords securely. The module also provides a facility for the generation and verification of one time use password reset tokens for use in your own password reset flows. This module is a wrapper for [libsodium]'s implementation of the [Argon2ID] password hashing algorithm. | ||
SecurePass (argon2-pass) is a module for the creation of hashes from passwords, allowing you to store passwords securely. The module also provides a facility for the generation and verification of one time authentication tokens for use in your own password reset flows. This module is a wrapper for [libsodium]'s implementation of the [Argon2ID] password hashing algorithm and Poly1305. | ||
@@ -18,4 +19,6 @@ # Features | ||
- Uses the state of the art, secure modern password hashing algorithm [Argon2ID]. | ||
- Uses <code>Buffer</code>'s for safer memory management. | ||
- Allows for generation of one time use password reset tokens to be used in your own password reset flow. | ||
- Uses Buffer's for safer memory management. | ||
- Uses static functions for basic operations, so you don't have to create a new instance every time. | ||
- asynchronous functions are defined to work with async/await, promises and callbacks. Synchronous versions are also available just in-case you don't want your hashing and verification to be asynchronous. | ||
- Allows for generation of one time use authentication tokens to be used in your own password reset flow. | ||
- Easily configurable work factors, allowing you to increase the security of your hashes over time. | ||
@@ -28,6 +31,11 @@ - Three default difficulty configurations for password hashing, as defined in [libsodium]'s implementation. Allowing you to configure your security level based on some recommended predefined values. | ||
To install the package you need to use a package manager such as npm or yarn. | ||
Install argon2-pass using [`yarn`](https://yarnpkg.com/en/package/argon2-pass): | ||
```bash | ||
yarn add argon2-pass | ||
``` | ||
yarn add argon2-pass | ||
Or via [`npm`](https://www.npmjs.com/package/argon2-pass): | ||
```bash | ||
npm install argon2-pass | ||
@@ -38,14 +46,52 @@ ``` | ||
**Coming Soon** | ||
Basic Usage Information: | ||
```typescript | ||
import { SecurePass, VerificationResult } from 'argon2-pass'; | ||
async function main() { | ||
// Create a new instance of SecurePass. Optional difficulty configurations can be passed in here. | ||
const sp = new SecurePass(); | ||
// Passwords and Hashes are stored as buffers internally. | ||
const password = Buffer.from('SamplePassword'); | ||
const hash = await sp.hashPassword(password); | ||
// Hash Verification returns an enumerator for easy validation of passwords against hashes. | ||
const result = await sp.verifyHash(password, hash); | ||
if (result == VerificationResult.InvalidOrUnrecognised) { | ||
console.log('Hash not created by SecurePass or invalid'); | ||
} else if (result == VerificationResult.Invalid) { | ||
console.log('Password not valid when compared with supplied hash'); | ||
} else if (result == VerificationResult.Valid) { | ||
console.log('Password and Hash are a match'); | ||
} else if (result == VerificationResult.ValidNeedsRehash) { | ||
console.log('Password and Hash are a match, but the security of the hash could be improved by rehashing.'); | ||
} | ||
} | ||
// Call the async function defined above to run the example. | ||
main(); | ||
``` | ||
For full documentation, please refer to the full [documentation site](https://drbarnabus.github.io/secure-pass/globals.html). The documentation was generated automaticaly with [TypeDoc]. | ||
# Testing | ||
This package is configured with [jest] tests, these tests ensure that the module is working correctly and as specified as well as generating code coverage reports. | ||
This package is configured with [jest] tests, these tests ensure that the module is working correctly and as specified as well as generating code coverage reports to ensure every line of code is covered by a unit test. | ||
``` | ||
To run the jest tests manualy run the test script defined in package.json: | ||
```bash | ||
yarn test | ||
or | ||
npm test | ||
``` | ||
This module also has the following automated testing: | ||
- CI Builds on [Travis]. | ||
- Code Coverage Reports on [CodeCov]. | ||
- Dependency Update Checks on [david-dm]. | ||
- Dependency Vulnerabilities Checks on [snyk]. | ||
- Automated Code Review and Quality Report on [codacy]. | ||
# Acknowledgements | ||
@@ -56,3 +102,3 @@ | ||
# Licence | ||
Licensed under [MIT](./LICENSE). | ||
Licensed under [MIT](https://raw.github.com/DrBarnabus/secure-pass/master/LICENSE). | ||
@@ -65,16 +111,25 @@ Copyright (C) 2018 DrBarnabus | ||
[sodium-native]: https://github.com/sodium-friends/sodium-native | ||
[jest]: https://github.com/facebook/jest | ||
[Travis]: https://travis-ci.org/DrBarnabus/secure-pass | ||
[CodeCov]: https://codecov.io/gh/DrBarnabus/secure-pass | ||
[david-dm]: https://david-dm.org/drbarnabus/secure-pass | ||
[snyk]: https://snyk.io/test/github/DrBarnabus/secure-pass?targetFile=package.json | ||
[codacy]: https://www.codacy.com/app/DrBarnabus/secure-pass?utm_source=github.com&utm_medium=referral&utm_content=DrBarnabus/secure-pass&utm_campaign=Badge_Grade | ||
[TypeDoc]: https://github.com/TypeStrong/typedoc | ||
<!-- Badges --> | ||
[npm-badge]: https://img.shields.io/npm/v/argon2-pass.svg?style=flat-square | ||
[npm-badge]: https://img.shields.io/npm/v/argon2-pass.svg | ||
[npm-url]: https://www.npmjs.com/package/argon2-pass | ||
[npmd-badge]: https://img.shields.io/npm/dw/argon2-pass.svg?style=flat-square | ||
[travis-badge]: https://img.shields.io/travis/DrBarnabus/secure-pass/master.svg?style=flat-square | ||
[npmd-badge]: https://img.shields.io/npm/dw/argon2-pass.svg | ||
[travis-badge]: https://travis-ci.org/DrBarnabus/secure-pass.svg?branch=master | ||
[travis-url]: https://travis-ci.org/DrBarnabus/secure-pass | ||
[dependencies-badge]: https://david-dm.org/drbarnabus/argon2-pass.svg?style=flat-square | ||
[codecov-badge]: https://img.shields.io/codecov/c/github/DrBarnabus/secure-pass/master.svg?style=flat-square | ||
[codecov-badge]: https://codecov.io/gh/DrBarnabus/secure-pass/branch/master/graph/badge.svg | ||
[codecov-url]: https://codecov.io/gh/DrBarnabus/secure-pass | ||
[dependencies-url]: https://david-dm.org/drbarnabus/argon2-pass | ||
[devDependencies-badge]: https://david-dm.org/drbarnabus/argon2-pass/dev-status.svg?style=flat-square | ||
[devDependencies-url]: https://david-dm.org/drbarnabus/argon2-pass?type=dev | ||
[snyk-badge]: https://snyk.io/test/github/DrBarnabus/secure-pass/badge.svg?targetFile=package.json&style=flat-square | ||
[snyk-url]: https://snyk.io/test/github/DrBarnabus/secure-pass?targetFile=package.json | ||
[dependencies-badge]: https://david-dm.org/drbarnabus/secure-pass.svg | ||
[dependencies-url]: https://david-dm.org/drbarnabus/secure-pass | ||
[devDependencies-badge]: https://david-dm.org/drbarnabus/secure-pass/dev-status.svg | ||
[devDependencies-url]: https://david-dm.org/drbarnabus/secure-pass?type=dev | ||
[snyk-badge]: https://snyk.io/test/github/DrBarnabus/secure-pass/badge.svg?targetFile=package.json | ||
[snyk-url]: https://snyk.io/test/github/DrBarnabus/secure-pass?targetFile=package.json | ||
[codacy-badge]: https://api.codacy.com/project/badge/Grade/86e94a6a25c44d7bb7e514e7e2747e24 | ||
[codacy-url]: https://www.codacy.com/app/DrBarnabus/secure-pass?utm_source=github.com&utm_medium=referral&utm_content=DrBarnabus/secure-pass&utm_campaign=Badge_Grade |
@@ -1,2 +0,2 @@ | ||
import { bufferFromSafeBase64, bufferToSafeBase64 } from '../base64'; | ||
import { bufferFromSafeBase64, bufferToSafeBase64 } from '..'; | ||
@@ -3,0 +3,0 @@ describe('base64', () => { |
import sodium from 'sodium-native'; | ||
import { GenerateOneTimeAuthResult, SecurePass, VerificationResult } from '../'; | ||
import { SecurePass, VerificationResult } from '..'; | ||
import { SecurePassError, SecurePassOptionsError } from '../error'; | ||
@@ -19,2 +19,10 @@ | ||
test('MacBytes Constant should be defined.', () => { | ||
expect(SecurePass.MacBytes).toBeDefined(); | ||
}); | ||
test('KeyBytes Constant should be defined.', () => { | ||
expect(SecurePass.KeyBytes).toBeDefined(); | ||
}); | ||
test('MemLimit* Constants should be defined.', () => { | ||
@@ -49,4 +57,4 @@ expect(SecurePass.MemLimitDefault).toBeDefined(); | ||
const sp = new SecurePass(); | ||
expect(sp.getMemLimit()).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.getOpsLimit()).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
expect(sp.MemLimit).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.OpsLimit).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
}); | ||
@@ -56,4 +64,4 @@ | ||
const sp = new SecurePass({}); | ||
expect(sp.getMemLimit()).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.getOpsLimit()).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
expect(sp.MemLimit).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.OpsLimit).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
}); | ||
@@ -65,4 +73,4 @@ | ||
}); | ||
expect(sp.getMemLimit()).toEqual(16384); | ||
expect(sp.getOpsLimit()).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
expect(sp.MemLimit).toEqual(16384); | ||
expect(sp.OpsLimit).toEqual(sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE); | ||
}); | ||
@@ -74,4 +82,4 @@ | ||
}); | ||
expect(sp.getMemLimit()).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.getOpsLimit()).toEqual(12); | ||
expect(sp.MemLimit).toEqual(sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE); | ||
expect(sp.OpsLimit).toEqual(12); | ||
}); | ||
@@ -83,3 +91,3 @@ | ||
}); | ||
expect(sp.getMemLimit()).toEqual(SecurePass.MemLimitMinimum); | ||
expect(sp.MemLimit).toEqual(SecurePass.MemLimitMinimum); | ||
}); | ||
@@ -91,3 +99,3 @@ | ||
}); | ||
expect(sp.getMemLimit()).toEqual(SecurePass.MemLimitMaximum); | ||
expect(sp.MemLimit).toEqual(SecurePass.MemLimitMaximum); | ||
}); | ||
@@ -99,3 +107,3 @@ | ||
}); | ||
expect(sp.getOpsLimit()).toEqual(SecurePass.OpsLimitMinimum); | ||
expect(sp.OpsLimit).toEqual(SecurePass.OpsLimitMinimum); | ||
}); | ||
@@ -107,3 +115,3 @@ | ||
}); | ||
expect(sp.getOpsLimit()).toEqual(SecurePass.OpsLimitMaximum); | ||
expect(sp.OpsLimit).toEqual(SecurePass.OpsLimitMaximum); | ||
}); | ||
@@ -132,2 +140,58 @@ | ||
}); | ||
describe('get and set - MemLimit', () => { | ||
test('MemLimit should return the currently configured Memory Limit.', () => { | ||
const sp = new SecurePass({ | ||
memLimit: SecurePass.MemLimitSensitive | ||
}); | ||
expect(sp.MemLimit).toEqual(SecurePass.MemLimitSensitive); | ||
}); | ||
test('MemLimit should set the configured Memory Limit to the new limit.', () => { | ||
const sp = new SecurePass(); | ||
sp.MemLimit = SecurePass.MemLimitModerate; | ||
expect(sp.MemLimit).toEqual(SecurePass.MemLimitModerate); | ||
}); | ||
test('MemLimit should throw an error if set with an invalid memory limit.', () => { | ||
const sp = new SecurePass(); | ||
try { | ||
sp.MemLimit = 1024; | ||
} catch (e) { | ||
expect(e instanceof SecurePassOptionsError).toBeTruthy(); | ||
} | ||
}); | ||
}); | ||
describe('get and set - OpsLimit', () => { | ||
test('OpsLimit should return the currently configured Memory Limit.', () => { | ||
const sp = new SecurePass({ | ||
opsLimit: SecurePass.OpsLimitSensitive | ||
}); | ||
expect(sp.OpsLimit).toEqual(SecurePass.OpsLimitSensitive); | ||
}); | ||
test('OpsLimit should set the configured Memory Limit to the new limit.', () => { | ||
const sp = new SecurePass(); | ||
sp.OpsLimit = SecurePass.OpsLimitModerate; | ||
expect(sp.OpsLimit).toEqual(SecurePass.OpsLimitModerate); | ||
}); | ||
test('OpsLimit should throw an error if set with an invalid memory limit.', () => { | ||
const sp = new SecurePass(); | ||
try { | ||
sp.OpsLimit = SecurePass.OpsLimitMaximum + 1; | ||
} catch (e) { | ||
expect(e instanceof SecurePassOptionsError).toBeTruthy(); | ||
} | ||
}); | ||
}); | ||
}); | ||
@@ -275,2 +339,31 @@ | ||
describe('hashPasswordSync', () => { | ||
test('Should return a valid hash if given a valid password.', done => { | ||
const sp = new SecurePass(); | ||
const password = Buffer.from('SecurePass'); | ||
const hash = sp.hashPasswordSync(password); | ||
expect(hash.length).toEqual(SecurePass.HashBytes); | ||
expect(sp.verifyHashSync(password, hash)).toEqual(VerificationResult.Valid); | ||
done(); | ||
}); | ||
test('Should throw an error if given a blank password buffer.', done => { | ||
const sp = new SecurePass(); | ||
const password = Buffer.from(''); | ||
try { | ||
const hash = sp.hashPasswordSync(password); | ||
} catch (e) { | ||
expect(e).toBeDefined(); | ||
expect(e instanceof SecurePassError).toBeTruthy(); | ||
} | ||
done(); | ||
}); | ||
}); | ||
describe('async/promise verifyHash()', () => { | ||
@@ -310,10 +403,10 @@ test('Should correctly verify a valid hashed password.', async () => { | ||
const rehashValid = await bsp.verifyHash(userPassword, weakHash); | ||
const rehashValid = bsp.verifyHashSync(userPassword, weakHash); | ||
expect(rehashValid).toEqual(VerificationResult.ValidNeedsRehash); | ||
const betterHash = await bsp.hashPassword(userPassword); | ||
const betterValid = await bsp.verifyHash(userPassword, betterHash); | ||
const betterValid = bsp.verifyHashSync(userPassword, betterHash); | ||
expect(betterValid).toEqual(VerificationResult.Valid); | ||
const betterInvalid = await bsp.verifyHash(wrongPassword, betterHash); | ||
const betterInvalid = bsp.verifyHashSync(wrongPassword, betterHash); | ||
expect(betterInvalid).toEqual(VerificationResult.Invalid); | ||
@@ -488,2 +581,85 @@ }); | ||
}); | ||
describe('verifyHashSync()', () => { | ||
test('Should correctly verify a valid hashed password.', () => { | ||
const sp = new SecurePass(); | ||
const password = Buffer.from('SecurePass'); | ||
const hash = sp.hashPasswordSync(password); | ||
const result = sp.verifyHashSync(password, hash); | ||
expect(result).toBeDefined(); | ||
expect(result).toEqual(VerificationResult.Valid); | ||
}); | ||
test('Should correctly rehash passwords.', () => { | ||
const wsp = new SecurePass({ | ||
memLimit: SecurePass.MemLimitDefault, | ||
opsLimit: SecurePass.OpsLimitDefault | ||
}); | ||
const userPassword = Buffer.from('SecurePass'); | ||
const wrongPassword = Buffer.from('SecurePass2'); | ||
const weakHash = wsp.hashPasswordSync(userPassword); | ||
const weakValid = wsp.verifyHashSync(userPassword, weakHash); | ||
expect(weakValid).toEqual(VerificationResult.Valid); | ||
const weakInvalid = wsp.verifyHashSync(wrongPassword, weakHash); | ||
expect(weakInvalid).toEqual(VerificationResult.Invalid); | ||
const bsp = new SecurePass({ | ||
memLimit: SecurePass.MemLimitDefault + 1024, | ||
opsLimit: SecurePass.OpsLimitDefault + 1 | ||
}); | ||
const rehashValid = bsp.verifyHashSync(userPassword, weakHash); | ||
expect(rehashValid).toEqual(VerificationResult.ValidNeedsRehash); | ||
const betterHash = bsp.hashPasswordSync(userPassword); | ||
const betterValid = bsp.verifyHashSync(userPassword, betterHash); | ||
expect(betterValid).toEqual(VerificationResult.Valid); | ||
const betterInvalid = bsp.verifyHashSync(wrongPassword, betterHash); | ||
expect(betterInvalid).toEqual(VerificationResult.Invalid); | ||
}); | ||
test('Should return an error if given a blank password buffer.', () => { | ||
const sp = new SecurePass(); | ||
try { | ||
const password = Buffer.from(''); | ||
const hash = Buffer.alloc(SecurePass.HashBytes); | ||
const result = sp.verifyHashSync(password, hash); | ||
} catch (e) { | ||
expect(e).toBeDefined(); | ||
expect(e instanceof SecurePassError).toBeTruthy(); | ||
} | ||
}); | ||
test('Should return an error if given a blank hash buffer.', () => { | ||
const sp = new SecurePass(); | ||
try { | ||
const password = Buffer.from('SecurePass'); | ||
const hash = Buffer.from(''); | ||
const result = sp.verifyHashSync(password, hash); | ||
} catch (e) { | ||
expect(e).toBeDefined(); | ||
expect(e instanceof SecurePassError).toBeTruthy(); | ||
} | ||
}); | ||
test('Should return InvalidOrUnrecognized if given an invalid hash buffer.', () => { | ||
const sp = new SecurePass(); | ||
const password = Buffer.from('SecurePass'); | ||
const hash = Buffer.alloc(SecurePass.HashBytes, 0); | ||
const result = sp.verifyHashSync(password, hash); | ||
expect(result).toEqual(VerificationResult.InvalidOrUnrecognized); | ||
}); | ||
}); | ||
}); |
@@ -0,1 +1,9 @@ | ||
/** | ||
* @module SecurePass | ||
*/ | ||
/** | ||
* Converts a buffer into url-safe base64. | ||
* @param buf - The Buffer to convert. | ||
*/ | ||
export function bufferToSafeBase64(buf: Buffer): string { | ||
@@ -9,2 +17,6 @@ return buf | ||
/** | ||
* Converts a url-safe base64 string into a buffer. | ||
* @param base64 - The base64 string to convert. | ||
*/ | ||
export function bufferFromSafeBase64(base64: string): Buffer { | ||
@@ -11,0 +23,0 @@ base64 = base64 + '==='.slice((base64.length + 3) % 4); |
@@ -0,1 +1,4 @@ | ||
/** | ||
* Standard SecurePass module error, returned if anything goes wrong when using the module. | ||
*/ | ||
export class SecurePassError extends Error { | ||
@@ -2,0 +5,0 @@ constructor(m: string) { |
@@ -0,1 +1,4 @@ | ||
/** | ||
* This error is thrown if the supplied options are invalid during creation of an instance of SecurePass. | ||
*/ | ||
export class SecurePassOptionsError extends Error { | ||
@@ -2,0 +5,0 @@ constructor(m: string) { |
235
src/index.ts
@@ -0,1 +1,5 @@ | ||
/** | ||
* @module SecurePass | ||
*/ | ||
import sodium from 'sodium-native'; | ||
@@ -6,6 +10,9 @@ import { bufferFromSafeBase64, bufferToSafeBase64 } from './base64'; | ||
export { SecurePassError, SecurePassOptionsError } from './error'; | ||
export { bufferFromSafeBase64, bufferToSafeBase64 } from './base64'; | ||
/** A set of configuration options used to configure a new instance of SecurePass */ | ||
export interface SecurePassOptions { | ||
/** | ||
* Configures the memory limit of Argon2ID. | ||
* Configures the memory limit of Argon2ID. This value is in bytes. | ||
* The default value is 64MB. | ||
*/ | ||
@@ -16,2 +23,3 @@ memLimit?: number; | ||
* Configures the operation limit of Argon2ID. | ||
* The default value is 2. | ||
*/ | ||
@@ -21,20 +29,34 @@ opsLimit?: number; | ||
/** The callback function signature when hashPassword is used with a callback. */ | ||
export type HashPasswordCallback = (err: SecurePassError | null, hash?: Buffer) => void; | ||
/** The callback function signature when verifyHash is used with a callback. */ | ||
export type VerifyHashCallback = (err: SecurePassError | null, result?: VerificationResult) => void; | ||
export enum VerificationResult { | ||
/** Returned if the hash is in an invalid format or wasn't created by SecurePass. */ | ||
InvalidOrUnrecognized, | ||
/** Returned if the hash doesn't match the supplied plaintext password. */ | ||
Invalid, | ||
/** Returned if the hash is valid and matches the supplied plaintext password. */ | ||
Valid, | ||
/** Returned if the hash is valid, but could be improved with the currently set difficulty options. */ | ||
ValidNeedsRehash | ||
} | ||
/** Used to return a One Time Authentication mac and key as well as the message it was derrived from. */ | ||
export interface GenerateOneTimeAuthResult { | ||
/** One Time Authentication mac. */ | ||
mac: Buffer; | ||
/** One Time Authentication message. */ | ||
message: Buffer; | ||
/** One Time Authentication randomly generated key. This value should be kept secret. */ | ||
key: Buffer; | ||
} | ||
/** Used to return a One Time Authentication Code and randomly generated key. */ | ||
export interface GenerateOneTimeAuthCodeResult { | ||
/** One Time Authentication mac and message stored as a specifically formatted base64 string. */ | ||
code: string; | ||
/** One Time Authentication randomly generated key. This value should be kept secret. */ | ||
key: Buffer; | ||
@@ -48,3 +70,3 @@ } | ||
*/ | ||
public static readonly PasswordBytesMin: number = sodium.crypto_pwhash_PASSWD_MIN; | ||
public static readonly PasswordBytesMin: number = 1; | ||
@@ -55,3 +77,3 @@ /** | ||
*/ | ||
public static readonly PasswordBytesMax: number = sodium.crypto_pwhash_PASSWD_MAX; | ||
public static readonly PasswordBytesMax: number = 4294967295; | ||
@@ -62,3 +84,3 @@ /** | ||
*/ | ||
public static readonly HashBytes: number = sodium.crypto_pwhash_STRBYTES; | ||
public static readonly HashBytes: number = 128; | ||
@@ -69,38 +91,59 @@ /** | ||
*/ | ||
public static readonly SaltBytes: number = sodium.crypto_pwhash_SALTBYTES; | ||
public static readonly SaltBytes: number = 16; | ||
/** | ||
* Length of the Mac buffer returned by generateOneTimeAuth. | ||
* @readonly | ||
*/ | ||
public static readonly MacBytes: number = 16; | ||
/** | ||
* Length of the secret Key buffer returned by generateOneTimeAuth and generateOneTimeAuthCode. | ||
* @readonly | ||
*/ | ||
public static readonly KeyBytes: number = 32; | ||
/** | ||
* Default Memory Limit. 64MB. | ||
* @readonly | ||
*/ | ||
public static readonly MemLimitDefault: number = sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; | ||
public static readonly MemLimitDefault: number = 67108864; | ||
/** | ||
* Interactive Memory Limit. 64MB. For a use case, please see the SecurePass documentation. | ||
* Interactive Memory Limit. 64MB. This value is the same as MemLimitDefault. | ||
* This memory limit is recommended for interactive "online" applications, when combined with OpsLimitInteractive, | ||
* this option requires 64 MiB of dedicated RAM and provides a baseline configuration for web app security. | ||
* Choosing a higher value such as MemLimitModerate, MemLimitSensitive or a custom value may improve security. | ||
* @readonly | ||
*/ | ||
public static readonly MemLimitInteractive: number = sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; | ||
public static readonly MemLimitInteractive: number = 67108864; | ||
/** | ||
* Moderate Memory Limit. 256MB. For a use case, please see the SecurePass documentation. | ||
* Moderate Memory Limit. 256MB. | ||
* This memory limit, combined with OpsLimitModerate provides a good performance and security baseline for | ||
* applications that require higher security than the default/interactive preset. | ||
* Use of this option requires a minimum of 256 MiB of dedicated RAM. | ||
* @readonly | ||
*/ | ||
public static readonly MemLimitModerate: number = sodium.crypto_pwhash_MEMLIMIT_MODERATE; | ||
public static readonly MemLimitModerate: number = 268435456; | ||
/** | ||
* Sensitive Memory Limit. 1GB. For a use case, please see the SecurePass documentation. | ||
* Sensitive Memory Limit. 1GB. | ||
* This memory limit, combined with OpsLimitSensitive, is recommended for highly sensitive data | ||
* and non-interactive operations. | ||
* Use of this option requires a minimum of 1024 MiB of dedicated RAM. | ||
*/ | ||
public static readonly MemLimitSensitive: number = sodium.crypto_pwhash_MEMLIMIT_SENSITIVE; | ||
public static readonly MemLimitSensitive: number = 1073741824; | ||
/** | ||
* Minimum Memory Limit. 8KB. | ||
* The Minimum Allowed Memory Limit. 8KB. | ||
* @readonly | ||
*/ | ||
public static readonly MemLimitMinimum: number = sodium.crypto_pwhash_MEMLIMIT_MIN; | ||
public static readonly MemLimitMinimum: number = 8192; | ||
/** | ||
* Maximum Memory Limit. 4TB. | ||
* The Maximum Allowed Memory Limit. 4TB. | ||
* @readonly | ||
*/ | ||
public static readonly MemLimitMaximum: number = sodium.crypto_pwhash_MEMLIMIT_MAX; | ||
public static readonly MemLimitMaximum: number = 4398046510080; | ||
@@ -111,37 +154,51 @@ /** | ||
*/ | ||
public static readonly OpsLimitDefault: number = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; | ||
public static readonly OpsLimitDefault: number = 2; | ||
/** | ||
* Interactive Operations Limit. 2 Operations. For a use case, please see the SecurePass documentation. | ||
* Interactive Operations Limit. 2 Operations.This value is the same as OpsLimitDefault. | ||
* This operations limit is recommended for interactive "online" applications, when combined with MemLimitInteractive, | ||
* this option provides a baseline configuration for web app security. | ||
* Choosing a higher value such as MemLimitModerate, MemLimitSensitive or a custom value may improve security. | ||
* @readonly | ||
*/ | ||
public static readonly OpsLimitInteractive: number = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; | ||
public static readonly OpsLimitInteractive: number = 2; | ||
/** | ||
* Moderate Operations Limit. 3 Operations. For a use case, please see the SecurePass documentation. | ||
* Moderate Operations Limit. 3 Operations. | ||
* This operations limit, combined with MemLimitModerate provides a good performance and security baseline for | ||
* applications that require higher security than the default/interactive preset. | ||
* Using this options takes around 0.7 seconds to derrive a hash on a 2.8Ghz Core i7 CPU. | ||
* @readonly | ||
*/ | ||
public static readonly OpsLimitModerate: number = sodium.crypto_pwhash_OPSLIMIT_MODERATE; | ||
public static readonly OpsLimitModerate: number = 3; | ||
/** | ||
* Sensitive Operations Limit. 4 Operations. For a use case, please see the SecurePass documentation. | ||
* Sensitive Operations Limit. 4 Operations. | ||
* This memory limit, combined with OpsLimitSensitive, is recommended for highly sensitive data | ||
* and non-interactive operations. | ||
* Using this option it takes around 3.5 seconds to derrive a hash on a 2.8Ghz Core i7 CPU. | ||
*/ | ||
public static readonly OpsLimitSensitive: number = sodium.crypto_pwhash_OPSLIMIT_SENSITIVE; | ||
public static readonly OpsLimitSensitive: number = 4; | ||
/** | ||
* Minimum Operations Limit. 1 Operation. | ||
* The Minimum Allowed Operations Limit. 1 Operation. | ||
* @readonly | ||
*/ | ||
public static readonly OpsLimitMinimum: number = sodium.crypto_pwhash_OPSLIMIT_MIN; | ||
public static readonly OpsLimitMinimum: number = 1; | ||
/** | ||
* Maximum Operations Limit. 4294967295 Operations. | ||
* The Maximum Allowed Operations Limit. 4294967295 Operations. | ||
* @readonly | ||
*/ | ||
public static readonly OpsLimitMaximum: number = sodium.crypto_pwhash_OPSLIMIT_MAX; | ||
public static readonly OpsLimitMaximum: number = 4294967295; | ||
/** | ||
* Generates a random key, and then uses that key and the supplied message, | ||
* to generate a one time authentication mac. | ||
* @param message - The message to be used as the base of the one time authentication key. | ||
*/ | ||
public static generateOneTimeAuth(message: Buffer): GenerateOneTimeAuthResult { | ||
const mac = Buffer.alloc(sodium.crypto_onetimeauth_BYTES); | ||
const mac = Buffer.alloc(SecurePass.MacBytes); | ||
const key = Buffer.alloc(sodium.crypto_onetimeauth_KEYBYTES); | ||
const key = Buffer.alloc(SecurePass.KeyBytes); | ||
sodium.randombytes_buf(key); | ||
@@ -154,2 +211,9 @@ | ||
/** | ||
* Verifys the authenticity of the mac using the supplied message and, | ||
* the key returned when generating the mac. | ||
* @param mac - The authentication mac generated by generateOneTimeAuth. | ||
* @param message - The original message used to generate the mac. | ||
* @param key - The authentication key generated by generateOneTimeAuth. | ||
*/ | ||
public static verifyOneTimeAuth(mac: Buffer, message: Buffer, key: Buffer): boolean { | ||
@@ -159,2 +223,7 @@ return sodium.crypto_onetimeauth_verify(mac, message, key); | ||
/** | ||
* Generates a random key, and then uses that key and the supplied message, | ||
* to generate a one time authentication code. | ||
* @param message - The message to be used as the base of the one time authentication code. | ||
*/ | ||
public static generateOneTimeAuthCode(message: Buffer): GenerateOneTimeAuthCodeResult { | ||
@@ -166,2 +235,7 @@ const ota = SecurePass.generateOneTimeAuth(message); | ||
/** | ||
* Verifys the authenticity of the supplied authentication code using the key returned when generating the code. | ||
* @param code - The one time authentication code generated by generateOneTimeAuthCode. | ||
* @param key - The authentication key generated by generateOneTimeAuthCode. | ||
*/ | ||
public static verifyOneTimeAuthCode(code: string, key: Buffer): boolean { | ||
@@ -204,3 +278,2 @@ const message = bufferFromSafeBase64(code.substr(0, code.indexOf('~'))); | ||
} else { | ||
// tslint:disable-next-line:max-line-length | ||
throw new SecurePassOptionsError( | ||
@@ -222,3 +295,2 @@ `SecurePass: Invalid Memory Limit Configured. Value must be between ${SecurePass.MemLimitMinimum} and ${ | ||
} else { | ||
// tslint:disable-next-line:max-line-length | ||
throw new SecurePassOptionsError( | ||
@@ -236,3 +308,3 @@ `Secure Pass: Invalid Operations Limit Configured. Value must be between ${SecurePass.OpsLimitMinimum} and ${ | ||
*/ | ||
public getMemLimit(): number { | ||
public get MemLimit(): number { | ||
return this.memLimit; | ||
@@ -242,5 +314,22 @@ } | ||
/** | ||
* Sets the Memory Limit to the new value provided. | ||
* @param newValue - The new Memory Limit. | ||
*/ | ||
public set MemLimit(newValue: number) { | ||
// Check configured Memory Limit is within allowed values. | ||
if (newValue >= SecurePass.MemLimitMinimum && newValue <= SecurePass.MemLimitMaximum) { | ||
this.memLimit = newValue; | ||
} else { | ||
throw new SecurePassOptionsError( | ||
`SecurePass: Invalid Memory Limit Configured. Value must be between ${SecurePass.MemLimitMinimum} and ${ | ||
SecurePass.MemLimitMaximum | ||
}` | ||
); | ||
} | ||
} | ||
/** | ||
* Returns the currently configured Operations Limit. | ||
*/ | ||
public getOpsLimit(): number { | ||
public get OpsLimit(): number { | ||
return this.opsLimit; | ||
@@ -250,4 +339,20 @@ } | ||
/** | ||
* Takes the provided password buffer and returns a Argon2ID hash. | ||
* @param password - The password buffer to be hashed. | ||
* Sets the Operations Limit to the new value provided. | ||
*/ | ||
public set OpsLimit(newValue: number) { | ||
// Check configured Operations Limit is within allowed values. | ||
if (newValue >= SecurePass.OpsLimitMinimum && newValue <= SecurePass.OpsLimitMaximum) { | ||
this.opsLimit = newValue; | ||
} else { | ||
throw new SecurePassOptionsError( | ||
`Secure Pass: Invalid Operations Limit Configured. Value must be between ${SecurePass.OpsLimitMinimum} and ${ | ||
SecurePass.OpsLimitMaximum | ||
}` | ||
); | ||
} | ||
} | ||
/** | ||
* Takes the provided password and returns the derived Argon2ID hash. | ||
* @param password - The password to be hashed. | ||
* @param callback - Optional callback function. | ||
@@ -266,6 +371,24 @@ */ | ||
/** | ||
* Takes the provided password buffer and the hash buffer and returns the result of the verification. | ||
* @param password - The password buffer to be verified. | ||
* @param hash - The has buffer to be verified agains. | ||
* Takes the provided password and returns the derived Argon2ID hash. | ||
* @param password - The password to be hashed. | ||
*/ | ||
public hashPasswordSync(password: Buffer): Buffer { | ||
if (!(password.length >= SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
throw new SecurePassError( | ||
`Length of Password Buffer must be between ${SecurePass.PasswordBytesMin} and ${SecurePass.PasswordBytesMax}` | ||
); | ||
} | ||
const hash = Buffer.allocUnsafe(SecurePass.HashBytes); | ||
sodium.crypto_pwhash_str(hash, password, this.opsLimit, this.memLimit); | ||
return hash; | ||
} | ||
/** | ||
* Takes the provided password and the hash buffer | ||
* and returns the result of the verification as an enumeration value. | ||
* @param password - The password to be verified. | ||
* @param hash - The hash to be verified against. | ||
*/ | ||
public verifyHash(password: Buffer, hash: Buffer): Promise<VerificationResult>; | ||
@@ -281,5 +404,37 @@ public verifyHash(password: Buffer, hash: Buffer, callback: VerifyHashCallback): void; | ||
/** | ||
* Takes the provided password and the hash buffer | ||
* and returns the result of the verification as an enumeration value. | ||
* @param password - The password to be verified. | ||
* @param hash - The hash to be verified against. | ||
*/ | ||
public verifyHashSync(password: Buffer, hash: Buffer): VerificationResult { | ||
if (!(password.length >= SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
throw new SecurePassError( | ||
`Length of Password Buffer must be between ${SecurePass.PasswordBytesMin} and ${SecurePass.PasswordBytesMax}` | ||
); | ||
} | ||
if (hash.length != SecurePass.HashBytes) { | ||
throw new SecurePassError(`Length of Hash Buffer must be between ${SecurePass.HashBytes}`); | ||
} | ||
if (!this.recognizedAlgorithm(hash)) { | ||
return VerificationResult.InvalidOrUnrecognized; | ||
} | ||
if (sodium.crypto_pwhash_str_verify(hash, password) == false) { | ||
return VerificationResult.Invalid; | ||
} | ||
if (sodium.crypto_pwhash_str_needs_rehash(hash, this.opsLimit, this.memLimit)) { | ||
return VerificationResult.ValidNeedsRehash; | ||
} | ||
return VerificationResult.Valid; | ||
} | ||
private async hashPasswordAsync(password: Buffer): Promise<Buffer> { | ||
return new Promise<Buffer>((resolve, reject) => { | ||
if (!(password.length > SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
if (!(password.length >= SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
reject( | ||
@@ -308,3 +463,3 @@ new SecurePassError( | ||
return new Promise<VerificationResult>((resolve, reject) => { | ||
if (!(password.length > SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
if (!(password.length >= SecurePass.PasswordBytesMin && password.length < SecurePass.PasswordBytesMax)) { | ||
reject( | ||
@@ -311,0 +466,0 @@ new SecurePassError( |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
628353
54
3500
129
8
3
4