Compare commits
92 Commits
6fd21e4e36
...
master
Author | SHA1 | Date | |
---|---|---|---|
fe1ab3bf64
|
|||
5bbf9d658c
|
|||
78a535087a
|
|||
7d41f8689f | |||
ae34356670 | |||
cac193a341 | |||
00f1a57f8c | |||
42857284fd | |||
ecd0195aee | |||
612686a224 | |||
142e9ec458 | |||
4d6e028fca | |||
b12ffc17b8 | |||
4004a93bb2 | |||
7152ae032c | |||
bcbc59f656 | |||
69d509d5c7 | |||
077f656f24 | |||
712ef8d284 | |||
2e60415625 | |||
b2dbea979f | |||
8a8689abc5 | |||
251974a558 | |||
d2252f203a | |||
f02c305e43 | |||
270a64aea4 | |||
ebcf0816ea | |||
4177ce3335 | |||
9931302872 | |||
be240d8754 | |||
b6ec3b29db | |||
1bac9cbca1 | |||
7b81e7118b | |||
bb4e6e645b | |||
37f41a04cf | |||
2a4dfc879b | |||
b47d0507dd | |||
467ff1e254 | |||
0294d151bb | |||
e2d6ebfe4a | |||
6d86eece90 | |||
771e3c0907 | |||
7a82387bc3 | |||
f157ee2d33 | |||
e7bff5b41c | |||
3ff9d3f959 | |||
88d30e9663 | |||
ab3bf29d61 | |||
c9f07a475e | |||
19d24333c4 | |||
8305ab78f6 | |||
1fc39df14f | |||
48f87e2ef4 | |||
fc54ff3932 | |||
6577c8e10f | |||
a45944bf1f | |||
0d04b915c4 | |||
bf9779dffa | |||
0d559539f1 | |||
159c1c1ced | |||
b3e3e4ab7e | |||
6cfacfb09f | |||
6e4ac8bdee | |||
d9dd021007 | |||
4082e582af | |||
7fc5cebb67 | |||
7e68e45ec4 | |||
1f0b0a377a | |||
7211bcc925 | |||
d3a7bbbdb2 | |||
baaa8e9b17 | |||
a80f8504ff | |||
b8741ea83a | |||
64dc7086fd | |||
3f0b9abc21 | |||
d72790e636 | |||
1b303ecea5 | |||
d8dc182521 | |||
f6ec536481 | |||
729a65839c | |||
18476b2aaf | |||
565dab36ac | |||
2e2ea43310 | |||
38542bb422 | |||
3715ec5183 | |||
8f227d8c0e | |||
cb9c142547 | |||
c0ec49fd4b | |||
e470b68ea5 | |||
59476a8087 | |||
c603fccd6f | |||
91f7d66dbb |
14
.drone.yml
Normal file
14
.drone.yml
Normal file
@ -0,0 +1,14 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: setup
|
||||
image: registry:5000/node-build
|
||||
commands:
|
||||
- yarn
|
||||
- curl https://git.scode.ovh/Timo/standard/raw/branch/master/ci.js > ci.js
|
||||
|
||||
- name: build
|
||||
image: registry:5000/node-build
|
||||
commands:
|
||||
- node ci.js
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
*.d.ts
|
25
.eslintrc.js
25
.eslintrc.js
@ -1,17 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of crypto-helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
node: true
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'@scode'
|
||||
],
|
||||
extends: [ '@sapphirecode' ],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018
|
||||
}
|
||||
}
|
||||
parserOptions: { ecmaVersion: 2018 }
|
||||
};
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@
|
||||
|
||||
# stryker temp files
|
||||
.stryker-tmp
|
||||
*.log
|
||||
*.d.ts
|
||||
|
8
.liconfig.json
Normal file
8
.liconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"has_license": true,
|
||||
"license": "MIT",
|
||||
"author": "Timo Hocker",
|
||||
"company": "Sapphirecode",
|
||||
"email": "timo@scode.ovh",
|
||||
"software": "crypto-helper"
|
||||
}
|
31
CHANGELOG.md
Normal file
31
CHANGELOG.md
Normal file
@ -0,0 +1,31 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.1.0
|
||||
|
||||
Added scrypt key derivation function
|
||||
|
||||
## 2.0.0
|
||||
|
||||
Signature related functions have changed to be asynchronous and to accept async
|
||||
functions as key retrieving functions
|
||||
|
||||
## 1.3.0
|
||||
|
||||
asymmetric encryption and signatures
|
||||
|
||||
object signing: ability to use rsa keys
|
||||
|
||||
## 1.2.0
|
||||
|
||||
object signing:
|
||||
|
||||
- custom properties
|
||||
- keys retrievable using functions
|
||||
|
||||
## 1.1.0
|
||||
|
||||
aes encryption functions
|
||||
|
||||
## 1.0.0
|
||||
|
||||
initial release
|
23
Jenkinsfile
vendored
23
Jenkinsfile
vendored
@ -1,23 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
VERSION = VersionNumber([
|
||||
versionNumberString:
|
||||
'${BUILDS_ALL_TIME}',
|
||||
versionPrefix: '1.1.',
|
||||
worstResultForIncrement: 'SUCCESS'
|
||||
])
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Building') {
|
||||
steps {
|
||||
script {
|
||||
currentBuild.displayName = env.VERSION
|
||||
}
|
||||
sh 'yarn ci ${VERSION}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
MIT License Copyright (c) <year> <author>
|
||||
|
||||
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 (including the next paragraph) 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.
|
59
README.md
59
README.md
@ -1,20 +1,67 @@
|
||||
# Crypto Helper
|
||||
# @sapphirecode/crypto-helper
|
||||
|
||||
Helper functions for cryptography
|
||||
version: 2.1.x
|
||||
|
||||
simple functions for cryptography
|
||||
|
||||
## Installation
|
||||
|
||||
npm:
|
||||
|
||||
> npm i --save @sapphirecode/crypto-helper
|
||||
|
||||
yarn:
|
||||
|
||||
> yarn add @sapphirecode/crypto-helper
|
||||
|
||||
## Usage
|
||||
|
||||
### Examples
|
||||
|
||||
```js
|
||||
const crypto = require('@scode/crypto-helper');
|
||||
const crypto = require('@sapphirecode/crypto-helper');
|
||||
|
||||
const rand_hex = crypto.random_hex(16); // outputs 16 byte random hex
|
||||
const rand_salt = crypto.create_salt(); // same as random_hex, but with fixed length of 32 bytes
|
||||
const random_string = crypto.random_string(16) // output 16 character long random string
|
||||
const random_string = crypto.random_string(16); // output 16 character long random string
|
||||
const hash = crypto.hash_sha512(random_string, random_hex); // returns sha 512 hex
|
||||
const check = crypto.checksum('foo'); // returns a sha 256 hex
|
||||
const scrypt_hash = await crypto.pbkdf_scrypt('foo', 'bar'); // returns a scrypt hash
|
||||
|
||||
// jwt like object signing
|
||||
const signed = crypto.sign_object({foo: 'bar'}, 'secret');
|
||||
const dec = crypto.decode_signed(signed); // decode a signed object without verifying the signature
|
||||
const ver = crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted.
|
||||
const info = await crypto.get_signature_info(signed); // returns an object with iat (issued at), key_info and data
|
||||
const dec = await crypto.decode_signed(signed); // decode a signed object without verifying the signature
|
||||
const ver = await crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted.
|
||||
const ver_info = await crypto.verify_signature_get_info(signed, 'secret', 10000); // verify a signature and get signature information like iat and key_info
|
||||
const ver_func = await crypto.verify_signature(
|
||||
signed,
|
||||
(signature_info) => 'secret',
|
||||
10000
|
||||
); // verify a signature, retrieve the key using the signature info
|
||||
|
||||
// encryption
|
||||
const enc = crypto.encrypt_aes('foo', 'bar');
|
||||
const dec = crypto.decrypt_aes(enc, 'bar');
|
||||
|
||||
// asymmetric encryption and signatures
|
||||
const keys = await crypto.generate_keypair(2048); // generate private and public key (length is optional and 2048 by default)
|
||||
|
||||
const aenc = crypto.asym_encrypt('foo', keys.public_key); // encrypt
|
||||
const adec = crypto.asym_decrypt(aenc, key.private_key); // decrypt
|
||||
|
||||
const asig = crypto.asym_sign('foo', keys.private_key); // create signature
|
||||
const aver = crypto.asym_verify('foo', keys.public_key, asig); // verify signature, returns boolean
|
||||
```
|
||||
|
||||
### Asymmetric signatures on object signing
|
||||
|
||||
the functions `sign_object`, `verify_signature`, ... will automatically detect
|
||||
rsa keys and use them to sign objects asymmetrically. Note that keys have to be
|
||||
provided in the correct order (private key for signing, public key for
|
||||
verifying). Else the keys will just be interpreted as symmetric and verification
|
||||
will fail.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Timo Hocker <timo@scode.ovh>
|
||||
|
248
index.js
248
index.js
@ -1,243 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* Created by Timo Hocker <timo@scode.ovh>, March 2020
|
||||
* This file is part of crypto-helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
// @ts-nocheck
|
||||
/* eslint-disable no-magic-numbers */
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('crypto');
|
||||
const encoding = require ('@scode/encoding-helper');
|
||||
|
||||
const encryption_mode_cbc_256 = {
|
||||
algorithm: 'aes-256-cbc',
|
||||
nonce_size: 16,
|
||||
key_size: 32,
|
||||
hash: 'sha256',
|
||||
salt_size: 16,
|
||||
iterations: 32767
|
||||
};
|
||||
|
||||
const encryption_mode_cbc_128 = {
|
||||
algorithm: 'aes-128-cbc',
|
||||
nonce_size: 16,
|
||||
key_size: 16,
|
||||
hash: 'sha256',
|
||||
salt_size: 16,
|
||||
iterations: 40
|
||||
};
|
||||
|
||||
/**
|
||||
* creates a random string
|
||||
*
|
||||
* @param {number} len string length default: 6
|
||||
* @returns {string} random string
|
||||
*/
|
||||
function random_string (len = 8) {
|
||||
if (len < 1)
|
||||
throw new Error ('invalid length');
|
||||
return crypto.randomBytes (Math.ceil (len * 3 / 4))
|
||||
.toString ('base64')
|
||||
.replace (/[=]+$/u, '')
|
||||
.substr (0, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a random hexadecimal string
|
||||
*
|
||||
* @param {number} len length
|
||||
* @returns {string} hex string
|
||||
*/
|
||||
function random_hex (len = 8) {
|
||||
if (len < 1)
|
||||
throw new Error ('invalid length');
|
||||
return crypto.randomBytes (Math.ceil (len / 2))
|
||||
.toString ('hex')
|
||||
.substr (0, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a 64 character long random hex string
|
||||
*
|
||||
* @returns {string} salt
|
||||
*/
|
||||
function create_salt () {
|
||||
return random_hex (64);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a sha512 hash
|
||||
*
|
||||
* @param {string} str string input
|
||||
* @param {string} salt salt
|
||||
* @returns {string} salt
|
||||
*/
|
||||
function hash_sha512 (str, salt) {
|
||||
const md = crypto.createHash ('sha512');
|
||||
md.update (str);
|
||||
md.update (salt);
|
||||
return md.digest ('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* sign an object
|
||||
*
|
||||
* @param {any} obj object to sign
|
||||
* @param {string} key key to use
|
||||
* @param {string} key_info key identifier
|
||||
* @returns {string} signed object
|
||||
*/
|
||||
function sign_object (obj, key, key_info = null) {
|
||||
const payload = { iat: Date.now (), key_info, obj };
|
||||
const str = encoding.to_b64 (JSON.stringify (payload));
|
||||
const token = encoding.to_b64 (hash_sha512 (str, key), 'hex');
|
||||
const res = `${str}.${token}`;
|
||||
return encodeURIComponent (res);
|
||||
}
|
||||
|
||||
/**
|
||||
* verify a signed object and return its contents
|
||||
*
|
||||
* @param {string} str string to verify
|
||||
* @param {string} key used key
|
||||
* @param {number} timeout timeout (optional)
|
||||
* @returns {any} returns object if successful, else null
|
||||
*/
|
||||
function verify_signature (str, key, timeout = 0) {
|
||||
const dec = decodeURIComponent (str)
|
||||
.split ('.');
|
||||
const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64'));
|
||||
const token = encoding.to_hex (dec[1], 'base64');
|
||||
const verify_token = hash_sha512 (dec[0], key);
|
||||
if (token !== verify_token)
|
||||
return null;
|
||||
const time = Date.now () - json.iat;
|
||||
if (timeout !== 0 && time > timeout)
|
||||
return null;
|
||||
return json.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a signed object info and data
|
||||
*
|
||||
* @param {string} str string to decode
|
||||
* @returns {any} data
|
||||
*/
|
||||
function get_signature_info (str) {
|
||||
const dec = decodeURIComponent (str)
|
||||
.split ('.');
|
||||
const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64'));
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* decode a signed object without verifying the signature
|
||||
*
|
||||
* @param {string} str string to decode
|
||||
* @returns {any} object
|
||||
*/
|
||||
function decode_signed (str) {
|
||||
return get_signature_info (str).obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a sha256 hash
|
||||
*
|
||||
* @param {any} data input
|
||||
* @returns {string} hash
|
||||
*/
|
||||
function checksum (data) {
|
||||
const md = crypto.createHash ('sha256');
|
||||
md.update (String (data));
|
||||
return md.digest ('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypt plain text with aes
|
||||
*
|
||||
* @param {string} text plaintext
|
||||
* @param {string} pass password
|
||||
* @param {object} mode encryption mode
|
||||
* @returns {string} encrypted
|
||||
*/
|
||||
function encrypt_aes (text, pass, mode = encryption_mode_cbc_256) {
|
||||
const salt = crypto.randomBytes (mode.salt_size);
|
||||
// eslint-disable-next-line no-sync
|
||||
const key = crypto.pbkdf2Sync (
|
||||
Buffer.from (pass, 'utf-8'),
|
||||
salt,
|
||||
mode.iterations,
|
||||
mode.key_size,
|
||||
mode.hash
|
||||
);
|
||||
const nonce = crypto.randomBytes (mode.nonce_size);
|
||||
const cipher = crypto.createCipheriv (mode.algorithm, key, nonce);
|
||||
return Buffer.concat ([
|
||||
salt,
|
||||
nonce,
|
||||
cipher.update (Buffer.from (text)),
|
||||
cipher.final ()
|
||||
])
|
||||
.toString ('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt an aes string
|
||||
*
|
||||
* @param {string} ciphertext encrypted text
|
||||
* @param {string} pass password
|
||||
* @param {object} mode encryption mode
|
||||
* @param {boolean} rethrow rethrow exceptions instead of returning null
|
||||
* @returns {string} plaintext
|
||||
*/
|
||||
function decrypt_aes (
|
||||
ciphertext,
|
||||
pass,
|
||||
mode = encryption_mode_cbc_256,
|
||||
rethrow = false
|
||||
) {
|
||||
try {
|
||||
let buf = Buffer.from (ciphertext, 'base64');
|
||||
const salt = buf.slice (0, mode.salt_size);
|
||||
buf = buf.slice (mode.salt_size);
|
||||
// eslint-disable-next-line no-sync
|
||||
const key = crypto.pbkdf2Sync (
|
||||
Buffer.from (pass, 'utf-8'),
|
||||
salt,
|
||||
mode.iterations,
|
||||
mode.key_size,
|
||||
mode.hash
|
||||
);
|
||||
const nonce = buf.slice (0, mode.nonce_size);
|
||||
buf = buf.slice (mode.nonce_size);
|
||||
const cipher = crypto.createDecipheriv (mode.algorithm, key, nonce);
|
||||
return Buffer.concat ([
|
||||
cipher.update (buf),
|
||||
cipher.final ()
|
||||
])
|
||||
.toString ('utf-8');
|
||||
}
|
||||
catch (e) {
|
||||
if (rethrow)
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const encryption = require ('./lib/encryption');
|
||||
const hashing = require ('./lib/hashing');
|
||||
const random = require ('./lib/random');
|
||||
const signatures = require ('./lib/signatures');
|
||||
const rsa = require ('./lib/rsa');
|
||||
const pbkdf = require ('./lib/pbkdf');
|
||||
|
||||
module.exports = {
|
||||
checksum,
|
||||
create_salt,
|
||||
decode_signed,
|
||||
decrypt_aes,
|
||||
encrypt_aes,
|
||||
encryption_mode_cbc_128,
|
||||
encryption_mode_cbc_256,
|
||||
get_signature_info,
|
||||
hash_sha512,
|
||||
random_hex,
|
||||
random_string,
|
||||
sign_object,
|
||||
verify_signature
|
||||
...random,
|
||||
...hashing,
|
||||
...encryption,
|
||||
...signatures,
|
||||
...rsa,
|
||||
...pbkdf
|
||||
};
|
||||
|
||||
|
14
jasmine.json
Normal file
14
jasmine.json
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
{
|
||||
"spec_dir": "test",
|
||||
"spec_files": [
|
||||
"spec/*.js",
|
||||
"spec/*.ts"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/*.js",
|
||||
"helpers/*.ts"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
26
jenkins.js
26
jenkins.js
@ -1,26 +0,0 @@
|
||||
/* eslint-disable no-process-exit */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-sync */
|
||||
'use strict';
|
||||
|
||||
const fs = require ('fs');
|
||||
const child_process = require ('child_process');
|
||||
|
||||
const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8'));
|
||||
[
|
||||
,, pkg.version
|
||||
] = process.argv;
|
||||
fs.writeFileSync ('package.json', JSON.stringify (pkg, null, 2));
|
||||
|
||||
child_process.execSync ('yarn lint', { stdio: 'inherit' });
|
||||
if (typeof pkg.scripts !== 'undefined' && typeof pkg.scripts.test === 'string')
|
||||
child_process.execSync ('yarn test', { stdio: 'inherit' });
|
||||
|
||||
child_process.exec ('git log -1 | grep \'\\[no publish\\]\'')
|
||||
.addListener ('exit', (code) => {
|
||||
if (code === 0) {
|
||||
console.log ('build not marked for deployment');
|
||||
process.exit (1);
|
||||
}
|
||||
else { child_process.execSync ('yarn publish'); }
|
||||
});
|
110
lib/encryption.js
Normal file
110
lib/encryption.js
Normal file
@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('crypto');
|
||||
|
||||
const encryption_mode_cbc_256 = {
|
||||
algorithm: 'aes-256-cbc',
|
||||
nonce_size: 16,
|
||||
key_size: 32,
|
||||
hash: 'sha256',
|
||||
salt_size: 16,
|
||||
iterations: 32767
|
||||
};
|
||||
|
||||
const encryption_mode_cbc_256_quick = {
|
||||
algorithm: 'aes-256-cbc',
|
||||
nonce_size: 16,
|
||||
key_size: 32,
|
||||
hash: 'sha256',
|
||||
salt_size: 16,
|
||||
iterations: 32
|
||||
};
|
||||
|
||||
const encryption_mode_cbc_128 = {
|
||||
algorithm: 'aes-128-cbc',
|
||||
nonce_size: 16,
|
||||
key_size: 16,
|
||||
hash: 'sha256',
|
||||
salt_size: 16,
|
||||
iterations: 40
|
||||
};
|
||||
|
||||
/**
|
||||
* encrypt plain text with aes
|
||||
*
|
||||
* @param {string} text plaintext
|
||||
* @param {string} pass password
|
||||
* @param {object} mode encryption mode
|
||||
* @returns {string} encrypted
|
||||
*/
|
||||
function encrypt_aes (text, pass, mode = encryption_mode_cbc_256) {
|
||||
const salt = crypto.randomBytes (mode.salt_size);
|
||||
// eslint-disable-next-line no-sync
|
||||
const key = crypto.pbkdf2Sync (
|
||||
Buffer.from (pass),
|
||||
salt,
|
||||
mode.iterations,
|
||||
mode.key_size,
|
||||
mode.hash
|
||||
);
|
||||
const nonce = crypto.randomBytes (mode.nonce_size);
|
||||
const cipher = crypto.createCipheriv (mode.algorithm, key, nonce);
|
||||
return Buffer.concat ([
|
||||
salt,
|
||||
nonce,
|
||||
cipher.update (Buffer.from (text)),
|
||||
cipher.final ()
|
||||
])
|
||||
.toString ('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt an aes string
|
||||
*
|
||||
* @param {string} ciphertext encrypted text
|
||||
* @param {string} pass password
|
||||
* @param {object} mode encryption mode
|
||||
* @param {boolean} rethrow rethrow exceptions instead of returning null
|
||||
* @returns {string} plaintext
|
||||
*/
|
||||
function decrypt_aes (
|
||||
ciphertext,
|
||||
pass,
|
||||
mode = encryption_mode_cbc_256,
|
||||
rethrow = false
|
||||
) {
|
||||
try {
|
||||
let buf = Buffer.from (ciphertext, 'base64');
|
||||
const salt = buf.slice (0, mode.salt_size);
|
||||
buf = buf.slice (mode.salt_size);
|
||||
// eslint-disable-next-line no-sync
|
||||
const key = crypto.pbkdf2Sync (
|
||||
Buffer.from (pass),
|
||||
salt,
|
||||
mode.iterations,
|
||||
mode.key_size,
|
||||
mode.hash
|
||||
);
|
||||
const nonce = buf.slice (0, mode.nonce_size);
|
||||
buf = buf.slice (mode.nonce_size);
|
||||
const cipher = crypto.createDecipheriv (mode.algorithm, key, nonce);
|
||||
return Buffer.concat ([
|
||||
cipher.update (buf),
|
||||
cipher.final ()
|
||||
])
|
||||
.toString ();
|
||||
}
|
||||
catch (e) {
|
||||
if (rethrow)
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decrypt_aes,
|
||||
encrypt_aes,
|
||||
encryption_mode_cbc_128,
|
||||
encryption_mode_cbc_256,
|
||||
encryption_mode_cbc_256_quick
|
||||
};
|
34
lib/hashing.js
Normal file
34
lib/hashing.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('crypto');
|
||||
|
||||
/**
|
||||
* creates a sha256 hash
|
||||
*
|
||||
* @param {any} data input
|
||||
* @returns {string} hash
|
||||
*/
|
||||
function checksum (data) {
|
||||
const md = crypto.createHash ('sha256');
|
||||
md.update (String (data));
|
||||
return md.digest ('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a sha512 hash
|
||||
*
|
||||
* @param {string} str string input
|
||||
* @param {string} salt salt
|
||||
* @returns {string} salt
|
||||
*/
|
||||
function hash_sha512 (str, salt) {
|
||||
const md = crypto.createHash ('sha512');
|
||||
md.update (str);
|
||||
md.update (salt);
|
||||
return md.digest ('hex');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hash_sha512,
|
||||
checksum
|
||||
};
|
21
lib/pbkdf.js
Normal file
21
lib/pbkdf.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const { scrypt } = require ('scrypt-js');
|
||||
|
||||
/**
|
||||
* creates a scrypt hash
|
||||
*
|
||||
* @param {string} str string input
|
||||
* @param {string} salt salt#
|
||||
* @param {(progress: number) => void} [progress] progress callback
|
||||
* @returns {Promise<string>} hash
|
||||
*/
|
||||
async function pbkdf_scrypt (str, salt, progress) {
|
||||
const bstr = Buffer.from (str.normalize ('NFKC'), 'utf-8');
|
||||
const bsalt = Buffer.from (salt.normalize ('NFKC'), 'utf-8');
|
||||
const hash = await scrypt (bstr, bsalt, 8192, 8, 1, 64, progress);
|
||||
return Buffer.from (hash)
|
||||
.toString ('hex');
|
||||
}
|
||||
|
||||
module.exports = { pbkdf_scrypt };
|
46
lib/random.js
Normal file
46
lib/random.js
Normal file
@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('crypto');
|
||||
|
||||
/**
|
||||
* creates a random string
|
||||
*
|
||||
* @param {number} len string length default: 8
|
||||
* @returns {string} random string
|
||||
*/
|
||||
function random_string (len = 8) {
|
||||
if (len < 1)
|
||||
throw new Error ('invalid length');
|
||||
return crypto.randomBytes (Math.ceil (len * 3 / 4))
|
||||
.toString ('base64')
|
||||
.substr (0, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a random hexadecimal string
|
||||
*
|
||||
* @param {number} len length
|
||||
* @returns {string} hex string
|
||||
*/
|
||||
function random_hex (len = 8) {
|
||||
if (len < 1)
|
||||
throw new Error ('invalid length');
|
||||
return crypto.randomBytes (Math.ceil (len / 2))
|
||||
.toString ('hex')
|
||||
.substr (0, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a 64 character long random hex string
|
||||
*
|
||||
* @returns {string} salt
|
||||
*/
|
||||
function create_salt () {
|
||||
return random_hex (64);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create_salt,
|
||||
random_hex,
|
||||
random_string
|
||||
};
|
98
lib/rsa.js
Normal file
98
lib/rsa.js
Normal file
@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('crypto');
|
||||
|
||||
/**
|
||||
* generate a new rsa keypair
|
||||
*
|
||||
* @param {number} length the key length in bit. default: 2048
|
||||
* @returns {Promise<{public_key: string, private_key: string}>} generated keys
|
||||
*/
|
||||
async function generate_keypair (length = 2048) {
|
||||
const key = await new Promise (
|
||||
(res, rej) => crypto.generateKeyPair (
|
||||
'rsa',
|
||||
{
|
||||
modulusLength: length,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
},
|
||||
(err, public_key, private_key) => {
|
||||
if (err)
|
||||
rej (err);
|
||||
res ({ public_key, private_key });
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypts data using a public key
|
||||
* it can only be decrypted with the corresponding private key
|
||||
*
|
||||
* @param {string} data data to encrypt
|
||||
* @param {string} public_key public key
|
||||
* @returns {string} encrypted data
|
||||
*/
|
||||
function asym_encrypt (data, public_key) {
|
||||
return crypto.publicEncrypt (public_key, Buffer.from (data))
|
||||
.toString ('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypts data using a private key
|
||||
*
|
||||
* @param {string} data data to decrypt
|
||||
* @param {string} private_key private key
|
||||
* @returns {string} decrypted data
|
||||
*/
|
||||
function asym_decrypt (data, private_key) {
|
||||
return crypto.privateDecrypt (private_key, Buffer.from (data, 'base64'))
|
||||
.toString ();
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a signature using a private key
|
||||
* can later be verified using the corresponding public key
|
||||
*
|
||||
* @param {string} data data to sign
|
||||
* @param {string} private_key private key
|
||||
* @returns {string} signature
|
||||
*/
|
||||
function asym_sign (data, private_key) {
|
||||
const sign = crypto.createSign ('sha256');
|
||||
sign.write (data);
|
||||
sign.end ();
|
||||
return sign.sign (private_key, 'hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* verifies a signature using a public key
|
||||
*
|
||||
* @param {string} data data to verify
|
||||
* @param {string} public_key public key
|
||||
* @param {string} signature signature to verify
|
||||
* @returns {boolean} true if signature is valid
|
||||
*/
|
||||
function asym_verify (data, public_key, signature) {
|
||||
const verify = crypto.createVerify ('sha256');
|
||||
verify.write (data);
|
||||
verify.end ();
|
||||
return verify.verify (public_key, signature, 'hex');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generate_keypair,
|
||||
asym_encrypt,
|
||||
asym_decrypt,
|
||||
asym_sign,
|
||||
asym_verify
|
||||
};
|
135
lib/signatures.js
Normal file
135
lib/signatures.js
Normal file
@ -0,0 +1,135 @@
|
||||
'use strict';
|
||||
|
||||
const encoding = require ('@sapphirecode/encoding-helper');
|
||||
const { hash_sha512 } = require ('./hashing');
|
||||
const { asym_sign, asym_verify } = require ('./rsa');
|
||||
|
||||
/**
|
||||
* sign an object
|
||||
*
|
||||
* @param {any} obj object to sign
|
||||
* @param {string} key key to use
|
||||
* @param {string|Object} key_info key identifier
|
||||
* @returns {string} signed object
|
||||
*/
|
||||
function sign_object (obj, key, key_info = null) {
|
||||
const payload = {
|
||||
iat: Date.now (),
|
||||
obj,
|
||||
...(typeof key_info === 'object' ? key_info : { key_info })
|
||||
};
|
||||
const str = encoding.to_b58 (JSON.stringify (payload));
|
||||
const is_rsa = (/^-----BEGIN PRIVATE KEY-----/u).test (key);
|
||||
const signature = is_rsa ? asym_sign (str, key) : hash_sha512 (str, key);
|
||||
const token = encoding.to_b58 (signature, 'hex');
|
||||
const res = `${str}.${token}.2`;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse a string signature
|
||||
*
|
||||
* @param {string} str string to verify
|
||||
* @param {string|((Object)=>string|Promise<string>)|null} key used key
|
||||
* @returns {Promise<any>} returns object if successful, else null
|
||||
*/
|
||||
async function parse_signature (str, key = null) {
|
||||
let dec = str.split ('.');
|
||||
const version = dec[2];
|
||||
const res = {};
|
||||
switch (version) {
|
||||
case '2':
|
||||
res.json = JSON.parse (encoding.to_utf8 (dec[0], 'base58'));
|
||||
res.token = encoding.to_hex (dec[1], 'base58');
|
||||
break;
|
||||
default:
|
||||
dec = decodeURIComponent (str)
|
||||
.split ('.');
|
||||
res.json = JSON.parse (
|
||||
encoding.to_utf8 (dec[0], 'base64')
|
||||
);
|
||||
res.token = encoding.to_hex (dec[1], 'base64');
|
||||
break;
|
||||
}
|
||||
|
||||
if (key !== null) {
|
||||
const string_key = typeof key === 'string' ? key : await key (res.json);
|
||||
res.is_rsa = (/^-----BEGIN RSA PUBLIC KEY-----/u).test (string_key);
|
||||
res.hash = res.is_rsa
|
||||
? asym_verify (dec[0], string_key, res.token)
|
||||
: hash_sha512 (dec[0], string_key);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify a signed object and return its info and contents
|
||||
*
|
||||
* @param {string} str string to verify
|
||||
* @param {string|((Object)=>string|Promise<string>)} key used key
|
||||
* @param {number|((Object)=>number|Promise<number>)} timeout timeout (optional)
|
||||
* @returns {Promise<any>} returns object if successful, else null
|
||||
*/
|
||||
async function verify_signature_get_info (str, key, timeout = 0) {
|
||||
if (typeof str !== 'string')
|
||||
return null;
|
||||
const { json, token, hash, is_rsa } = await parse_signature (str, key);
|
||||
if (is_rsa ? !hash : (token !== hash))
|
||||
return null;
|
||||
const time = Date.now () - json.iat;
|
||||
const num_timeout = typeof timeout === 'number'
|
||||
? timeout
|
||||
: await timeout (json);
|
||||
if (num_timeout === 0 || time <= num_timeout)
|
||||
return json;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify a signed object and return its contents
|
||||
*
|
||||
* @param {string} str string to verify
|
||||
* @param {string|((Object)=>string|Promise<string>)} key used key
|
||||
* @param {number|((Object)=>number|Promise<number>)} timeout timeout (optional)
|
||||
* @returns {Promise<any>} returns object if successful, else null
|
||||
*/
|
||||
async function verify_signature (str, key, timeout = 0) {
|
||||
const res = await verify_signature_get_info (str, key, timeout);
|
||||
if (res === null)
|
||||
return null;
|
||||
return res.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a signed object info and data
|
||||
*
|
||||
* @param {string} str string to decode
|
||||
* @returns {Promise<any>} data
|
||||
*/
|
||||
async function get_signature_info (str) {
|
||||
if (typeof str !== 'string')
|
||||
return null;
|
||||
const { json } = await parse_signature (str);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* decode a signed object without verifying the signature
|
||||
*
|
||||
* @param {string} str string to decode
|
||||
* @returns {Promise<any>} object
|
||||
*/
|
||||
async function decode_signed (str) {
|
||||
const info = await get_signature_info (str);
|
||||
if (info)
|
||||
return info.obj;
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decode_signed,
|
||||
get_signature_info,
|
||||
sign_object,
|
||||
verify_signature,
|
||||
verify_signature_get_info
|
||||
};
|
53
package.json
53
package.json
@ -1,23 +1,50 @@
|
||||
{
|
||||
"name": "@scode/crypto-helper",
|
||||
"version": "1.0.0",
|
||||
"name": "@sapphirecode/crypto-helper",
|
||||
"version": "2.1.2",
|
||||
"main": "index.js",
|
||||
"author": "Timo Hocker <t-hocker@web.de>",
|
||||
"author": {
|
||||
"name": "Timo Hocker",
|
||||
"email": "timo@scode.ovh"
|
||||
},
|
||||
"bugs": "https://redmine.scode.ovh/projects/crypto-helper",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"cryptography",
|
||||
"crypto",
|
||||
"hashing"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.scode.ovh:timo/crypto-helper.git"
|
||||
},
|
||||
"description": "simple functions for cryptography",
|
||||
"devDependencies": {
|
||||
"@scode/eslint-config": "^1.2.25",
|
||||
"@stryker-mutator/core": "^2.5.0",
|
||||
"@stryker-mutator/javascript-mutator": "^2.5.0",
|
||||
"ava": "^3.5.0",
|
||||
"eslint": "^6.8.0",
|
||||
"nyc": "^15.0.0"
|
||||
"@sapphirecode/eslint-config": "^2.1.4",
|
||||
"@stryker-mutator/core": "^6.1.2",
|
||||
"@stryker-mutator/jasmine-runner": "^6.1.2",
|
||||
"@types/jasmine": "^4.0.3",
|
||||
"eslint": "^8.21.0",
|
||||
"jasmine": "^4.3.0",
|
||||
"nyc": "^15.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "nyc ava",
|
||||
"ci": "yarn && node jenkins.js"
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
|
||||
"test": "nyc jasmine --config=\"jasmine.json\"",
|
||||
"mutate": "stryker run",
|
||||
"compile": "tsc --allowJs --declaration --emitDeclarationOnly --lib es6 index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scode/encoding-helper": "^1.0.1"
|
||||
"@sapphirecode/encoding-helper": "^1.1.0",
|
||||
"scrypt-js": "^3.0.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"lib/*.js",
|
||||
"lib/*.d.ts",
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,29 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
mutator: "javascript",
|
||||
packageManager: "yarn",
|
||||
reporters: ["clear-text"],
|
||||
testRunner: "command",
|
||||
transpilers: [],
|
||||
coverageAnalysis: "all",
|
||||
mutate: ["index.js"]
|
||||
});
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of crypto-helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @type {import('@stryker-mutator/api/core').StrykerOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
packageManager: 'yarn',
|
||||
reporters: [
|
||||
'clear-text',
|
||||
'progress'
|
||||
],
|
||||
testRunner: 'jasmine',
|
||||
jasmineConfigFile: 'jasmine.json',
|
||||
coverageAnalysis: 'perTest',
|
||||
mutate: [
|
||||
'**/*.js',
|
||||
'**/*.ts',
|
||||
'!**/test/**/*',
|
||||
'!**/spec/**/*',
|
||||
'!stryker.conf.js'
|
||||
]
|
||||
};
|
||||
|
@ -1,50 +0,0 @@
|
||||
/* eslint-disable no-magic-numbers */
|
||||
// @ts-nocheck
|
||||
'use strict';
|
||||
|
||||
const test = require ('ava');
|
||||
const crypto = require ('../index');
|
||||
|
||||
test ('encryption', (t) => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
t.is (typeof enc, 'string');
|
||||
});
|
||||
|
||||
test ('decryption', (t) => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
const dec = crypto.decrypt_aes (enc, 'bar');
|
||||
t.is (dec, 'foo');
|
||||
});
|
||||
|
||||
test ('fail decryption', (t) => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
const dec = crypto.decrypt_aes (enc, 'baz');
|
||||
t.is (dec, null);
|
||||
});
|
||||
|
||||
test ('rethrow decryption', (t) => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
t.throws (() => {
|
||||
crypto.decrypt_aes (
|
||||
enc,
|
||||
'baz',
|
||||
crypto.encryption_mode_cbc_256,
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test ('unique crypto strings', (t) => {
|
||||
const enc = [
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar')
|
||||
];
|
||||
const unique = enc.filter ((v, i) => enc.indexOf (v) === i).length;
|
||||
t.is (unique, 8);
|
||||
});
|
121
test/index.js
121
test/index.js
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* Created by Timo Hocker <timo@scode.ovh>, March 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable no-magic-numbers */
|
||||
// @ts-nocheck
|
||||
'use strict';
|
||||
|
||||
const test = require ('ava');
|
||||
const crypto = require ('../index');
|
||||
|
||||
test ('random_hex', (t) => {
|
||||
const hex = crypto.random_hex (16);
|
||||
t.is (hex.length, 16);
|
||||
t.regex (hex, /^[0-9a-f]+$/iu);
|
||||
});
|
||||
|
||||
test ('random_hex with default length', (t) => {
|
||||
const hex = crypto.random_hex ();
|
||||
t.is (hex.length, 8);
|
||||
t.regex (hex, /^[0-9a-f]+$/iu);
|
||||
});
|
||||
|
||||
test ('random_hex should refuse lenght smaller 1', (t) => {
|
||||
t.throws (() => (crypto.random_hex (0)));
|
||||
});
|
||||
|
||||
test ('random_string', (t) => {
|
||||
const str = crypto.random_string (16);
|
||||
t.is (str.length, 16);
|
||||
});
|
||||
|
||||
test ('random_string with default length', (t) => {
|
||||
const str = crypto.random_string ();
|
||||
t.is (str.length, 8);
|
||||
});
|
||||
|
||||
test ('random_string should refuse lenght smaller 1', (t) => {
|
||||
t.throws (() => (crypto.random_string (0)));
|
||||
});
|
||||
|
||||
test ('hash_sha512', (t) => {
|
||||
const hash = crypto.hash_sha512 ('a', 'b');
|
||||
t.is (
|
||||
hash,
|
||||
// eslint-disable-next-line max-len
|
||||
'2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d'
|
||||
);
|
||||
});
|
||||
|
||||
test ('checksum', (t) => {
|
||||
const hash = crypto.checksum ('foo');
|
||||
t.is (
|
||||
hash,
|
||||
// eslint-disable-next-line max-len
|
||||
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
|
||||
);
|
||||
});
|
||||
|
||||
test ('create_salt', (t) => {
|
||||
const salt = crypto.create_salt ();
|
||||
t.is (salt.length, 64);
|
||||
t.regex (salt, /^[0-9a-f]+$/iu);
|
||||
});
|
||||
|
||||
test ('sign_object', (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
t.notThrows (() => {
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
t.is (typeof str, 'string');
|
||||
});
|
||||
});
|
||||
|
||||
test ('decode_signed', (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = crypto.decode_signed (str);
|
||||
t.deepEqual (obj, dec);
|
||||
});
|
||||
|
||||
test ('verify_signature', (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = crypto.verify_signature (str, 'baz');
|
||||
t.deepEqual (obj, dec);
|
||||
});
|
||||
|
||||
test ('reject tampered signatures', (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = crypto.verify_signature (str, 'foo');
|
||||
t.is (dec, null);
|
||||
});
|
||||
|
||||
test ('reject old signatures', async (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
await new Promise ((res) => {
|
||||
setTimeout (res, 10);
|
||||
});
|
||||
const dec = crypto.verify_signature (str, 'baz', 1);
|
||||
t.is (dec, null);
|
||||
});
|
||||
|
||||
test ('do not reject valid signatures', async (t) => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
await new Promise ((res) => {
|
||||
setTimeout (res, 10);
|
||||
});
|
||||
const dec = crypto.verify_signature (str, 'baz', 100);
|
||||
t.deepEqual (obj, dec);
|
||||
});
|
||||
|
||||
test ('decode problematic token', (t) => {
|
||||
// eslint-disable-next-line max-len
|
||||
const str = 'eyJpYXQiOjE1ODE0NDAwMTIyODgsIm9iaiI6eyJpZCI6MX19.24ZOsWrnfkNe%2FbM0r7DaVJMqE2bfn2aAM%2BZSzWeSf31OCTlXXNWD34RBL2X5v3UliYQ4IIsLNBFbaW9texPHug%3D%3D';
|
||||
const obj = crypto.decode_signed (str);
|
||||
t.deepEqual (obj, { id: 1 });
|
||||
});
|
116
test/spec/encryption.js
Normal file
116
test/spec/encryption.js
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of crypto-helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
// @ts-nocheck
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('encryption', () => {
|
||||
it ('encryption', () => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
expect (typeof enc)
|
||||
.toEqual ('string');
|
||||
});
|
||||
|
||||
it ('decryption', () => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
const dec = crypto.decrypt_aes (enc, 'bar');
|
||||
expect (dec)
|
||||
.toEqual ('foo');
|
||||
});
|
||||
|
||||
it ('encryption 128', () => {
|
||||
const enc = crypto.encrypt_aes (
|
||||
'foo',
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_128
|
||||
);
|
||||
expect (typeof enc)
|
||||
.toEqual ('string');
|
||||
});
|
||||
|
||||
it ('decryption 128', () => {
|
||||
const enc = crypto.encrypt_aes (
|
||||
'foo',
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_128
|
||||
);
|
||||
const dec = crypto.decrypt_aes (
|
||||
enc,
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_128
|
||||
);
|
||||
expect (dec)
|
||||
.toEqual ('foo');
|
||||
});
|
||||
|
||||
it ('encryption 256_quick', () => {
|
||||
const enc = crypto.encrypt_aes (
|
||||
'foo',
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_256_quick
|
||||
);
|
||||
expect (typeof enc)
|
||||
.toEqual ('string');
|
||||
});
|
||||
|
||||
it ('decryption 256_quick', () => {
|
||||
const enc = crypto.encrypt_aes (
|
||||
'foo',
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_256_quick
|
||||
);
|
||||
const dec = crypto.decrypt_aes (
|
||||
enc,
|
||||
'bar',
|
||||
crypto.encryption_mode_cbc_256_quick
|
||||
);
|
||||
expect (dec)
|
||||
.toEqual ('foo');
|
||||
});
|
||||
|
||||
it ('fail decryption', () => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
const dec = crypto.decrypt_aes (enc, 'baz');
|
||||
expect (dec)
|
||||
.toEqual (null);
|
||||
});
|
||||
|
||||
it ('rethrow decryption', () => {
|
||||
const enc = crypto.encrypt_aes ('foo', 'bar');
|
||||
expect (() => {
|
||||
crypto.decrypt_aes (
|
||||
enc,
|
||||
'baz',
|
||||
crypto.encryption_mode_cbc_256,
|
||||
true
|
||||
);
|
||||
})
|
||||
.toThrowError (
|
||||
// eslint-disable-next-line max-len
|
||||
/bad decrypt/ui
|
||||
);
|
||||
});
|
||||
|
||||
it ('unique crypto strings', () => {
|
||||
const enc = [
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar'),
|
||||
crypto.encrypt_aes ('foo', 'bar')
|
||||
];
|
||||
const unique = enc.filter ((v, i) => enc.indexOf (v) === i).length;
|
||||
expect (unique)
|
||||
.toEqual (8);
|
||||
});
|
||||
});
|
27
test/spec/hashing.js
Normal file
27
test/spec/hashing.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
|
||||
describe ('hashing', () => {
|
||||
it ('sha512', () => {
|
||||
const hash = crypto.hash_sha512 ('a', 'b');
|
||||
expect (
|
||||
hash
|
||||
)
|
||||
.toEqual (
|
||||
// eslint-disable-next-line max-len
|
||||
'2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d'
|
||||
);
|
||||
});
|
||||
|
||||
it ('checksum', () => {
|
||||
const hash = crypto.checksum ('foo');
|
||||
expect (
|
||||
hash
|
||||
)
|
||||
.toEqual (
|
||||
// eslint-disable-next-line max-len
|
||||
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
|
||||
);
|
||||
});
|
||||
});
|
16
test/spec/pbkdf.js
Normal file
16
test/spec/pbkdf.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
|
||||
describe ('hashing', () => {
|
||||
it ('scrypt', async () => {
|
||||
const hash = await crypto.pbkdf_scrypt ('a', 'b');
|
||||
expect (
|
||||
hash
|
||||
)
|
||||
.toEqual (
|
||||
// eslint-disable-next-line max-len
|
||||
'f70baff6e80e83a2fc5af752f4b57b6b73442f913fc6446b6cb62e3f5f56f9b18c51c3461081239fdac6e9f8498a95ecfabb0b927db8a9ee2de80ed40469b034'
|
||||
);
|
||||
});
|
||||
});
|
72
test/spec/random.js
Normal file
72
test/spec/random.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('random', () => {
|
||||
it ('random_hex', () => {
|
||||
const hex = crypto.random_hex (16);
|
||||
expect (hex.length)
|
||||
.toEqual (16);
|
||||
expect (hex)
|
||||
.toMatch (/^[0-9a-f]+$/iu);
|
||||
});
|
||||
|
||||
it ('random_hex with default length', () => {
|
||||
const hex = crypto.random_hex ();
|
||||
expect (hex.length)
|
||||
.toEqual (8);
|
||||
expect (hex)
|
||||
.toMatch (/^[0-9a-f]+$/iu);
|
||||
});
|
||||
|
||||
it ('random_hex should refuse length smaller 1', () => {
|
||||
expect (
|
||||
() => (crypto.random_hex (0))
|
||||
)
|
||||
.toThrowError ('invalid length');
|
||||
});
|
||||
|
||||
it ('random_hex should always return correct length', () => {
|
||||
for (let i = 1; i < 32; i++) {
|
||||
const hex = crypto.random_hex (i);
|
||||
expect (hex.length)
|
||||
.toEqual (i);
|
||||
}
|
||||
});
|
||||
|
||||
it ('random_string', () => {
|
||||
const str = crypto.random_string (16);
|
||||
expect (str.length)
|
||||
.toEqual (16);
|
||||
});
|
||||
|
||||
it ('random_string with default length', () => {
|
||||
const str = crypto.random_string ();
|
||||
expect (str.length)
|
||||
.toEqual (8);
|
||||
});
|
||||
|
||||
it ('random_string should refuse length smaller 1', () => {
|
||||
expect (
|
||||
() => (crypto.random_string (0))
|
||||
)
|
||||
.toThrowError ('invalid length');
|
||||
});
|
||||
|
||||
it ('random_string should always return correct length', () => {
|
||||
for (let i = 1; i < 32; i++) {
|
||||
const str = crypto.random_string (i);
|
||||
expect (str.length)
|
||||
.toEqual (i);
|
||||
}
|
||||
});
|
||||
|
||||
it ('create_salt', () => {
|
||||
const salt = crypto.create_salt ();
|
||||
expect (salt.length)
|
||||
.toEqual (64);
|
||||
expect (salt)
|
||||
.toMatch (/^[0-9a-f]+$/iu);
|
||||
});
|
||||
});
|
133
test/spec/rsa.js
Normal file
133
test/spec/rsa.js
Normal file
@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
|
||||
const key_length = 512;
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('rsa', () => {
|
||||
it ('should create a keypair', async () => {
|
||||
const k = await crypto.generate_keypair ();
|
||||
|
||||
expect (k.private_key)
|
||||
.toMatch (/^-----BEGIN PRIVATE KEY-----.+-----END PRIVATE KEY-----\n$/su);
|
||||
expect (k.public_key)
|
||||
// eslint-disable-next-line max-len
|
||||
.toMatch (/^-----BEGIN RSA PUBLIC KEY-----.+-----END RSA PUBLIC KEY-----\n$/su);
|
||||
});
|
||||
|
||||
it ('should throw on too small key size', async () => {
|
||||
await expectAsync (crypto.generate_keypair (16))
|
||||
.toBeRejectedWithError (
|
||||
/key size too small/ui
|
||||
);
|
||||
});
|
||||
|
||||
it ('should encrypt and decrypt', async () => {
|
||||
const k = await crypto.generate_keypair (key_length);
|
||||
const data = 'foobar';
|
||||
const enc = crypto.asym_encrypt (data, k.public_key);
|
||||
const dec = crypto.asym_decrypt (enc, k.private_key);
|
||||
expect (dec)
|
||||
.toEqual (data);
|
||||
expect (enc).not.toEqual (data);
|
||||
});
|
||||
|
||||
it ('should throw if encryption key is invalid', () => {
|
||||
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
|
||||
+ 'MEgCCQDGvKLaq1SB/BTzocR4ZqGNr8dz1ylxxUpDncCu0C/ayOGPnCilB0LGEdqK\n'
|
||||
+ 'rORlsYpIaDvLv6x6k8iSg6A/TsybAgMBAAE=\n'
|
||||
+ '-----END RSA PUBLIC KEY-----\n';
|
||||
const data = 'foobar';
|
||||
expect (() => crypto.asym_encrypt (data, key))
|
||||
.toThrow ();
|
||||
});
|
||||
|
||||
it ('should throw on invalid decryption key', () => {
|
||||
const data = 'EHsGr2eSVqZKTX1U9Qj4crGRFkk299kmxhiiO3fyaIS'
|
||||
+ 'olB9+UYDdqrwcf/INwNddW4AzjA2Kf4dYaXIRLZsU1Q==';
|
||||
const key = '-----BEGIN PRIVATE KEY-----\n'
|
||||
+ 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxryi2qtUgfwU86HE\n'
|
||||
+ 'eGahja/Hc9cpccVKQ53ArtXv2sjhj5wopQdCxhHaiqzkZbGKSGg7y7+sepPIkoOg\n'
|
||||
+ 'P07MmwIDAQABAkBKIk3hoi24+07ZfwuqGibDksGlLarxHLZSOMOKsnBXfRRyGqMr\n'
|
||||
+ '/Z+qtQ9VPRWHBzGHZ9rXAVKa8gnRirik+ez5AiEA/XL6tOy92Yvxm46ewswmZ7ab\n'
|
||||
+ 'V1KvChsXziaRj5eLzacCIQDIvLBO8og5Ng4r7E/dOAYrvzOLFqlN5UCuCRZzuFpv\n'
|
||||
+ '7QIgCnj5ywgNQDP8I8Vc4ge1fouZF56fBPfhn+8QDLLiX/kCIHewKd+otJiIJoMB\n'
|
||||
+ '78yTLvq+klkINgKAAsTCHmT5MtMxAiEAkFE70ms8C73JvTkd0znq8H6fBJV0iZxQ\n'
|
||||
+ 'qOXON8bzv8A=\n'
|
||||
+ '-----END PRIVATE KEY-----\n';
|
||||
expect (() => crypto.asym_decrypt (data, key))
|
||||
.toThrow ();
|
||||
});
|
||||
|
||||
it ('should throw on invalid decryption data', () => {
|
||||
const data = 'EHsGr2eSkqZKTX1U9Qj4crGRFkk299kmxhiiO3fyaIS'
|
||||
+ 'olB9+UYDdqrwcf/INwNddW4AzjA2Kf4dYaXIRLZsU1Q==';
|
||||
const key = '-----BEGIN PRIVATE KEY-----\n'
|
||||
+ 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxryi2qtUgfwU86HE\n'
|
||||
+ 'eGahja/Hc9cpccVKQ53ArtAv2sjhj5wopQdCxhHaiqzkZbGKSGg7y7+sepPIkoOg\n'
|
||||
+ 'P07MmwIDAQABAkBKIk3hoi24+07ZfwuqGibDksGlLarxHLZSOMOKsnBXfRRyGqMr\n'
|
||||
+ '/Z+qtQ9VPRWHBzGHZ9rXAVKa8gnRirik+ez5AiEA/XL6tOy92Yvxm46ewswmZ7ab\n'
|
||||
+ 'V1KvChsXziaRj5eLzacCIQDIvLBO8og5Ng4r7E/dOAYrvzOLFqlN5UCuCRZzuFpv\n'
|
||||
+ '7QIgCnj5ywgNQDP8I8Vc4ge1fouZF56fBPfhn+8QDLLiX/kCIHewKd+otJiIJoMB\n'
|
||||
+ '78yTLvq+klkINgKAAsTCHmT5MtMxAiEAkFE70ms8C73JvTkd0znq8H6fBJV0iZxQ\n'
|
||||
+ 'qOXON8bzv8A=\n'
|
||||
+ '-----END PRIVATE KEY-----\n';
|
||||
expect (() => crypto.asym_decrypt (data, key))
|
||||
.toThrow ();
|
||||
});
|
||||
|
||||
it ('should create a signature and verify it', async () => {
|
||||
const data = 'foobar';
|
||||
const k = await crypto.generate_keypair (key_length);
|
||||
const signature = crypto.asym_sign (data, k.private_key);
|
||||
expect (typeof signature)
|
||||
.toEqual ('string');
|
||||
expect (signature)
|
||||
.toMatch (/^[a-f0-9]+$/ui);
|
||||
expect (crypto.asym_verify (data, k.public_key, signature))
|
||||
.toBeTrue ();
|
||||
});
|
||||
|
||||
it ('should throw on invalid sign key', () => {
|
||||
const key = '-----BEGIN PRIVATE KEY-----\n'
|
||||
+ 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAl4RZveQ8IXHPZTUf\n'
|
||||
+ 'djwgDxIB5444yFvhIRasdasdasdApz+FmsXxGCatnPUCIHO9P1EfxasdaIq/Lpng\n'
|
||||
+ 'y2ZQh2IaDYxC7K7tvGZwSY/CEcGfAiAcNgsg5ZMIQOWxn2KbFM81ne3b5FzD3Ozh\n'
|
||||
+ 'GxItcBfKuw==\n'
|
||||
+ '-----END PRIVATE KEY-----\n';
|
||||
const data = 'foobar';
|
||||
expect (() => crypto.asym_sign (data, key))
|
||||
.toThrow ();
|
||||
});
|
||||
|
||||
it ('should throw on invalid verify key', () => {
|
||||
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
|
||||
+ 'MEgCQQCXhFm95DwDcc9lNasdasdasdasdasdHhEqigZupsWUxX/3tKn9tvEKV/\n'
|
||||
+ 'zjoENWN63aorN+O+5MxDZMnEk+z9AgMBAAE=\n'
|
||||
+ '-----END RSA PUBLIC KEY-----\n';
|
||||
const data = 'foobar';
|
||||
const signature = '3433ef165d6be80430e1107be0d7183bee769dbb38ea7d2737'
|
||||
+ '1429c853fcab78bf1d3256fc16ee93a38cfd4ae79a74748e59fe9e9a65400c720d'
|
||||
+ 'adb2dbcc1fa3';
|
||||
expect (() => crypto.asym_verify (data, key, signature))
|
||||
.toThrow ();
|
||||
});
|
||||
|
||||
it ('should not throw on invalid data or signature', () => {
|
||||
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
|
||||
+ 'MEgCQQC+9nw80cG+AsZ2euIZx4ptmUykJgEUgs4JiEvC+IaiIRAd9zGc0TcQAeND\n'
|
||||
+ '171lw77kE02KJ4ARl1hkcLCW2bIrAgMBAAE=\n'
|
||||
+ '-----END RSA PUBLIC KEY-----\n';
|
||||
const data = 'foobar';
|
||||
const signature = '6bae51b0449d71ca2e04099268bd0f6506a5dfc6f810bd72d'
|
||||
+ '47865574a99910e404e5856da650cd45ee88365a2511fcc0866a0b5d1faf15c067'
|
||||
+ 'ab8a4427554bf';
|
||||
expect (crypto.asym_verify (data, key, signature))
|
||||
.toBeTrue ();
|
||||
expect (crypto.asym_verify (data.replace ('b', 'c'), key, signature))
|
||||
.toBeFalse ();
|
||||
expect (crypto.asym_verify (data, key, signature.replace ('f', 'e')))
|
||||
.toBeFalse ();
|
||||
});
|
||||
});
|
262
test/spec/signatures.js
Normal file
262
test/spec/signatures.js
Normal file
@ -0,0 +1,262 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require ('../../index');
|
||||
const rsa = require ('../../lib/rsa');
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('signatures', () => {
|
||||
beforeEach (() => {
|
||||
jasmine.clock ()
|
||||
.install ();
|
||||
const base_time = (new Date);
|
||||
jasmine.clock ()
|
||||
.mockDate (base_time);
|
||||
});
|
||||
|
||||
afterEach (() => {
|
||||
jasmine.clock ()
|
||||
.uninstall ();
|
||||
});
|
||||
|
||||
it ('sign_object', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
await expectAsync ((async () => {
|
||||
const str = await crypto.sign_object (obj, 'baz');
|
||||
expect (typeof str)
|
||||
.toEqual ('string');
|
||||
}) ())
|
||||
.toBeResolved ();
|
||||
});
|
||||
|
||||
it ('sign_object with rsa key', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const k = await rsa.generate_keypair ();
|
||||
await expectAsync ((async () => {
|
||||
const str = await crypto.sign_object (obj, k.private_key);
|
||||
expect (typeof str)
|
||||
.toEqual ('string');
|
||||
}) ())
|
||||
.toBeResolved ();
|
||||
});
|
||||
|
||||
it ('verify_signature with rsa key', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt'
|
||||
+ '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X'
|
||||
+ 'LjRUkc2M8M84LmWLg76EvcY2pw6HwsFvCUoZYcKAJp3vkp9MQVrVYdHKMPkBjQKyy2V'
|
||||
+ 'KtEZiBsomBVJd6Hudd1YLMQ4J4s52iHsegDswKE9djYVEmgKkJUAiZJ2viFHw3fBbp2'
|
||||
+ 'Abo2Dm5oqYtw7Nn9RFstW3CcNQHV1PzHDKD56Uw3opuYwVZhQth8ux2CdkC2yMvgVsT'
|
||||
+ 'dUyCuu78ugaGvzsMXCbe2BzaPFDTE9JYtMcDFFP43nUGHNd6cWwzoKTZBX852Exz6Rb'
|
||||
+ 'VjcWUvL81dLPBLJV.2';
|
||||
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
|
||||
+ 'MIIBCgKCAQEA4LCEoJYNwwksuzPESpmPziHp98WhY5Qml6RiN9uxrKGPV6QwwmDQ\n'
|
||||
+ 'ks6C+ZfYbFG9NCx1MEuWL0Tvp/6ZBhMyaJrI5iwo0CmSX3WdFcbXmdl0l6N1+5r7\n'
|
||||
+ 'l3SkKsr/AX4gwcDor4dYuLEv5KawGdfcP0IxsoAcIN1UJ5HJ+eheB3fVcSh/IIBf\n'
|
||||
+ 'O+cL/4Chw8eAaDBG5mZ1Xgd4gIjJGYAxgUNvaShGzs8k1y+jqjD5IkZ1h9dgoGJG\n'
|
||||
+ 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n'
|
||||
+ '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n'
|
||||
+ '-----END RSA PUBLIC KEY-----\n';
|
||||
const ver = await crypto.verify_signature (str, key);
|
||||
expect (ver)
|
||||
.toEqual (obj);
|
||||
});
|
||||
|
||||
it ('verify_signature reject with rsa key', async () => {
|
||||
const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt'
|
||||
+ '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X'
|
||||
+ 'LjRUkc2M8M84LmWLg76EvcY2pw6HwsFvCUoZYcKAJp3vkp9MQVrVYdHKMPkBjQKyy2V'
|
||||
+ 'KtEZiBsomBVJd6Hudd1YLMQ4J4s52iHsegDswKE9djYVEmgKkJUAiZJ2viFHw3fBbp2'
|
||||
+ 'Abo2Dm5oqYtw7Nn9RFstW3CcNQHV1PzHDKD56Uw3opuYwVZhQth8ux2CdkC2yMvgVsT'
|
||||
+ 'dUyCuu78ugaGvzsMXCbe2BzaPFDTE9JYtMcDFFP43nUGHNd6cWwzoKTZBX852Exz6Rb'
|
||||
+ 'VjcWUvL81dLPBLJA.2';
|
||||
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
|
||||
+ 'MIIBCgKCAQEA4LCEoJYNwwksuzPESpmPziHp98WhY5Qml6RiN9uxrKGPV6QwwmDQ\n'
|
||||
+ 'ks6C+ZfYbFG9NCx1MEuWL0Tvp/6ZBhMyaJrI5iwo0CmSX3WdFcbXmdl0l6N1+5r7\n'
|
||||
+ 'l3SkKsr/AX4gwcDor4dYuLEv5KawGdfcP0IxsoAcIN1UJ5HJ+eheB3fVcSh/IIBf\n'
|
||||
+ 'O+cL/4Chw8eAaDBG5mZ1Xgd4gIjJGYAxgUNvaShGzs8k1y+jqjD5IkZ1h9dgoGJG\n'
|
||||
+ 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n'
|
||||
+ '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n'
|
||||
+ '-----END RSA PUBLIC KEY-----\n';
|
||||
const ver = await crypto.verify_signature (str, key);
|
||||
expect (ver)
|
||||
.toBeNull ();
|
||||
});
|
||||
|
||||
it ('should sign object with key info', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
await expectAsync ((async () => {
|
||||
const str = crypto.sign_object (obj, 'baz', 'baz');
|
||||
const res = await crypto.get_signature_info (str);
|
||||
expect (res.key_info)
|
||||
.toEqual ('baz');
|
||||
}) ())
|
||||
.toBeResolved ();
|
||||
});
|
||||
|
||||
it ('should sign object with custom properties', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
await expectAsync ((async () => {
|
||||
const str = crypto.sign_object (obj, 'baz', { bar: 'baz' });
|
||||
const res = await crypto.get_signature_info (str);
|
||||
expect (res.bar)
|
||||
.toEqual ('baz');
|
||||
}) ())
|
||||
.toBeResolved ();
|
||||
});
|
||||
|
||||
it ('should sign object with custom override properties', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
await expectAsync ((async () => {
|
||||
const str = crypto.sign_object (obj, 'baz', { iat: 'baz' });
|
||||
const res = await crypto.get_signature_info (str);
|
||||
expect (res.iat)
|
||||
.toEqual ('baz');
|
||||
}) ())
|
||||
.toBeResolved ();
|
||||
});
|
||||
|
||||
it ('decode_signed', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = await crypto.decode_signed (str);
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('verify_signature', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = await crypto.verify_signature (str, 'baz');
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('should verify and return all info', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz', { iat: 'baz' });
|
||||
const dec = await crypto.verify_signature_get_info (str, 'baz');
|
||||
expect (dec.obj)
|
||||
.toEqual (obj);
|
||||
expect (dec.iat)
|
||||
.toEqual ('baz');
|
||||
});
|
||||
|
||||
it ('should verify signature using function retrieved key', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = await crypto.verify_signature (str, () => 'baz');
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it (
|
||||
'should verify signature using function retrieved timeout 0',
|
||||
async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = await crypto.verify_signature (str, 'baz', () => 0);
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
}
|
||||
);
|
||||
|
||||
it ('should reject tampered signatures', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
const dec = await crypto.verify_signature (str, 'foo');
|
||||
expect (dec)
|
||||
.toEqual (null);
|
||||
});
|
||||
|
||||
it ('should return null on invalid input', async () => {
|
||||
const ver = await crypto.verify_signature (null, 'foo');
|
||||
expect (ver)
|
||||
.toEqual (null);
|
||||
const dec = await crypto.decode_signed (null);
|
||||
expect (dec)
|
||||
.toEqual (null);
|
||||
});
|
||||
|
||||
it ('should not fail verification if timeout unspecified', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (36e5);
|
||||
|
||||
const dec = await crypto.verify_signature (str, 'baz');
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('should reject old signatures', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (50);
|
||||
|
||||
const dec = await crypto.verify_signature (str, 'baz', 1);
|
||||
expect (dec)
|
||||
.toEqual (null);
|
||||
});
|
||||
|
||||
it ('should not reject valid signatures', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (50);
|
||||
|
||||
const dec = await crypto.verify_signature (str, 'baz', 100);
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('should verify signature using function retrieved timeout', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz', { to: 100 });
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (50);
|
||||
|
||||
const dec = await crypto.verify_signature (str, 'baz', (info) => info.to);
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('verify_signature on almost timed out packet', async () => {
|
||||
const obj = { foo: 'bar' };
|
||||
const str = crypto.sign_object (obj, 'baz');
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (10);
|
||||
const dec = await crypto.verify_signature (str, 'baz', 10);
|
||||
expect (obj)
|
||||
.toEqual (dec);
|
||||
});
|
||||
|
||||
it ('should decode problematic token', async () => {
|
||||
// eslint-disable-next-line max-len
|
||||
const str = 'wEJbzvUywiaiGWZUG6CtCXNkNmRGyVoi9icytpTe4gZhsb8Gk.5PZbhGL525mdV7EmYomTwUei6qULpLaZwSXy92eaUDNgbyXPHsr9dfUCeEBpTqmzuq3VtmmV43epUyWRoHocAsV3.2';
|
||||
const obj = await crypto.decode_signed (str);
|
||||
expect (obj)
|
||||
.toEqual ({ id: 1 });
|
||||
});
|
||||
|
||||
it ('should automatically reencode b64 tokens', async () => {
|
||||
// eslint-disable-next-line max-len
|
||||
const str = 'eyJpYXQiOjE1ODE0NDAwMTIyODgsIm9iaiI6eyJpZCI6MX19.24ZOsWrnfkNe%2FbM0r7DaVJMqE2bfn2aAM%2BZSzWeSf31OCTlXXNWD34RBL2X5v3UliYQ4IIsLNBFbaW9texPHug%3D%3D';
|
||||
const obj = await crypto.decode_signed (str);
|
||||
expect (obj)
|
||||
.toEqual ({ id: 1 });
|
||||
});
|
||||
|
||||
it ('verify_signature on b64 string', async () => {
|
||||
// eslint-disable-next-line max-len
|
||||
const str = 'eyJpYXQiOjE2MDkzNDQ4MDMyMjcsIm9iaiI6eyJpZCI6MX19.N762xuMaNbT%2Fqb0uTKST68BZgSnmNxXaHl4GY7iAKqaDDEwZn3biYfg5DgJ45QgPZrndchczDjUqLkyXoqw4KQ%3D%3D';
|
||||
const obj = await crypto.verify_signature (str, 'baz');
|
||||
expect (obj)
|
||||
.toEqual ({ id: 1 });
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user