mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-07-17 08:52:03 +00:00
Compare commits
295 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dee91b8d97 | ||
|
a05e870c45 | ||
|
bc681c76f4 | ||
|
1c16dd3dec | ||
|
4e5a05a12c | ||
|
c067685f45 | ||
|
bc337698b6 | ||
|
b07978dbea | ||
|
0cc270dc4a | ||
|
b28c232e22 | ||
|
04a4e81317 | ||
|
29df292338 | ||
|
253cca2799 | ||
|
afcffc8115 | ||
|
a0f387aeab | ||
|
8b3dc56dc2 | ||
|
7888afada6 | ||
|
7273739f04 | ||
|
609297be65 | ||
|
89a297793d | ||
|
32fae9b505 | ||
|
c2dd0a535d | ||
|
2f18a077b4 | ||
|
4aa77a6b12 | ||
|
210dd27479 | ||
|
437a76fbe3 | ||
|
75d250c876 | ||
|
e761427153 | ||
|
90d51ee8c7 | ||
|
ccda21fe91 | ||
|
206999ce11 | ||
|
3272688489 | ||
|
456a365378 | ||
|
42bd594068 | ||
|
ab12e6f068 | ||
|
6bbf12c169 | ||
|
a68fc2e98d | ||
|
d73a0ca52e | ||
|
1b0fac84a8 | ||
|
efaafa9c06 | ||
|
88b3018c9c | ||
|
9aacb478c4 | ||
|
269d169f7c | ||
|
c956d1ad2a | ||
|
9b4231eb75 | ||
|
fdab19b7d9 | ||
|
35f196ea4d | ||
|
d3601fa936 | ||
|
f01e3812e9 | ||
|
00477e3bcb | ||
|
0f4c533dfa | ||
|
d566e7ef3b | ||
|
78e2ddd2bd | ||
|
0ad513887a | ||
|
e7468d830d | ||
|
ae109d46f7 | ||
|
41c9cd1691 | ||
|
3e88839c2b | ||
|
cc2094975b | ||
|
ad4bf3b357 | ||
|
730d762717 | ||
|
e01977c5a3 | ||
|
89901f7097 | ||
|
9894c34f94 | ||
|
e36a9f6b79 | ||
|
b5d94ecae7 | ||
|
1f9c2ddadb | ||
|
d6d06a8404 | ||
|
8b8d0c1510 | ||
|
b998f63aec | ||
|
adc6eb478c | ||
|
2c1bac5ce9 | ||
|
027a5a9332 | ||
|
2c294b56ab | ||
|
487cd076fb | ||
|
b8e2414420 | ||
|
9f747a173f | ||
|
34c5f5c8f0 | ||
|
a008bc2fcb | ||
|
b68060388f | ||
|
afe94ded6b | ||
|
a5e05603ef | ||
|
0b686d363c | ||
|
2d15e717e4 | ||
|
6775dbf670 | ||
|
4b09aae8ca | ||
|
764d8bff3e | ||
|
26b6217041 | ||
|
5500ac4a6e | ||
|
d675670ed9 | ||
|
ad7107233e | ||
|
a521cd9b11 | ||
|
ed670209ad | ||
|
1974eb92be | ||
|
fbd42385e3 | ||
|
9f01868c6c | ||
|
3bb84514d1 | ||
|
5cd0e8cc1a | ||
|
3f131d4a0a | ||
|
0ffe31821a | ||
|
7b3625888c | ||
|
b7bce77ad5 | ||
|
c1f867bd9c | ||
|
e3f02eb6f1 | ||
|
a0874389a9 | ||
|
ce22cf13f0 | ||
|
5fc391c8e0 | ||
|
53a2b590a9 | ||
|
f4dbd62e49 | ||
|
567d68c855 | ||
|
af782c5906 | ||
|
f0593c9e6d | ||
|
8d8294dc3f | ||
|
3a8bab9f44 | ||
|
c983edfdb9 | ||
|
6c9928abd6 | ||
|
720246f012 | ||
|
cfdcbe08d4 | ||
|
df23d634c5 | ||
|
88e1bcf75f | ||
|
c54ea206f0 | ||
|
857d2bd902 | ||
|
200110cb9d | ||
|
9e5778694c | ||
|
87e8f1c86f | ||
|
df75980a88 | ||
|
934390acd3 | ||
|
8b80b46667 | ||
|
e8efad546f | ||
|
e8cbf13d85 | ||
|
c7e0409c1c | ||
|
f4c00893ad | ||
|
b05e77f375 | ||
|
1602c440ad | ||
|
937cc76714 | ||
|
ad478454d8 | ||
|
8c69ffb20f | ||
|
e689a402a3 | ||
|
4bd032a6ae | ||
|
50c61ba46e | ||
|
3a90f70350 | ||
|
743c69524c | ||
|
1a347fa04c | ||
|
71339e08e7 | ||
|
0ab2c2d2d6 | ||
|
cdcca5f828 | ||
|
651d08f67e | ||
|
f2b67f7d82 | ||
|
2c0dc706b7 | ||
|
21d4c8b74e | ||
|
285b6ca392 | ||
|
7d96dd3243 | ||
|
b3421284f9 | ||
|
f3cb8ced36 | ||
|
0a6f63dce5 | ||
|
0ce5f34a08 | ||
|
a826968e71 | ||
|
7608fdd858 | ||
|
bf9b532067 | ||
|
5296f8a42f | ||
|
3a91ae2ed8 | ||
|
e7c11a8e01 | ||
|
9bb96dc2bf | ||
|
957fdd37e9 | ||
|
83257bc4bd | ||
|
cb7fae7fcc | ||
|
7669847c17 | ||
|
dc2793f138 | ||
|
e0b916ace9 | ||
|
0a71af7b89 | ||
|
46adafb207 | ||
|
9e977c7d44 | ||
|
f20267b962 | ||
|
98b285a840 | ||
|
ce5fb8c1b9 | ||
|
0dcf1a6f52 | ||
|
1b6a070fa8 | ||
|
1471e07bf9 | ||
|
bc554d1407 | ||
|
83d2d52205 | ||
|
301c779f49 | ||
|
d552fd423a | ||
|
8401154102 | ||
|
306504c386 | ||
|
bf80e8e511 | ||
|
4e619e60f5 | ||
|
838ecdbaef | ||
|
d14c11310e | ||
|
41c03a86a5 | ||
|
a958f52a5c | ||
|
dfc476c4f6 | ||
|
71eb33a44f | ||
|
310733726e | ||
|
1013becd66 | ||
|
c21454c4e8 | ||
|
4ee48a737a | ||
|
0efc94bd20 | ||
|
68f4b5f496 | ||
|
2f8e234044 | ||
|
363cda56da | ||
|
c2c6fde394 | ||
|
14dde32177 | ||
|
5bc30297de | ||
|
a5ad8cf444 | ||
|
be64372a5e | ||
|
2b0b7abd78 | ||
|
17f2065d21 | ||
|
2f065167fa | ||
|
e0ecce18ce | ||
|
cea1140ec6 | ||
|
e5b7c1f622 | ||
|
20326199d9 | ||
|
4e56e1724f | ||
|
d8b0c74ec9 | ||
|
647fab7170 | ||
|
e66383137f | ||
|
7d63f698c0 | ||
|
a49df7786c | ||
|
c73adb00cc | ||
|
638ea963e6 | ||
|
35abf8fcb4 | ||
|
9430e7f91e | ||
|
8a95de4700 | ||
|
c35a65133f | ||
|
4eb4aa05de | ||
|
21f96a4eb7 | ||
|
39b5124526 | ||
|
93e50dde10 | ||
|
e383da5d44 | ||
|
9b687f380c | ||
|
82abede64a | ||
|
42678bba30 | ||
|
f637ff2485 | ||
|
a56ab406e4 | ||
|
d753941c6e | ||
|
6022eb0838 | ||
|
4c2d42984d | ||
|
de8b16b00b | ||
|
73b8b39de5 | ||
|
288c9cff8f | ||
|
418a0885ad | ||
|
4c744d3b60 | ||
|
4c36aeba17 | ||
|
1c6d0912cd | ||
|
e18804c31e | ||
|
133a2c8833 | ||
|
fad0865b2b | ||
|
4edef07182 | ||
|
19c6ce7c06 | ||
|
3fa76a874f | ||
|
6e0fd6f257 | ||
|
c91d9b61c8 | ||
|
76eeb5aa18 | ||
|
843b5e33d6 | ||
|
308ac7cd1a | ||
|
6c8f978ea4 | ||
|
b93b410357 | ||
|
99a5245fa3 | ||
|
34856d58ab | ||
|
d59c6af7c0 | ||
|
1683bf1dc8 | ||
|
a4edf8b99b | ||
|
27524354af | ||
|
a1f054ca8e | ||
|
5c61c89391 | ||
|
e57e4ffa0d | ||
|
c138a04d2d | ||
|
7c913c0769 | ||
|
1a2d468369 | ||
|
98bc0bbc5f | ||
|
c45bdf602e | ||
|
e92bab1736 | ||
|
c57d1e4d4f | ||
|
04682acd86 | ||
|
fba4833aef | ||
|
2842df7944 | ||
|
74c0d28f86 | ||
|
cf7ed6fa49 | ||
|
c1ffa41697 | ||
|
933119445f | ||
|
a4e6f9dd83 | ||
|
e252db300c | ||
|
98b37d49c4 | ||
|
f979fcd3c2 | ||
|
0af48bbebc | ||
|
3f9d8d557c | ||
|
91a3b50ac9 | ||
|
148d16ab25 | ||
|
6d15450438 | ||
|
ebe1cecdeb | ||
|
904cfb27bd | ||
|
7790beb207 | ||
|
6308461f0f | ||
|
95a0f1f0c2 | ||
|
0c64122342 |
3
.aegir.js
Normal file
3
.aegir.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
bundlesize: { maxSize: '124kB' }
|
||||
}
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,6 +1,11 @@
|
||||
docs
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
**/node_modules/
|
||||
**/*.log
|
||||
test/repo-tests*
|
||||
**/bundle.js
|
||||
|
||||
# Logs
|
||||
logs
|
||||
@@ -31,4 +36,12 @@ build
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
lib
|
||||
dist
|
||||
test/test-data/go-ipfs-repo/LOCK
|
||||
test/test-data/go-ipfs-repo/LOG
|
||||
test/test-data/go-ipfs-repo/LOG.old
|
||||
|
||||
# while testing npm5
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
34
.npmignore
34
.npmignore
@@ -1,34 +0,0 @@
|
||||
**/node_modules/
|
||||
**/*.log
|
||||
test/repo-tests*
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
coverage
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
build
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
test
|
71
.travis.yml
71
.travis.yml
@@ -1,38 +1,45 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
|
||||
matrix:
|
||||
cache: npm
|
||||
|
||||
stages:
|
||||
- check
|
||||
- test
|
||||
- cov
|
||||
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
script: npx nyc -s npm run test:node -- --bail
|
||||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- node_js: 4
|
||||
env: CXX=g++-4.8
|
||||
- node_js: 6
|
||||
env:
|
||||
- SAUCE=true
|
||||
- CXX=g++-4.8
|
||||
- node_js: stable
|
||||
env: CXX=g++-4.8
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir build --bundlesize
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
# Make sure we have new NPM.
|
||||
before_install:
|
||||
- npm install -g npm
|
||||
- stage: test
|
||||
name: chrome
|
||||
addons:
|
||||
chrome: stable
|
||||
script:
|
||||
- npx aegir test -t browser
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
- npm run coverage
|
||||
- stage: test
|
||||
name: firefox
|
||||
addons:
|
||||
firefox: latest
|
||||
script:
|
||||
- npx aegir test -t browser -- --browsers FirefoxHeadless
|
||||
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
|
||||
after_success:
|
||||
- npm run coverage-publish
|
||||
|
||||
addons:
|
||||
firefox: 'latest'
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
notifications:
|
||||
email: false
|
||||
|
402
CHANGELOG.md
Normal file
402
CHANGELOG.md
Normal file
@@ -0,0 +1,402 @@
|
||||
<a name="0.19.2"></a>
|
||||
## [0.19.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.19.1...v0.19.2) (2021-03-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ed25519 PeerID generation ([#186](https://github.com/libp2p/js-libp2p-crypto/issues/186)) ([1c16dd3](https://github.com/libp2p/js-libp2p-crypto/commit/1c16dd3)), closes [ipfs/js-ipfs#3591](https://github.com/ipfs/js-ipfs/issues/3591) [libp2p/js-libp2p-crypto#185](https://github.com/libp2p/js-libp2p-crypto/issues/185)
|
||||
|
||||
|
||||
|
||||
<a name="0.19.1"></a>
|
||||
## [0.19.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.19.0...v0.19.1) (2021-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ed25519 key ID generation ([bc33769](https://github.com/libp2p/js-libp2p-crypto/commit/bc33769))
|
||||
|
||||
|
||||
|
||||
<a name="0.19.0"></a>
|
||||
# [0.19.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.18.0...v0.19.0) (2021-01-15)
|
||||
|
||||
|
||||
|
||||
<a name="0.18.0"></a>
|
||||
# [0.18.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.9...v0.18.0) (2020-08-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove rendundant public key ([#181](https://github.com/libp2p/js-libp2p-crypto/issues/181)) ([afcffc8](https://github.com/libp2p/js-libp2p-crypto/commit/afcffc8))
|
||||
* replace node buffers with uint8arrays ([#180](https://github.com/libp2p/js-libp2p-crypto/issues/180)) ([a0f387a](https://github.com/libp2p/js-libp2p-crypto/commit/a0f387a))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The private ed25519 key will no longer include the redundant public key
|
||||
|
||||
* chore: fix lint
|
||||
* - Where node Buffers were returned, now Uint8Arrays are
|
||||
|
||||
* chore: remove commented code
|
||||
|
||||
|
||||
|
||||
<a name="0.17.9"></a>
|
||||
## [0.17.9](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.8...v0.17.9) (2020-08-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add exporting/importing of non rsa keys in libp2p-key format ([#179](https://github.com/libp2p/js-libp2p-crypto/issues/179)) ([7273739](https://github.com/libp2p/js-libp2p-crypto/commit/7273739))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.8"></a>
|
||||
## [0.17.8](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.7...v0.17.8) (2020-07-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* go ed25519 interop ([2f18a07](https://github.com/libp2p/js-libp2p-crypto/commit/2f18a07))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.7"></a>
|
||||
## [0.17.7](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.6...v0.17.7) (2020-06-09)
|
||||
|
||||
|
||||
|
||||
<a name="0.17.6"></a>
|
||||
## [0.17.6](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.5...v0.17.6) (2020-04-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add buffer and update deps ([#25](https://github.com/libp2p/js-libp2p-crypto/issues/25)) ([35f196e](https://github.com/libp2p/js-libp2p-crypto/commit/35f196e))
|
||||
* **unmarshal:** provide only one arg to callback ([#17](https://github.com/libp2p/js-libp2p-crypto/issues/17)) ([3bb8451](https://github.com/libp2p/js-libp2p-crypto/commit/3bb8451))
|
||||
* circular circular dep -> DI ([0dcf1a6](https://github.com/libp2p/js-libp2p-crypto/commit/0dcf1a6))
|
||||
* update deps and repo setup ([cfdcbe0](https://github.com/libp2p/js-libp2p-crypto/commit/cfdcbe0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `id()` method to Secp256k1PrivateKey ([f4dbd62](https://github.com/libp2p/js-libp2p-crypto/commit/f4dbd62))
|
||||
* initial implementation ([4c36aeb](https://github.com/libp2p/js-libp2p-crypto/commit/4c36aeb))
|
||||
* next libp2p-crypto ([#4](https://github.com/libp2p/js-libp2p-crypto/issues/4)) ([4ee48a7](https://github.com/libp2p/js-libp2p-crypto/commit/4ee48a7))
|
||||
* use async await ([#18](https://github.com/libp2p/js-libp2p-crypto/issues/18)) ([1974eb9](https://github.com/libp2p/js-libp2p-crypto/commit/1974eb9))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Callback support has been dropped in favor of async/await.
|
||||
|
||||
* feat: use async/await
|
||||
|
||||
This PR changes this module to remove callbacks and use async/await. The API is unchanged aside from the obvious removal of the `callback` parameter.
|
||||
|
||||
refs https://github.com/ipfs/js-ipfs/issues/1670
|
||||
|
||||
* fix: use latest multihashing-async as it is all promises now
|
||||
|
||||
|
||||
|
||||
<a name="0.17.5"></a>
|
||||
## [0.17.5](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.4...v0.17.5) (2020-03-24)
|
||||
|
||||
|
||||
|
||||
<a name="0.17.4"></a>
|
||||
## [0.17.4](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.3...v0.17.4) (2020-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add buffer, cleanup, reduce size ([#170](https://github.com/libp2p/js-libp2p-crypto/issues/170)) ([c956d1a](https://github.com/libp2p/js-libp2p-crypto/commit/c956d1a))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.3"></a>
|
||||
## [0.17.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.2...v0.17.3) (2020-02-26)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* remove asn1.js and use node-forge ([#166](https://github.com/libp2p/js-libp2p-crypto/issues/166)) ([00477e3](https://github.com/libp2p/js-libp2p-crypto/commit/00477e3))
|
||||
* remove jwk2privPem and jwk2pubPem ([#162](https://github.com/libp2p/js-libp2p-crypto/issues/162)) ([cc20949](https://github.com/libp2p/js-libp2p-crypto/commit/cc20949))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* removes unused jwk2pem methods `jwk2pubPem` and `jwk2privPem`. These methods are not being used in any js libp2p modules, so only users referencing these directly will be impacted.
|
||||
|
||||
|
||||
|
||||
<a name="0.17.2"></a>
|
||||
## [0.17.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.1...v0.17.2) (2020-01-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add typescript types + linting/tests ([#161](https://github.com/libp2p/js-libp2p-crypto/issues/161)) ([e01977c](https://github.com/libp2p/js-libp2p-crypto/commit/e01977c))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.1"></a>
|
||||
## [0.17.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.0...v0.17.1) (2019-10-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* better error for missing web crypto ([a5e0560](https://github.com/libp2p/js-libp2p-crypto/commit/a5e0560))
|
||||
* browser rsa enc/dec ([b8e2414](https://github.com/libp2p/js-libp2p-crypto/commit/b8e2414))
|
||||
* jwk var naming ([8b8d0c1](https://github.com/libp2p/js-libp2p-crypto/commit/8b8d0c1))
|
||||
* lint ([2c294b5](https://github.com/libp2p/js-libp2p-crypto/commit/2c294b5))
|
||||
* padding error ([2c1bac5](https://github.com/libp2p/js-libp2p-crypto/commit/2c1bac5))
|
||||
* use direct buffers instead of converting to hex ([027a5a9](https://github.com/libp2p/js-libp2p-crypto/commit/027a5a9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add (rsa)pubKey.encrypt and (rsa)privKey.decrypt ([34c5f5c](https://github.com/libp2p/js-libp2p-crypto/commit/34c5f5c))
|
||||
* browser enc/dec ([9f747a1](https://github.com/libp2p/js-libp2p-crypto/commit/9f747a1))
|
||||
* use forge to convert jwk2forge ([b998f63](https://github.com/libp2p/js-libp2p-crypto/commit/b998f63))
|
||||
|
||||
|
||||
|
||||
<a name="0.17.0"></a>
|
||||
# [0.17.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.1...v0.17.0) (2019-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update to ursa-optiona@0.10 ([26b6217](https://github.com/libp2p/js-libp2p-crypto/commit/26b6217))
|
||||
* fix links in README ([#148](https://github.com/libp2p/js-libp2p-crypto/issues/148)) ([5cd0e8c](https://github.com/libp2p/js-libp2p-crypto/commit/5cd0e8c))
|
||||
* put optional args last for key export ([#154](https://github.com/libp2p/js-libp2p-crypto/issues/154)) ([d675670](https://github.com/libp2p/js-libp2p-crypto/commit/d675670))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* refactor to use async/await ([#131](https://github.com/libp2p/js-libp2p-crypto/issues/131)) ([ad71072](https://github.com/libp2p/js-libp2p-crypto/commit/ad71072))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* key export arguments are now swapped so that the optional format is last
|
||||
* API refactored to use async/await
|
||||
|
||||
feat: WIP use async await
|
||||
fix: passing tests
|
||||
chore: update travis node.js versions
|
||||
fix: skip ursa optional tests on windows
|
||||
fix: benchmarks
|
||||
docs: update docs
|
||||
fix: remove broken and intested private key decrypt
|
||||
chore: update deps
|
||||
|
||||
|
||||
|
||||
<a name="0.16.1"></a>
|
||||
## [0.16.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.16.0...v0.16.1) (2019-02-26)
|
||||
|
||||
|
||||
|
||||
<a name="0.16.0"></a>
|
||||
# [0.16.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.15.0...v0.16.0) (2019-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean up, bundle size reduction ([8d8294d](https://github.com/libp2p/js-libp2p-crypto/commit/8d8294d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* getRandomValues method exported from src/keys/rsa-browser.js and src/keys/rsa.js signature has changed from accepting an array to a number for random byte length
|
||||
|
||||
|
||||
|
||||
<a name="0.15.0"></a>
|
||||
# [0.15.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.14.1...v0.15.0) (2019-01-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* nextTick instead of setImmediate, and fix sync in async ([#136](https://github.com/libp2p/js-libp2p-crypto/issues/136)) ([c54ea20](https://github.com/libp2p/js-libp2p-crypto/commit/c54ea20))
|
||||
|
||||
|
||||
|
||||
<a name="0.14.1"></a>
|
||||
## [0.14.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.14.0...v0.14.1) (2018-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dont setimmediate when its not needed ([9e57786](https://github.com/libp2p/js-libp2p-crypto/commit/9e57786))
|
||||
|
||||
|
||||
|
||||
<a name="0.14.0"></a>
|
||||
# [0.14.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.13.0...v0.14.0) (2018-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* windows build ([c7e0409](https://github.com/libp2p/js-libp2p-crypto/commit/c7e0409))
|
||||
* **lint:** use ~ for ursa-optional version ([e8cbf13](https://github.com/libp2p/js-libp2p-crypto/commit/e8cbf13))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use ursa-optional for lightning fast key generation ([b05e77f](https://github.com/libp2p/js-libp2p-crypto/commit/b05e77f))
|
||||
|
||||
|
||||
|
||||
<a name="0.13.0"></a>
|
||||
# [0.13.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.12.1...v0.13.0) (2018-04-05)
|
||||
|
||||
|
||||
|
||||
<a name="0.12.1"></a>
|
||||
## [0.12.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.12.0...v0.12.1) (2018-02-12)
|
||||
|
||||
|
||||
|
||||
<a name="0.12.0"></a>
|
||||
# [0.12.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.11.0...v0.12.0) (2018-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve perf ([#117](https://github.com/libp2p/js-libp2p-crypto/issues/117)) ([cdcca5f](https://github.com/libp2p/js-libp2p-crypto/commit/cdcca5f))
|
||||
|
||||
|
||||
|
||||
<a name="0.11.0"></a>
|
||||
# [0.11.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.10.4...v0.11.0) (2017-12-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* key exchange with jsrsasign ([#115](https://github.com/libp2p/js-libp2p-crypto/issues/115)) ([b342128](https://github.com/libp2p/js-libp2p-crypto/commit/b342128))
|
||||
|
||||
|
||||
|
||||
<a name="0.10.4"></a>
|
||||
## [0.10.4](https://github.com/libp2p/js-libp2p-crypto/compare/v0.10.3...v0.10.4) (2017-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch error when unmarshaling instead of crashing ([#113](https://github.com/libp2p/js-libp2p-crypto/issues/113)) ([7608fdd](https://github.com/libp2p/js-libp2p-crypto/commit/7608fdd))
|
||||
|
||||
|
||||
|
||||
<a name="0.10.3"></a>
|
||||
## [0.10.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.10.2...v0.10.3) (2017-09-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* switch protocol-buffers to protons ([#110](https://github.com/libp2p/js-libp2p-crypto/issues/110)) ([3a91ae2](https://github.com/libp2p/js-libp2p-crypto/commit/3a91ae2))
|
||||
|
||||
|
||||
|
||||
<a name="0.10.2"></a>
|
||||
## [0.10.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.10.1...v0.10.2) (2017-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use regular protocol-buffers until protobufjs is fixed ([#109](https://github.com/libp2p/js-libp2p-crypto/issues/109)) ([957fdd3](https://github.com/libp2p/js-libp2p-crypto/commit/957fdd3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** upgrade to aegir@12 and browserify-aes@1.0.8 ([83257bc](https://github.com/libp2p/js-libp2p-crypto/commit/83257bc))
|
||||
|
||||
|
||||
|
||||
<a name="0.10.1"></a>
|
||||
## [0.10.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.10.0...v0.10.1) (2017-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* switch to protobufjs ([#107](https://github.com/libp2p/js-libp2p-crypto/issues/107)) ([dc2793f](https://github.com/libp2p/js-libp2p-crypto/commit/dc2793f))
|
||||
|
||||
|
||||
|
||||
<a name="0.10.0"></a>
|
||||
# [0.10.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.9.4...v0.10.0) (2017-09-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* p2p addrs situation ([#106](https://github.com/libp2p/js-libp2p-crypto/issues/106)) ([9e977c7](https://github.com/libp2p/js-libp2p-crypto/commit/9e977c7))
|
||||
* skip nextTick in nodeify ([#103](https://github.com/libp2p/js-libp2p-crypto/issues/103)) ([f20267b](https://github.com/libp2p/js-libp2p-crypto/commit/f20267b))
|
||||
|
||||
|
||||
|
||||
<a name="0.9.4"></a>
|
||||
## [0.9.4](https://github.com/libp2p/js-libp2p-crypto/compare/v0.9.3...v0.9.4) (2017-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* circular circular dep -> DI ([bc554d1](https://github.com/libp2p/js-libp2p-crypto/commit/bc554d1))
|
||||
|
||||
|
||||
|
||||
<a name="0.9.3"></a>
|
||||
## [0.9.3](https://github.com/libp2p/js-libp2p-crypto/compare/v0.9.2...v0.9.3) (2017-07-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.9.2"></a>
|
||||
## [0.9.2](https://github.com/libp2p/js-libp2p-crypto/compare/v0.9.1...v0.9.2) (2017-07-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.9.1"></a>
|
||||
## [0.9.1](https://github.com/libp2p/js-libp2p-crypto/compare/v0.9.0...v0.9.1) (2017-07-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.9.0"></a>
|
||||
# [0.9.0](https://github.com/libp2p/js-libp2p-crypto/compare/v0.8.8...v0.9.0) (2017-07-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.8.8"></a>
|
||||
## [0.8.8](https://github.com/libp2p/js-libp2p-crypto/compare/v0.8.7...v0.8.8) (2017-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ecdh:** allow base64 to be left-0-padded, needed for JWK format ([be64372](https://github.com/libp2p/js-libp2p-crypto/commit/be64372)), closes [#97](https://github.com/libp2p/js-libp2p-crypto/issues/97)
|
||||
|
||||
|
||||
|
||||
<a name="0.8.7"></a>
|
||||
## [0.8.7](https://github.com/libp2p/js-libp2p-crypto/compare/v0.8.6...v0.8.7) (2017-03-21)
|
||||
|
||||
|
||||
|
||||
<a name="0.8.6"></a>
|
||||
## [0.8.6](https://github.com/libp2p/js-libp2p-crypto/compare/v0.8.5...v0.8.6) (2017-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **package:** update tweetnacl to version 1.0.0-rc.1 ([4e56e17](https://github.com/libp2p/js-libp2p-crypto/commit/4e56e17))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **keys:** implement generateKeyPairFromSeed for ed25519 ([e5b7c1f](https://github.com/libp2p/js-libp2p-crypto/commit/e5b7c1f))
|
||||
|
||||
|
||||
|
307
README.md
307
README.md
@@ -1,49 +1,52 @@
|
||||
# js-libp2p-crypto
|
||||
|
||||
[](http://ipn.io)
|
||||
[](http://ipfs.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23ipfs)
|
||||
[](https://github.com/RichardLitt/standard-readme)
|
||||
[](https://coveralls.io/github/libp2p/js-libp2p-crypto?branch=master)
|
||||
[](https://travis-ci.org/libp2p/js-libp2p-crypto)
|
||||
[](https://circleci.com/gh/libp2p/js-libp2p-crypto)
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
[](https://codecov.io/gh/libp2p/js-libp2p-crypto)
|
||||
[](https://travis-ci.com/libp2p/js-libp2p-crypto)
|
||||
[](https://david-dm.org/libp2p/js-libp2p-crypto)
|
||||
[](https://github.com/feross/standard)
|
||||

|
||||

|
||||
|
||||
[](https://saucelabs.com/u/ipfs-js-
|
||||
libp2p-crypto)
|
||||
|
||||
> Crypto primitives for libp2p in JavaScript
|
||||
|
||||
This repo contains the JavaScript implementation of the crypto primitives
|
||||
needed for libp2p. This is based on this [go implementation](https://github.com/libp2p/go-libp2p-crypto).
|
||||
This repo contains the JavaScript implementation of the crypto primitives needed for libp2p. This is based on this [go implementation](https://github.com/libp2p/go-libp2p-crypto).
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Jacob Heun](https://github.com/jacobheun/)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Example](#example)
|
||||
- [API](#api)
|
||||
- [`hmac`](#hmac)
|
||||
- [`create(hash, secret, callback)`](#createhash-secret-callback)
|
||||
- [`digest(data, callback)`](#digestdata-callback)
|
||||
- [`aes`](#aes)
|
||||
- [`create(key, iv, callback)`](#createkey-iv-callback)
|
||||
- [`encrypt(data, callback)`](#encryptdata-callback)
|
||||
- [`encrypt(data, callback)`](#encryptdata-callback)
|
||||
- [`webcrypto`](#webcrypto)
|
||||
- [`keys`](#keys)
|
||||
- [`generateKeyPair(type, bits, callback)`](#generatekeypairtype-bits-callback)
|
||||
- [`generateEphemeralKeyPair(curve, callback)`](#generateephemeralkeypaircurve-callback)
|
||||
- [`keyStretcher(cipherType, hashType, secret, callback)`](#keystretcherciphertype-hashtype-secret-callback)
|
||||
- [`marshalPublicKey(key[, type], callback)`](#marshalpublickeykey-type-callback)
|
||||
- [`unmarshalPublicKey(buf)`](#unmarshalpublickeybuf)
|
||||
- [`marshalPrivateKey(key[, type])`](#marshalprivatekeykey-type)
|
||||
- [`unmarshalPrivateKey(buf, callback)`](#unmarshalprivatekeybuf-callback)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
- [js-libp2p-crypto](#js-libp2p-crypto)
|
||||
- [Lead Maintainer](#lead-maintainer)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Web Crypto API](#web-crypto-api)
|
||||
- [API](#api)
|
||||
- [`crypto.aes`](#cryptoaes)
|
||||
- [`crypto.aes.create(key, iv)`](#cryptoaescreatekey-iv)
|
||||
- [`decrypt(data)`](#decryptdata)
|
||||
- [`encrypt(data)`](#encryptdata)
|
||||
- [`crypto.hmac`](#cryptohmac)
|
||||
- [`crypto.hmac.create(hash, secret)`](#cryptohmaccreatehash-secret)
|
||||
- [`digest(data)`](#digestdata)
|
||||
- [`crypto.keys`](#cryptokeys)
|
||||
- [`crypto.keys.generateKeyPair(type, bits)`](#cryptokeysgeneratekeypairtype-bits)
|
||||
- [`crypto.keys.generateEphemeralKeyPair(curve)`](#cryptokeysgenerateephemeralkeypaircurve)
|
||||
- [`crypto.keys.keyStretcher(cipherType, hashType, secret)`](#cryptokeyskeystretcherciphertype-hashtype-secret)
|
||||
- [`crypto.keys.marshalPublicKey(key, [type])`](#cryptokeysmarshalpublickeykey-type)
|
||||
- [`crypto.keys.unmarshalPublicKey(buf)`](#cryptokeysunmarshalpublickeybuf)
|
||||
- [`crypto.keys.marshalPrivateKey(key, [type])`](#cryptokeysmarshalprivatekeykey-type)
|
||||
- [`crypto.keys.unmarshalPrivateKey(buf)`](#cryptokeysunmarshalprivatekeybuf)
|
||||
- [`crypto.keys.import(encryptedKey, password)`](#cryptokeysimportencryptedkey-password)
|
||||
- [`privateKey.export(password, format)`](#privatekeyexportpassword-format)
|
||||
- [`crypto.randomBytes(number)`](#cryptorandombytesnumber)
|
||||
- [`crypto.pbkdf2(password, salt, iterations, keySize, hash)`](#cryptopbkdf2password-salt-iterations-keysize-hash)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
@@ -53,138 +56,250 @@ npm install --save libp2p-crypto
|
||||
|
||||
## Usage
|
||||
|
||||
### Example
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
// Now available to you:
|
||||
//
|
||||
// crypto.aes
|
||||
// crypto.hmac
|
||||
// crypto.keys
|
||||
// etc.
|
||||
//
|
||||
// See full API details below...
|
||||
```
|
||||
|
||||
### Web Crypto API
|
||||
|
||||
The `libp2p-crypto` library depends on the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in the browser. Web Crypto is available in all modern browsers, however browsers restrict its usage to [Secure Contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
||||
|
||||
**This means you will not be able to use some `libp2p-crypto` functions in the browser when the page is served over HTTP.** To enable the Web Crypto API and allow `libp2p-crypto` to work fully, please serve your page over HTTPS.
|
||||
|
||||
## API
|
||||
|
||||
### `crypto.aes`
|
||||
|
||||
Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
|
||||
This uses `CTR` mode.
|
||||
|
||||
#### `crypto.aes.create(key, iv)`
|
||||
|
||||
- `key: Uint8Array` The key, if length `16` then `AES 128` is used. For length `32`, `AES 256` is used.
|
||||
- `iv: Uint8Array` Must have length `16`.
|
||||
|
||||
Returns `Promise<{decrypt<Function>, encrypt<Function>}>`
|
||||
|
||||
##### `decrypt(data)`
|
||||
|
||||
- `data: Uint8Array`
|
||||
|
||||
Returns `Promise<Uint8Array>`
|
||||
|
||||
##### `encrypt(data)`
|
||||
|
||||
- `data: Uint8Array`
|
||||
|
||||
Returns `Promise<Uint8Array>`
|
||||
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
crypto.generateKeyPair('RSA', 2048, (err, key) => {
|
||||
})
|
||||
// Setting up Key and IV
|
||||
|
||||
// A 16 bytes array, 128 Bits, AES-128 is chosen
|
||||
const key128 = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
|
||||
// A 16 bytes array, 128 Bits,
|
||||
const IV = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
||||
|
||||
async function main () {
|
||||
const decryptedMessage = 'Hello, world!'
|
||||
|
||||
// Encrypting
|
||||
const cipher = await crypto.aes.create(key128, IV)
|
||||
const encryptedBuffer = await cipher.encrypt(Uint8Array.from(decryptedMessage))
|
||||
console.log(encryptedBuffer)
|
||||
// prints: <Uint8Array 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
|
||||
// Decrypting
|
||||
const decipher = await crypto.aes.create(key128, IV)
|
||||
const decryptedBuffer = await cipher.decrypt(encryptedBuffer)
|
||||
|
||||
console.log(decryptedBuffer)
|
||||
// prints: <Uint8Array 42 f1 67 d9 2e 42 d0 32 9e b1 f8 3c>
|
||||
|
||||
console.log(decryptedBuffer.toString('utf-8'))
|
||||
// prints: Hello, world!
|
||||
}
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `hmac`
|
||||
### `crypto.hmac`
|
||||
|
||||
Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC) as defined in U.S. Federal Information Processing Standards Publication 198. An HMAC is a cryptographic hash that uses a key to sign a message. The receiver verifies the hash by recomputing it using the same key.
|
||||
|
||||
#### `create(hash, secret, callback)`
|
||||
#### `crypto.hmac.create(hash, secret)`
|
||||
|
||||
- `hash: String`
|
||||
- `secret: Buffer`
|
||||
- `callback: Function`
|
||||
- `secret: Uint8Array`
|
||||
|
||||
##### `digest(data, callback)`
|
||||
Returns `Promise<{digest<Function>}>`
|
||||
|
||||
- `data: Buffer`
|
||||
- `callback: Function`
|
||||
##### `digest(data)`
|
||||
|
||||
### `aes`
|
||||
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
- `data: Uint8Array`
|
||||
|
||||
This uses `CTR` mode.
|
||||
Returns `Promise<Uint8Array>`
|
||||
|
||||
#### `create(key, iv, callback)`
|
||||
Example:
|
||||
|
||||
- `key: Buffer` The key, if length `16` then `AES 128` is used. For length `32`, `AES 256` is used.
|
||||
- `iv: Buffer` Must have length `16`.
|
||||
- `callback: Function`
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
##### `encrypt(data, callback)`
|
||||
async function main () {
|
||||
const hash = 'SHA1' // 'SHA256' || 'SHA512'
|
||||
const hmac = await crypto.hmac.create(hash, uint8ArrayFromString('secret'))
|
||||
const sig = await hmac.digest(uint8ArrayFromString('hello world'))
|
||||
console.log(sig)
|
||||
}
|
||||
|
||||
- `data: Buffer`
|
||||
- `callback: Function`
|
||||
main()
|
||||
```
|
||||
|
||||
##### `encrypt(data, callback)`
|
||||
### `crypto.keys`
|
||||
|
||||
- `data: Buffer`
|
||||
- `callback: Function`
|
||||
**Supported Key Types**
|
||||
|
||||
The [`generateKeyPair`](#generatekeypairtype-bits), [`marshalPublicKey`](#marshalpublickeykey-type), and [`marshalPrivateKey`](#marshalprivatekeykey-type) functions accept a string `type` argument.
|
||||
|
||||
### `webcrypto`
|
||||
Currently the `'RSA'`, `'ed25519'`, and `secp256k1` types are supported, although ed25519 and secp256k1 keys support only signing and verification of messages. For encryption / decryption support, RSA keys should be used.
|
||||
|
||||
Depending on the environment this is either an instance of [node-webcrypto-ossl](https://github.com/PeculiarVentures/node-webcrypto-ossl) or the result of `window.crypto`.
|
||||
### `crypto.keys.generateKeyPair(type, bits)`
|
||||
|
||||
### `keys`
|
||||
|
||||
### `generateKeyPair(type, bits, callback)`
|
||||
|
||||
- `type: String`, only `'RSA'` is currently supported
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above.
|
||||
- `bits: Number` Minimum of 1024
|
||||
- `callback: Function`
|
||||
|
||||
Returns `Promise<{privateKey<Uint8Array>, publicKey<Uint8Array>}>`
|
||||
|
||||
Generates a keypair of the given type and bitsize.
|
||||
|
||||
### `generateEphemeralKeyPair(curve, callback)`
|
||||
### `crypto.keys.generateEphemeralKeyPair(curve)`
|
||||
|
||||
- `curve: String`, one of `'P-256'`, `'P-384'`, `'P-521'` is currently supported
|
||||
- `callback: Function`
|
||||
|
||||
Returns `Promise`
|
||||
|
||||
Generates an ephemeral public key and returns a function that will compute the shared secret key.
|
||||
|
||||
Focuses only on ECDH now, but can be made more general in the future.
|
||||
|
||||
Calls back with an object of the form
|
||||
Resolves to an object of the form:
|
||||
|
||||
```js
|
||||
{
|
||||
key: Buffer,
|
||||
key: Uint8Array,
|
||||
genSharedKey: Function
|
||||
}
|
||||
```
|
||||
|
||||
### `keyStretcher(cipherType, hashType, secret, callback)`
|
||||
### `crypto.keys.keyStretcher(cipherType, hashType, secret)`
|
||||
|
||||
- `cipherType: String`, one of `'AES-128'`, `'AES-256'`, `'Blowfish'`
|
||||
- `hashType: String`, one of `'SHA1'`, `SHA256`, `SHA512`
|
||||
- `secret: Buffer`
|
||||
- `callback: Function`
|
||||
- `secret: Uint8Array`
|
||||
|
||||
Returns `Promise`
|
||||
|
||||
Generates a set of keys for each party by stretching the shared key.
|
||||
|
||||
Calls back with an object of the form
|
||||
Resolves to an object of the form:
|
||||
|
||||
```js
|
||||
{
|
||||
k1: {
|
||||
iv: Buffer,
|
||||
cipherKey: Buffer,
|
||||
macKey: Buffer
|
||||
iv: Uint8Array,
|
||||
cipherKey: Uint8Array,
|
||||
macKey: Uint8Array
|
||||
},
|
||||
k2: {
|
||||
iv: Buffer,
|
||||
cipherKey: Buffer,
|
||||
macKey: Buffer
|
||||
iv: Uint8Array,
|
||||
cipherKey: Uint8Array,
|
||||
macKey: Uint8Array
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `marshalPublicKey(key[, type], callback)`
|
||||
### `crypto.keys.marshalPublicKey(key, [type])`
|
||||
|
||||
- `key: crypto.rsa.RsaPublicKey`
|
||||
- `type: String`, only `'RSA'` is currently supported
|
||||
- `key: keys.rsa.RsaPublicKey | keys.ed25519.Ed25519PublicKey | keys.secp256k1.Secp256k1PublicKey`
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above. Defaults to 'rsa'.
|
||||
|
||||
Returns `Uint8Array`
|
||||
|
||||
Converts a public key object into a protobuf serialized public key.
|
||||
|
||||
### `unmarshalPublicKey(buf)`
|
||||
### `crypto.keys.unmarshalPublicKey(buf)`
|
||||
|
||||
- `buf: Buffer`
|
||||
- `buf: Uint8Array`
|
||||
|
||||
Converts a protobuf serialized public key into its representative object.
|
||||
Returns `RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey`
|
||||
|
||||
### `marshalPrivateKey(key[, type])`
|
||||
Converts a protobuf serialized public key into its representative object.
|
||||
|
||||
- `key: crypto.rsa.RsaPrivateKey`
|
||||
- `type: String`, only `'RSA'` is currently supported
|
||||
### `crypto.keys.marshalPrivateKey(key, [type])`
|
||||
|
||||
- `key: keys.rsa.RsaPrivateKey | keys.ed25519.Ed25519PrivateKey | keys.secp256k1.Secp256k1PrivateKey`
|
||||
- `type: String`, see [Supported Key Types](#supported-key-types) above.
|
||||
|
||||
Returns `Uint8Array`
|
||||
|
||||
Converts a private key object into a protobuf serialized private key.
|
||||
|
||||
### `unmarshalPrivateKey(buf, callback)`
|
||||
### `crypto.keys.unmarshalPrivateKey(buf)`
|
||||
|
||||
- `buf: Buffer`
|
||||
- `callback: Function`
|
||||
- `buf: Uint8Array`
|
||||
|
||||
Returns `Promise<RsaPrivateKey|Ed25519PrivateKey|Secp256k1PrivateKey>`
|
||||
|
||||
Converts a protobuf serialized private key into its representative object.
|
||||
|
||||
### `crypto.keys.import(encryptedKey, password)`
|
||||
|
||||
- `encryptedKey: string`
|
||||
- `password: string`
|
||||
|
||||
Returns `Promise<PrivateKey>`
|
||||
|
||||
Converts an exported private key into its representative object. Supported formats are 'pem' (RSA only) and 'libp2p-key'.
|
||||
|
||||
### `privateKey.export(password, format)`
|
||||
|
||||
- `password: string`
|
||||
- `format: string` the format to export to: 'pem' (rsa only), 'libp2p-key'
|
||||
|
||||
Returns `string`
|
||||
|
||||
Exports the password protected `PrivateKey`. RSA keys will be exported as password protected PEM by default. Ed25519 and Secp256k1 keys will be exported as password protected AES-GCM base64 encoded strings ('libp2p-key' format).
|
||||
|
||||
### `crypto.randomBytes(number)`
|
||||
|
||||
- `number: Number`
|
||||
|
||||
Returns `Uint8Array`
|
||||
|
||||
Generates a Uint8Array with length `number` populated by random bytes.
|
||||
|
||||
### `crypto.pbkdf2(password, salt, iterations, keySize, hash)`
|
||||
|
||||
- `password: String`
|
||||
- `salt: String`
|
||||
- `iterations: Number`
|
||||
- `keySize: Number` in bytes
|
||||
- `hash: String` the hashing algorithm ('sha1', 'sha2-512', ...)
|
||||
|
||||
Computes the Password Based Key Derivation Function 2; returning a new password.
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -196,4 +311,4 @@ This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/c
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
[MIT](./LICENSE)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
@@ -9,29 +10,14 @@ const secrets = []
|
||||
const curves = ['P-256', 'P-384', 'P-521']
|
||||
|
||||
curves.forEach((curve) => {
|
||||
suite.add(`ephemeral key with secrect ${curve}`, (d) => {
|
||||
crypto.generateEphemeralKeyPair('P-256', (err, res) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
res.genSharedKey(res.key, (err, secret) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
secrets.push(secret)
|
||||
|
||||
d.resolve()
|
||||
})
|
||||
})
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
suite.add(`ephemeral key with secrect ${curve}`, async (d) => {
|
||||
const res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
const secret = await res.genSharedKey(res.key)
|
||||
secrets.push(secret)
|
||||
d.resolve()
|
||||
}, { defer: true })
|
||||
})
|
||||
|
||||
suite
|
||||
.on('cycle', (event) => {
|
||||
console.log(String(event.target))
|
||||
})
|
||||
.run({
|
||||
async: true
|
||||
})
|
||||
.on('cycle', (event) => console.log(String(event.target)))
|
||||
.run({ async: true })
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
const async = require('async')
|
||||
|
||||
const crypto = require('../src')
|
||||
|
||||
@@ -12,38 +12,23 @@ const keys = []
|
||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
|
||||
async.waterfall([
|
||||
(cb) => crypto.generateEphemeralKeyPair('P-256', cb),
|
||||
(res, cb) => res.genSharedKey(res.key, cb)
|
||||
], (err, secret) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
;(async () => {
|
||||
const res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
const secret = await res.genSharedKey(res.key)
|
||||
|
||||
ciphers.forEach((cipher) => hashes.forEach((hash) => {
|
||||
setup(cipher, hash, secret)
|
||||
}))
|
||||
|
||||
suite
|
||||
.on('cycle', (event) => {
|
||||
console.log(String(event.target))
|
||||
})
|
||||
.run({
|
||||
async: true
|
||||
})
|
||||
})
|
||||
.on('cycle', (event) => console.log(String(event.target)))
|
||||
.run({ async: true })
|
||||
})()
|
||||
|
||||
function setup (cipher, hash, secret) {
|
||||
suite.add(`keyStretcher ${cipher} ${hash}`, (d) => {
|
||||
crypto.keyStretcher(cipher, hash, secret, (err, k) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
keys.push(k)
|
||||
d.resolve()
|
||||
})
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
suite.add(`keyStretcher ${cipher} ${hash}`, async (d) => {
|
||||
const k = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
keys.push(k)
|
||||
d.resolve()
|
||||
}, { defer: true })
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
@@ -9,44 +10,28 @@ const keys = []
|
||||
const bits = [1024, 2048, 4096]
|
||||
|
||||
bits.forEach((bit) => {
|
||||
suite.add(`generateKeyPair ${bit}bits`, (d) => {
|
||||
crypto.generateKeyPair('RSA', bit, (err, key) => {
|
||||
if (err) throw err
|
||||
keys.push(key)
|
||||
d.resolve()
|
||||
})
|
||||
suite.add(`generateKeyPair ${bit}bits`, async (d) => {
|
||||
const key = await crypto.keys.generateKeyPair('RSA', bit)
|
||||
keys.push(key)
|
||||
d.resolve()
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
})
|
||||
|
||||
suite.add('sign and verify', (d) => {
|
||||
suite.add('sign and verify', async (d) => {
|
||||
const key = keys[0]
|
||||
const text = key.genSecret()
|
||||
|
||||
key.sign(text, (err, sig) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
|
||||
key.public.verify(text, sig, (err, res) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
if (res !== true) {
|
||||
throw new Error('failed to verify')
|
||||
}
|
||||
d.resolve()
|
||||
})
|
||||
})
|
||||
if (res !== true) { throw new Error('failed to verify') }
|
||||
d.resolve()
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
|
||||
suite
|
||||
.on('cycle', (event) => {
|
||||
console.log(String(event.target))
|
||||
})
|
||||
.run({
|
||||
async: true
|
||||
})
|
||||
.on('cycle', (event) => console.log(String(event.target)))
|
||||
.run({ async: true })
|
||||
|
12
circle.yml
12
circle.yml
@@ -1,12 +0,0 @@
|
||||
machine:
|
||||
node:
|
||||
version: stable
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- google-chrome --version
|
||||
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
||||
- sudo apt-get update
|
||||
- sudo apt-get --only-upgrade install google-chrome-stable
|
||||
- google-chrome --version
|
116
package.json
116
package.json
@@ -1,70 +1,106 @@
|
||||
{
|
||||
"name": "libp2p-crypto",
|
||||
"version": "0.7.1",
|
||||
"version": "0.19.2",
|
||||
"description": "Crypto primitives for libp2p",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"browser": {
|
||||
"node-webcrypto-ossl": false,
|
||||
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js",
|
||||
"./src/crypto/hmac.js": "./src/crypto/hmac-browser.js",
|
||||
"./src/crypto/ciphers.js": "./src/crypto/ciphers-browser.js"
|
||||
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js",
|
||||
"./src/ciphers/aes-gcm.js": "./src/ciphers/aes-gcm.browser.js",
|
||||
"./src/hmac/index.js": "./src/hmac/index-browser.js",
|
||||
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js",
|
||||
"./src/keys/rsa.js": "./src/keys/rsa-browser.js"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "aegir-lint",
|
||||
"build": "aegir-build",
|
||||
"test": "aegir-test",
|
||||
"test:node": "aegir-test --env node",
|
||||
"test:browser": "aegir-test --env browser",
|
||||
"release": "aegir-release",
|
||||
"release-minor": "aegir-release --type minor",
|
||||
"release-major": "aegir-release --type major",
|
||||
"coverage": "aegir-coverage",
|
||||
"coverage-publish": "aegir-coverage publish"
|
||||
"lint": "aegir lint",
|
||||
"build": "aegir build",
|
||||
"build-proto": "pbjs --wrap commonjs --target static-module src/keys/keys.proto > src/keys/keys.proto.js",
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node",
|
||||
"test:browser": "aegir test -t browser -t webworker",
|
||||
"release": "aegir release",
|
||||
"release-minor": "aegir release --type minor",
|
||||
"release-major": "aegir release --type major",
|
||||
"coverage": "aegir coverage --ignore src/keys/keys.proto.js",
|
||||
"size": "aegir build --bundlesize",
|
||||
"test:types": "npx tsc"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS",
|
||||
"libp2p",
|
||||
"crypto",
|
||||
"rsa"
|
||||
"rsa",
|
||||
"secp256k1"
|
||||
],
|
||||
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asn1.js": "^4.8.1",
|
||||
"async": "^2.1.2",
|
||||
"browserify-aes": "^1.0.6",
|
||||
"multihashing-async": "^0.2.0",
|
||||
"node-webcrypto-ossl": "^1.0.7",
|
||||
"nodeify": "^1.0.0",
|
||||
"protocol-buffers": "^3.1.6",
|
||||
"webcrypto-shim": "github:dignifiedquire/webcrypto-shim#master"
|
||||
"err-code": "^2.0.0",
|
||||
"is-typedarray": "^1.0.0",
|
||||
"iso-random-stream": "^1.1.0",
|
||||
"keypair": "^1.0.1",
|
||||
"multibase": "^4.0.3",
|
||||
"multicodec": "^3.0.1",
|
||||
"multihashes": "^4.0.2",
|
||||
"multihashing-async": "^2.0.1",
|
||||
"node-forge": "^0.10.0",
|
||||
"pem-jwk": "^2.0.0",
|
||||
"protons": "^2.0.0",
|
||||
"secp256k1": "^4.0.0",
|
||||
"uint8arrays": "^2.1.4",
|
||||
"ursa-optional": "^0.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^9.0.1",
|
||||
"benchmark": "^2.1.2",
|
||||
"chai": "^3.5.0",
|
||||
"pre-commit": "^1.1.3"
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/chai-string": "^1.4.2",
|
||||
"@types/dirty-chai": "^2.0.2",
|
||||
"@types/mocha": "^8.0.1",
|
||||
"aegir": "^25.0.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"chai": "^4.2.0",
|
||||
"chai-string": "^1.5.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"sinon": "^9.0.0"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint",
|
||||
"test"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ipfs/js-libp2p-crypto.git"
|
||||
"url": "https://github.com/libp2p/js-libp2p-crypto.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ipfs/js-libp2p-crypto/issues"
|
||||
"url": "https://github.com/libp2p/js-libp2p-crypto/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ipfs/js-libp2p-crypto",
|
||||
"homepage": "https://github.com/libp2p/js-libp2p-crypto",
|
||||
"contributors": [
|
||||
"David Dias <daviddias.p@gmail.com>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"Jacob Heun <jacobheun@gmail.com>",
|
||||
"Maciej Krüger <mkg20001@gmail.com>",
|
||||
"dryajov <dryajov@gmail.com>",
|
||||
"Alan Shaw <alan.shaw@protocol.ai>",
|
||||
"Hugo Dias <hugomrdias@gmail.com>",
|
||||
"Cayman <caymannava@gmail.com>",
|
||||
"Yusef Napora <yusef@napora.org>",
|
||||
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
|
||||
"Arve Knudsen <arve.knudsen@gmail.com>",
|
||||
"Alex Potsides <alex@achingbrain.net>",
|
||||
"Vasco Santos <vasco.santos@ua.pt>",
|
||||
"Alberto Elias <hi@albertoelias.me>",
|
||||
"Jack Kleeman <jackkleeman@gmail.com>",
|
||||
"Nadim Kobeissi <nadim@symbolic.software>",
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"greenkeeperio-bot <support@greenkeeper.io>",
|
||||
"nikuda <nikuda@gmail.com>"
|
||||
"Richard Schneider <makaretu@gmail.com>",
|
||||
"dirkmc <dirkmdev@gmail.com>",
|
||||
"nikuda <nikuda@gmail.com>",
|
||||
"Joao Santos <jrmsantos15@gmail.com>",
|
||||
"Carson Farmer <carson.farmer@gmail.com>",
|
||||
"Tom Swindell <t.swindell@rubyx.co.uk>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
17
src/aes/cipher-mode.js
Normal file
17
src/aes/cipher-mode.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
|
||||
const CIPHER_MODES = {
|
||||
16: 'aes-128-ctr',
|
||||
32: 'aes-256-ctr'
|
||||
}
|
||||
|
||||
module.exports = function (key) {
|
||||
const mode = CIPHER_MODES[key.length]
|
||||
if (!mode) {
|
||||
const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
|
||||
throw errcode(new Error(`Invalid key length ${key.length} bytes. Must be ${modes}`), 'ERR_INVALID_KEY_LENGTH')
|
||||
}
|
||||
return mode
|
||||
}
|
29
src/aes/ciphers-browser.js
Normal file
29
src/aes/ciphers-browser.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
require('node-forge/lib/aes')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
module.exports = {
|
||||
createCipheriv: (mode, key, iv) => {
|
||||
const cipher2 = forge.cipher.createCipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
|
||||
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
|
||||
return {
|
||||
update: (data) => {
|
||||
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
|
||||
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
|
||||
}
|
||||
}
|
||||
},
|
||||
createDecipheriv: (mode, key, iv) => {
|
||||
const cipher2 = forge.cipher.createDecipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
|
||||
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
|
||||
return {
|
||||
update: (data) => {
|
||||
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
|
||||
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/aes/index.js
Normal file
22
src/aes/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict'
|
||||
|
||||
const ciphers = require('./ciphers')
|
||||
const cipherMode = require('./cipher-mode')
|
||||
|
||||
exports.create = async function (key, iv) { // eslint-disable-line require-await
|
||||
const mode = cipherMode(key)
|
||||
const cipher = ciphers.createCipheriv(mode, key, iv)
|
||||
const decipher = ciphers.createDecipheriv(mode, key, iv)
|
||||
|
||||
const res = {
|
||||
async encrypt (data) { // eslint-disable-line require-await
|
||||
return cipher.update(data)
|
||||
},
|
||||
|
||||
async decrypt (data) { // eslint-disable-line require-await
|
||||
return decipher.update(data)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
89
src/ciphers/aes-gcm.browser.js
Normal file
89
src/ciphers/aes-gcm.browser.js
Normal file
@@ -0,0 +1,89 @@
|
||||
'use strict'
|
||||
|
||||
const concat = require('uint8arrays/concat')
|
||||
const fromString = require('uint8arrays/from-string')
|
||||
|
||||
const webcrypto = require('../webcrypto')
|
||||
|
||||
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.algorithm=AES-GCM]
|
||||
* @param {Number} [options.nonceLength=12]
|
||||
* @param {Number} [options.keyLength=16]
|
||||
* @param {string} [options.digest=sha256]
|
||||
* @param {Number} [options.saltLength=16]
|
||||
* @param {Number} [options.iterations=32767]
|
||||
* @returns {*}
|
||||
*/
|
||||
function create ({
|
||||
algorithm = 'AES-GCM',
|
||||
nonceLength = 12,
|
||||
keyLength = 16,
|
||||
digest = 'SHA-256',
|
||||
saltLength = 16,
|
||||
iterations = 32767
|
||||
} = {}) {
|
||||
const crypto = webcrypto.get()
|
||||
keyLength *= 8 // Browser crypto uses bits instead of bytes
|
||||
|
||||
/**
|
||||
* Uses the provided password to derive a pbkdf2 key. The key
|
||||
* will then be used to encrypt the data.
|
||||
*
|
||||
* @param {Uint8Array} data The data to decrypt
|
||||
* @param {string} password A plain password
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
async function encrypt (data, password) { // eslint-disable-line require-await
|
||||
const salt = crypto.getRandomValues(new Uint8Array(saltLength))
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(nonceLength))
|
||||
const aesGcm = { name: algorithm, iv: nonce }
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
|
||||
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
|
||||
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt'])
|
||||
|
||||
// Encrypt the string.
|
||||
const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data)
|
||||
return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)])
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided password to derive a pbkdf2 key. The key
|
||||
* will then be used to decrypt the data. The options used to create
|
||||
* this decryption cipher must be the same as those used to create
|
||||
* the encryption cipher.
|
||||
*
|
||||
* @param {Uint8Array} data The data to decrypt
|
||||
* @param {string} password A plain password
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
async function decrypt (data, password) {
|
||||
const salt = data.slice(0, saltLength)
|
||||
const nonce = data.slice(saltLength, saltLength + nonceLength)
|
||||
const ciphertext = data.slice(saltLength + nonceLength)
|
||||
const aesGcm = { name: algorithm, iv: nonce }
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
|
||||
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
|
||||
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt'])
|
||||
|
||||
// Decrypt the string.
|
||||
const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext)
|
||||
return new Uint8Array(plaintext)
|
||||
}
|
||||
|
||||
return {
|
||||
encrypt,
|
||||
decrypt
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create
|
||||
}
|
130
src/ciphers/aes-gcm.js
Normal file
130
src/ciphers/aes-gcm.js
Normal file
@@ -0,0 +1,130 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const uint8ArrayConcat = require('uint8arrays/concat')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @param {Number} [options.algorithmTagLength=16]
|
||||
* @param {Number} [options.nonceLength=12]
|
||||
* @param {Number} [options.keyLength=16]
|
||||
* @param {string} [options.digest=sha256]
|
||||
* @param {Number} [options.saltLength=16]
|
||||
* @param {Number} [options.iterations=32767]
|
||||
* @returns {*}
|
||||
*/
|
||||
function create ({
|
||||
algorithmTagLength = 16,
|
||||
nonceLength = 12,
|
||||
keyLength = 16,
|
||||
digest = 'sha256',
|
||||
saltLength = 16,
|
||||
iterations = 32767
|
||||
} = {}) {
|
||||
const algorithm = 'aes-128-gcm'
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
* @param {Uint8Array} data
|
||||
* @param {Uint8Array} key
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
async function encryptWithKey (data, key) { // eslint-disable-line require-await
|
||||
const nonce = crypto.randomBytes(nonceLength)
|
||||
|
||||
// Create the cipher instance.
|
||||
const cipher = crypto.createCipheriv(algorithm, key, nonce)
|
||||
|
||||
// Encrypt and prepend nonce.
|
||||
const ciphertext = uint8ArrayConcat([cipher.update(data), cipher.final()])
|
||||
|
||||
return uint8ArrayConcat([nonce, ciphertext, cipher.getAuthTag()])
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided password to derive a pbkdf2 key. The key
|
||||
* will then be used to encrypt the data.
|
||||
*
|
||||
* @param {Uint8Array} data The data to decrypt
|
||||
* @param {string|Uint8Array} password A plain password
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
async function encrypt (data, password) { // eslint-disable-line require-await
|
||||
// Generate a 128-bit salt using a CSPRNG.
|
||||
const salt = crypto.randomBytes(saltLength)
|
||||
|
||||
if (typeof password === 'string' || password instanceof String) {
|
||||
password = uint8ArrayFromString(password)
|
||||
}
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
|
||||
|
||||
// Encrypt and prepend salt.
|
||||
return uint8ArrayConcat([salt, await encryptWithKey(Uint8Array.from(data), key)])
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the given cipher text with the provided key. The `key` should
|
||||
* be a cryptographically safe key and not a plaintext password. To use
|
||||
* a plaintext password, use `decrypt`. The options used to create
|
||||
* this decryption cipher must be the same as those used to create
|
||||
* the encryption cipher.
|
||||
*
|
||||
* @private
|
||||
* @param {Uint8Array} ciphertextAndNonce The data to decrypt
|
||||
* @param {Uint8Array} key
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
async function decryptWithKey (ciphertextAndNonce, key) { // eslint-disable-line require-await
|
||||
// Create Uint8Arrays of nonce, ciphertext and tag.
|
||||
const nonce = ciphertextAndNonce.slice(0, nonceLength)
|
||||
const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength)
|
||||
const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength)
|
||||
|
||||
// Create the cipher instance.
|
||||
const cipher = crypto.createDecipheriv(algorithm, key, nonce)
|
||||
|
||||
// Decrypt and return result.
|
||||
cipher.setAuthTag(tag)
|
||||
return uint8ArrayConcat([cipher.update(ciphertext), cipher.final()])
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided password to derive a pbkdf2 key. The key
|
||||
* will then be used to decrypt the data. The options used to create
|
||||
* this decryption cipher must be the same as those used to create
|
||||
* the encryption cipher.
|
||||
*
|
||||
* @param {Uint8Array} data The data to decrypt
|
||||
* @param {string|Uint8Array} password A plain password
|
||||
*/
|
||||
async function decrypt (data, password) { // eslint-disable-line require-await
|
||||
// Create Uint8Arrays of salt and ciphertextAndNonce.
|
||||
const salt = data.slice(0, saltLength)
|
||||
const ciphertextAndNonce = data.slice(saltLength)
|
||||
|
||||
if (typeof password === 'string' || password instanceof String) {
|
||||
password = uint8ArrayFromString(password)
|
||||
}
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
|
||||
|
||||
// Decrypt and return result.
|
||||
return decryptWithKey(ciphertextAndNonce, key)
|
||||
}
|
||||
|
||||
return {
|
||||
encrypt,
|
||||
decrypt
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
exports.webcrypto = require('./crypto/webcrypto')()
|
||||
exports.hmac = require('./crypto/hmac')
|
||||
exports.ecdh = require('./crypto/ecdh')
|
||||
exports.aes = require('./crypto/aes')
|
||||
exports.rsa = require('./crypto/rsa')
|
@@ -1,30 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const ciphers = require('./ciphers')
|
||||
|
||||
const CIPHER_MODES = {
|
||||
16: 'aes-128-ctr',
|
||||
32: 'aes-256-ctr'
|
||||
}
|
||||
|
||||
exports.create = function (key, iv, callback) {
|
||||
const mode = CIPHER_MODES[key.length]
|
||||
if (!mode) {
|
||||
return callback(new Error('Invalid key length'))
|
||||
}
|
||||
|
||||
const cipher = ciphers.createCipheriv(mode, key, iv)
|
||||
const decipher = ciphers.createDecipheriv(mode, key, iv)
|
||||
|
||||
const res = {
|
||||
encrypt (data, cb) {
|
||||
cb(null, cipher.update(data))
|
||||
},
|
||||
|
||||
decrypt (data, cb) {
|
||||
cb(null, decipher.update(data))
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, res)
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('browserify-aes')
|
||||
|
||||
module.exports = {
|
||||
createCipheriv: crypto.createCipheriv,
|
||||
createDecipheriv: crypto.createDecipheriv
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('./webcrypto')()
|
||||
const nodeify = require('nodeify')
|
||||
const BN = require('asn1.js').bignum
|
||||
|
||||
const util = require('./util')
|
||||
const toBase64 = util.toBase64
|
||||
const toBn = util.toBn
|
||||
|
||||
exports.generateEphmeralKeyPair = function (curve, callback) {
|
||||
nodeify(crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
true,
|
||||
['deriveBits']
|
||||
).then((pair) => {
|
||||
// forcePrivate is used for testing only
|
||||
const genSharedKey = (theirPub, forcePrivate, cb) => {
|
||||
if (typeof forcePrivate === 'function') {
|
||||
cb = forcePrivate
|
||||
forcePrivate = undefined
|
||||
}
|
||||
|
||||
const privateKey = forcePrivate || pair.privateKey
|
||||
nodeify(crypto.subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPublicKey(curve, theirPub),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
[]
|
||||
).then((publicKey) => {
|
||||
return crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve,
|
||||
public: publicKey
|
||||
},
|
||||
privateKey,
|
||||
256
|
||||
)
|
||||
}).then((bits) => {
|
||||
// return p.derive(pub.getPublic()).toBuffer('be')
|
||||
return Buffer.from(bits)
|
||||
}), cb)
|
||||
}
|
||||
|
||||
return crypto.subtle.exportKey(
|
||||
'jwk',
|
||||
pair.publicKey
|
||||
).then((publicKey) => {
|
||||
return {
|
||||
key: marshalPublicKey(publicKey),
|
||||
genSharedKey
|
||||
}
|
||||
})
|
||||
}), callback)
|
||||
}
|
||||
|
||||
const curveLengths = {
|
||||
'P-256': 32,
|
||||
'P-384': 48,
|
||||
'P-521': 66
|
||||
}
|
||||
|
||||
// Marshal converts a jwk encodec ECDH public key into the
|
||||
// form specified in section 4.3.6 of ANSI X9.62. (This is the format
|
||||
// go-ipfs uses)
|
||||
function marshalPublicKey (jwk) {
|
||||
const byteLen = curveLengths[jwk.crv]
|
||||
|
||||
return Buffer.concat([
|
||||
Buffer([4]), // uncompressed point
|
||||
toBn(jwk.x).toBuffer('be', byteLen),
|
||||
toBn(jwk.y).toBuffer('be', byteLen)
|
||||
], 1 + byteLen * 2)
|
||||
}
|
||||
|
||||
// Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
|
||||
function unmarshalPublicKey (curve, key) {
|
||||
const byteLen = curveLengths[curve]
|
||||
|
||||
if (!key.slice(0, 1).equals(Buffer([4]))) {
|
||||
throw new Error('Invalid key format')
|
||||
}
|
||||
const x = new BN(key.slice(1, byteLen + 1))
|
||||
const y = new BN(key.slice(1 + byteLen))
|
||||
|
||||
return {
|
||||
kty: 'EC',
|
||||
crv: curve,
|
||||
x: toBase64(x),
|
||||
y: toBase64(y),
|
||||
ext: true
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const nodeify = require('nodeify')
|
||||
|
||||
const crypto = require('./webcrypto')()
|
||||
const lengths = require('./hmac-lengths')
|
||||
|
||||
const hashTypes = {
|
||||
SHA1: 'SHA-1',
|
||||
SHA256: 'SHA-256',
|
||||
SHA512: 'SHA-512'
|
||||
}
|
||||
|
||||
exports.create = function (hashType, secret, callback) {
|
||||
const hash = hashTypes[hashType]
|
||||
|
||||
nodeify(crypto.subtle.importKey(
|
||||
'raw',
|
||||
secret,
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: {name: hash}
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
).then((key) => {
|
||||
return {
|
||||
digest (data, cb) {
|
||||
nodeify(crypto.subtle.sign(
|
||||
{name: 'HMAC'},
|
||||
key,
|
||||
data
|
||||
).then((raw) => Buffer.from(raw)), cb)
|
||||
},
|
||||
length: lengths[hashType]
|
||||
}
|
||||
}), callback)
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
const lengths = require('./hmac-lengths')
|
||||
|
||||
exports.create = function (hash, secret, callback) {
|
||||
const res = {
|
||||
digest (data, cb) {
|
||||
const hmac = genFresh()
|
||||
hmac.update(data)
|
||||
|
||||
setImmediate(() => {
|
||||
cb(null, hmac.digest())
|
||||
})
|
||||
},
|
||||
length: lengths[hash]
|
||||
}
|
||||
|
||||
function genFresh () {
|
||||
return crypto.createHmac(hash.toLowerCase(), secret)
|
||||
}
|
||||
callback(null, res)
|
||||
}
|
@@ -1,228 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const nodeify = require('nodeify')
|
||||
const asn1 = require('asn1.js')
|
||||
|
||||
const util = require('./util')
|
||||
const toBase64 = util.toBase64
|
||||
const toBn = util.toBn
|
||||
const crypto = require('./webcrypto')()
|
||||
|
||||
exports.generateKey = function (bits, callback) {
|
||||
nodeify(crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: bits,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||
hash: {name: 'SHA-256'}
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
)
|
||||
.then(exportKey)
|
||||
.then((keys) => ({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
})), callback)
|
||||
}
|
||||
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = function (key, callback) {
|
||||
const privateKey = crypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: {name: 'SHA-256'}
|
||||
},
|
||||
true,
|
||||
['sign']
|
||||
)
|
||||
|
||||
nodeify(Promise.all([
|
||||
privateKey,
|
||||
derivePublicFromPrivate(key)
|
||||
]).then((keys) => exportKey({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
})).then((keys) => ({
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
})), callback)
|
||||
}
|
||||
|
||||
exports.getRandomValues = function (arr) {
|
||||
return Buffer.from(crypto.getRandomValues(arr))
|
||||
}
|
||||
|
||||
exports.hashAndSign = function (key, msg, callback) {
|
||||
nodeify(crypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: {name: 'SHA-256'}
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
).then((privateKey) => {
|
||||
return crypto.subtle.sign(
|
||||
{name: 'RSASSA-PKCS1-v1_5'},
|
||||
privateKey,
|
||||
Uint8Array.from(msg)
|
||||
)
|
||||
}).then((sig) => Buffer.from(sig)), callback)
|
||||
}
|
||||
|
||||
exports.hashAndVerify = function (key, sig, msg, callback) {
|
||||
nodeify(crypto.subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: {name: 'SHA-256'}
|
||||
},
|
||||
false,
|
||||
['verify']
|
||||
).then((publicKey) => {
|
||||
return crypto.subtle.verify(
|
||||
{name: 'RSASSA-PKCS1-v1_5'},
|
||||
publicKey,
|
||||
sig,
|
||||
msg
|
||||
)
|
||||
}), callback)
|
||||
}
|
||||
|
||||
function exportKey (pair) {
|
||||
return Promise.all([
|
||||
crypto.subtle.exportKey('jwk', pair.privateKey),
|
||||
crypto.subtle.exportKey('jwk', pair.publicKey)
|
||||
])
|
||||
}
|
||||
|
||||
function derivePublicFromPrivate (jwKey) {
|
||||
return crypto.subtle.importKey(
|
||||
'jwk',
|
||||
{
|
||||
kty: jwKey.kty,
|
||||
n: jwKey.n,
|
||||
e: jwKey.e,
|
||||
alg: jwKey.alg,
|
||||
kid: jwKey.kid
|
||||
},
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: {name: 'SHA-256'}
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
)
|
||||
}
|
||||
|
||||
const RSAPrivateKey = asn1.define('RSAPrivateKey', function () {
|
||||
this.seq().obj(
|
||||
this.key('version').int(),
|
||||
this.key('modulus').int(),
|
||||
this.key('publicExponent').int(),
|
||||
this.key('privateExponent').int(),
|
||||
this.key('prime1').int(),
|
||||
this.key('prime2').int(),
|
||||
this.key('exponent1').int(),
|
||||
this.key('exponent2').int(),
|
||||
this.key('coefficient').int()
|
||||
)
|
||||
})
|
||||
|
||||
const AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () {
|
||||
this.seq().obj(
|
||||
this.key('algorithm').objid({
|
||||
'1.2.840.113549.1.1.1': 'rsa'
|
||||
}),
|
||||
this.key('none').optional().null_(),
|
||||
this.key('curve').optional().objid(),
|
||||
this.key('params').optional().seq().obj(
|
||||
this.key('p').int(),
|
||||
this.key('q').int(),
|
||||
this.key('g').int()
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const PublicKey = asn1.define('RSAPublicKey', function () {
|
||||
this.seq().obj(
|
||||
this.key('algorithm').use(AlgorithmIdentifier),
|
||||
this.key('subjectPublicKey').bitstr()
|
||||
)
|
||||
})
|
||||
|
||||
const RSAPublicKey = asn1.define('RSAPublicKey', function () {
|
||||
this.seq().obj(
|
||||
this.key('modulus').int(),
|
||||
this.key('publicExponent').int()
|
||||
)
|
||||
})
|
||||
|
||||
// Convert a PKCS#1 in ASN1 DER format to a JWK key
|
||||
exports.pkcs1ToJwk = function (bytes) {
|
||||
const asn1 = RSAPrivateKey.decode(bytes, 'der')
|
||||
|
||||
return {
|
||||
kty: 'RSA',
|
||||
n: toBase64(asn1.modulus),
|
||||
e: toBase64(asn1.publicExponent),
|
||||
d: toBase64(asn1.privateExponent),
|
||||
p: toBase64(asn1.prime1),
|
||||
q: toBase64(asn1.prime2),
|
||||
dp: toBase64(asn1.exponent1),
|
||||
dq: toBase64(asn1.exponent2),
|
||||
qi: toBase64(asn1.coefficient),
|
||||
alg: 'RS256',
|
||||
kid: '2011-04-29'
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a JWK key into PKCS#1 in ASN1 DER format
|
||||
exports.jwkToPkcs1 = function (jwk) {
|
||||
return RSAPrivateKey.encode({
|
||||
version: 0,
|
||||
modulus: toBn(jwk.n),
|
||||
publicExponent: toBn(jwk.e),
|
||||
privateExponent: toBn(jwk.d),
|
||||
prime1: toBn(jwk.p),
|
||||
prime2: toBn(jwk.q),
|
||||
exponent1: toBn(jwk.dp),
|
||||
exponent2: toBn(jwk.dq),
|
||||
coefficient: toBn(jwk.qi)
|
||||
}, 'der')
|
||||
}
|
||||
|
||||
// Convert a PKCIX in ASN1 DER format to a JWK key
|
||||
exports.pkixToJwk = function (bytes) {
|
||||
const ndata = PublicKey.decode(bytes, 'der')
|
||||
const asn1 = RSAPublicKey.decode(ndata.subjectPublicKey.data, 'der')
|
||||
|
||||
return {
|
||||
kty: 'RSA',
|
||||
n: toBase64(asn1.modulus),
|
||||
e: toBase64(asn1.publicExponent),
|
||||
alg: 'RS256',
|
||||
kid: '2011-04-29'
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a JWK key to PKCIX in ASN1 DER format
|
||||
exports.jwkToPkix = function (jwk) {
|
||||
return PublicKey.encode({
|
||||
algorithm: {
|
||||
algorithm: 'rsa',
|
||||
none: null
|
||||
},
|
||||
subjectPublicKey: {
|
||||
data: RSAPublicKey.encode({
|
||||
modulus: toBn(jwk.n),
|
||||
publicExponent: toBn(jwk.e)
|
||||
}, 'der')
|
||||
}
|
||||
}, 'der')
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const BN = require('asn1.js').bignum
|
||||
|
||||
// Convert a BN.js instance to a base64 encoded string without padding
|
||||
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
|
||||
exports.toBase64 = function toBase64 (bn) {
|
||||
let s = bn.toBuffer('be').toString('base64')
|
||||
|
||||
return s
|
||||
.replace(/(=*)$/, '') // Remove any trailing '='s
|
||||
.replace(/\+/g, '-') // 62nd char of encoding
|
||||
.replace(/\//g, '_') // 63rd char of encoding
|
||||
}
|
||||
|
||||
// Convert a base64 encoded string to a BN.js instance
|
||||
exports.toBn = function toBn (str) {
|
||||
return new BN(Buffer.from(str, 'base64'))
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function getWebCrypto () {
|
||||
if (typeof window !== 'undefined') {
|
||||
// This is only a shim for interfaces, not for functionality
|
||||
require('webcrypto-shim')(window)
|
||||
|
||||
if (window.crypto) {
|
||||
return window.crypto
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Please use an environment with crypto support')
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function getWebCrypto () {
|
||||
const WebCrypto = require('node-webcrypto-ossl')
|
||||
const webCrypto = new WebCrypto()
|
||||
return webCrypto
|
||||
}
|
37
src/hmac/index-browser.js
Normal file
37
src/hmac/index-browser.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const webcrypto = require('../webcrypto')
|
||||
const lengths = require('./lengths')
|
||||
|
||||
const hashTypes = {
|
||||
SHA1: 'SHA-1',
|
||||
SHA256: 'SHA-256',
|
||||
SHA512: 'SHA-512'
|
||||
}
|
||||
|
||||
const sign = async (key, data) => {
|
||||
const buf = await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data)
|
||||
return new Uint8Array(buf, buf.byteOffset, buf.byteLength)
|
||||
}
|
||||
|
||||
exports.create = async function (hashType, secret) {
|
||||
const hash = hashTypes[hashType]
|
||||
|
||||
const key = await webcrypto.get().subtle.importKey(
|
||||
'raw',
|
||||
secret,
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: { name: hash }
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
return {
|
||||
async digest (data) { // eslint-disable-line require-await
|
||||
return sign(key, data)
|
||||
},
|
||||
length: lengths[hashType]
|
||||
}
|
||||
}
|
17
src/hmac/index.js
Normal file
17
src/hmac/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const lengths = require('./lengths')
|
||||
|
||||
exports.create = async function (hash, secret) { // eslint-disable-line require-await
|
||||
const res = {
|
||||
async digest (data) { // eslint-disable-line require-await
|
||||
const hmac = crypto.createHmac(hash.toLowerCase(), secret)
|
||||
hmac.update(data)
|
||||
return hmac.digest()
|
||||
},
|
||||
length: lengths[hash]
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
338
src/index.d.ts
vendored
Normal file
338
src/index.d.ts
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Supported key types.
|
||||
*/
|
||||
export type KeyType = "Ed25519" | "RSA" | "secp256k1";
|
||||
|
||||
/**
|
||||
* Maps an IPFS hash name to its node-forge equivalent.
|
||||
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
|
||||
*/
|
||||
export type HashType = "SHA1" | "SHA256" | "SHA512";
|
||||
|
||||
/**
|
||||
* Supported curve types.
|
||||
*/
|
||||
export type CurveType = "P-256" | "P-384" | "P-521";
|
||||
|
||||
/**
|
||||
* Supported cipher types.
|
||||
*/
|
||||
export type CipherType = "AES-128" | "AES-256" | "Blowfish";
|
||||
|
||||
/**
|
||||
* Exposes an interface to AES encryption (formerly Rijndael),
|
||||
* as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
* This uses CTR mode.
|
||||
*/
|
||||
export namespace aes {
|
||||
/**
|
||||
* AES Cipher in CTR mode.
|
||||
*/
|
||||
interface Cipher {
|
||||
encrypt(data: Uint8Array): Promise<Uint8Array>;
|
||||
decrypt(data: Uint8Array): Promise<Uint8Array>;
|
||||
}
|
||||
/**
|
||||
* Create a new AES Cipher.
|
||||
* @param key The key, if length 16 then AES 128 is used. For length 32, AES 256 is used.
|
||||
* @param iv Must have length 16.
|
||||
*/
|
||||
function create(key: Uint8Array, iv: Uint8Array): Promise<Cipher>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC)
|
||||
* as defined in U.S. Federal Information Processing Standards Publication 198.
|
||||
* An HMAC is a cryptographic hash that uses a key to sign a message.
|
||||
* The receiver verifies the hash by recomputing it using the same key.
|
||||
*/
|
||||
export namespace hmac {
|
||||
/**
|
||||
* HMAC Digest.
|
||||
*/
|
||||
interface Digest {
|
||||
digest(data: Uint8Array): Promise<Uint8Array>;
|
||||
length: 20 | 32 | 64 | number;
|
||||
}
|
||||
/**
|
||||
* Create a new HMAC Digest.
|
||||
*/
|
||||
function create(
|
||||
hash: "SHA1" | "SHA256" | "SHA512" | string,
|
||||
secret: Uint8Array
|
||||
): Promise<Digest>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic public key interface.
|
||||
*/
|
||||
export interface PublicKey {
|
||||
readonly bytes: Uint8Array;
|
||||
verify(data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
||||
marshal(): Uint8Array;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic private key interface.
|
||||
*/
|
||||
export interface PrivateKey {
|
||||
readonly public: PublicKey;
|
||||
readonly bytes: Uint8Array;
|
||||
sign(data: Uint8Array): Promise<Uint8Array>;
|
||||
marshal(): Uint8Array;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
/**
|
||||
* Gets the ID of the key.
|
||||
*
|
||||
* The key id is the base58 encoding of the SHA-256 multihash of its public key.
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*/
|
||||
id(): Promise<string>;
|
||||
/**
|
||||
* Exports the password protected key in the format specified.
|
||||
*/
|
||||
export(password: string, format?: "pkcs-8" | string): Promise<string>;
|
||||
}
|
||||
|
||||
export interface Keystretcher {
|
||||
(res: Uint8Array): Keystretcher;
|
||||
iv: Uint8Array;
|
||||
cipherKey: Uint8Array;
|
||||
macKey: Uint8Array;
|
||||
}
|
||||
|
||||
export interface StretchPair {
|
||||
k1: Keystretcher;
|
||||
k2: Keystretcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes an interface to various cryptographic key generation routines.
|
||||
* Currently the 'RSA' and 'ed25519' types are supported, although ed25519 keys
|
||||
* support only signing and verification of messages. For encryption / decryption
|
||||
* support, RSA keys should be used.
|
||||
* Installing the libp2p-crypto-secp256k1 module adds support for the 'secp256k1'
|
||||
* type, which supports ECDSA signatures using the secp256k1 elliptic curve
|
||||
* popularized by Bitcoin. This module is not installed by default, and should be
|
||||
* explicitly depended on if your project requires secp256k1 support.
|
||||
*/
|
||||
export namespace keys {
|
||||
export {};
|
||||
export namespace supportedKeys {
|
||||
namespace rsa {
|
||||
class RsaPublicKey implements PublicKey {
|
||||
constructor(key: Uint8Array);
|
||||
readonly bytes: Uint8Array;
|
||||
verify(data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
||||
marshal(): Uint8Array;
|
||||
encrypt(bytes: Uint8Array): Uint8Array;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
class RsaPrivateKey implements PrivateKey {
|
||||
constructor(key: any, publicKey: Uint8Array);
|
||||
readonly public: RsaPublicKey;
|
||||
readonly bytes: Uint8Array;
|
||||
genSecret(): Uint8Array;
|
||||
sign(data: Uint8Array): Promise<Uint8Array>;
|
||||
decrypt(bytes: Uint8Array): Uint8Array;
|
||||
marshal(): Uint8Array;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
id(): Promise<string>;
|
||||
export(password: string, format?: string): Promise<string>;
|
||||
}
|
||||
function unmarshalRsaPublicKey(buf: Uint8Array): RsaPublicKey;
|
||||
function unmarshalRsaPrivateKey(buf: Uint8Array): Promise<RsaPrivateKey>;
|
||||
function generateKeyPair(bits: number): Promise<RsaPrivateKey>;
|
||||
function fromJwk(jwk: Uint8Array): Promise<RsaPrivateKey>;
|
||||
}
|
||||
|
||||
namespace ed25519 {
|
||||
class Ed25519PublicKey implements PublicKey {
|
||||
constructor(key: Uint8Array);
|
||||
readonly bytes: Uint8Array;
|
||||
verify(data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
||||
marshal(): Uint8Array;
|
||||
encrypt(bytes: Uint8Array): Uint8Array;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
class Ed25519PrivateKey implements PrivateKey {
|
||||
constructor(key: Uint8Array, publicKey: Uint8Array);
|
||||
readonly public: Ed25519PublicKey;
|
||||
readonly bytes: Uint8Array;
|
||||
sign(data: Uint8Array): Promise<Uint8Array>;
|
||||
marshal(): Uint8Array;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
id(): Promise<string>;
|
||||
export(password: string, format?: string): Promise<string>;
|
||||
}
|
||||
|
||||
function unmarshalEd25519PrivateKey(
|
||||
buf: Uint8Array
|
||||
): Promise<Ed25519PrivateKey>;
|
||||
function unmarshalEd25519PublicKey(buf: Uint8Array): Ed25519PublicKey;
|
||||
function generateKeyPair(): Promise<Ed25519PrivateKey>;
|
||||
function generateKeyPairFromSeed(
|
||||
seed: Uint8Array
|
||||
): Promise<Ed25519PrivateKey>;
|
||||
}
|
||||
|
||||
namespace secp256k1 {
|
||||
class Secp256k1PublicKey implements PublicKey {
|
||||
constructor(key: Uint8Array);
|
||||
readonly bytes: Uint8Array;
|
||||
verify(data: Uint8Array, sig: Uint8Array): Promise<boolean>;
|
||||
marshal(): Uint8Array;
|
||||
encrypt(bytes: Uint8Array): Uint8Array;
|
||||
equals(key: PublicKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
class Secp256k1PrivateKey implements PrivateKey {
|
||||
constructor(key: Uint8Array, publicKey: Uint8Array);
|
||||
readonly public: Secp256k1PublicKey;
|
||||
readonly bytes: Uint8Array;
|
||||
sign(data: Uint8Array): Promise<Uint8Array>;
|
||||
marshal(): Uint8Array;
|
||||
equals(key: PrivateKey): boolean;
|
||||
hash(): Promise<Uint8Array>;
|
||||
id(): Promise<string>;
|
||||
export(password: string, format?: string): Promise<string>;
|
||||
}
|
||||
|
||||
function unmarshalSecp256k1PrivateKey(
|
||||
bytes: Uint8Array
|
||||
): Promise<Secp256k1PrivateKey>;
|
||||
function unmarshalSecp256k1PublicKey(bytes: Uint8Array): Secp256k1PublicKey;
|
||||
function generateKeyPair(): Promise<Secp256k1PrivateKey>;
|
||||
}
|
||||
}
|
||||
|
||||
export const keysPBM: any;
|
||||
|
||||
/**
|
||||
* Generates a keypair of the given type and bitsize.
|
||||
* @param type One of the supported key types.
|
||||
* @param bits Number of bits. Minimum of 1024.
|
||||
*/
|
||||
export function generateKeyPair(
|
||||
type: KeyType | string,
|
||||
bits: number
|
||||
): Promise<PrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "Ed25519"
|
||||
): Promise<keys.supportedKeys.ed25519.Ed25519PrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "RSA",
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.rsa.RsaPrivateKey>;
|
||||
export function generateKeyPair(
|
||||
type: "secp256k1"
|
||||
): Promise<keys.supportedKeys.secp256k1.Secp256k1PrivateKey>;
|
||||
|
||||
/**
|
||||
* Generates a keypair of the given type and bitsize.
|
||||
* @param type One of the supported key types. Currently only 'Ed25519' is supported.
|
||||
* @param seed A 32 byte uint8array.
|
||||
* @param bits Number of bits. Minimum of 1024.
|
||||
*/
|
||||
export function generateKeyPairFromSeed(
|
||||
type: KeyType | string,
|
||||
seed: Uint8Array,
|
||||
bits: number
|
||||
): Promise<PrivateKey>;
|
||||
export function generateKeyPairFromSeed(
|
||||
type: "Ed25519",
|
||||
seed: Uint8Array,
|
||||
bits: number
|
||||
): Promise<keys.supportedKeys.ed25519.Ed25519PrivateKey>;
|
||||
|
||||
/**
|
||||
* Generates an ephemeral public key and returns a function that will compute the shared secret key.
|
||||
* Focuses only on ECDH now, but can be made more general in the future.
|
||||
* @param curve The curve to use. One of 'P-256', 'P-384', 'P-521' is currently supported.
|
||||
*/
|
||||
export function generateEphemeralKeyPair(
|
||||
curve: CurveType | string
|
||||
): Promise<{
|
||||
key: Uint8Array;
|
||||
genSharedKey: (theirPub: Uint8Array, forcePrivate?: any) => Promise<Uint8Array>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Generates a set of keys for each party by stretching the shared key.
|
||||
* @param cipherType The cipher type to use. One of 'AES-128', 'AES-256', or 'Blowfish'
|
||||
* @param hashType The hash type to use. One of 'SHA1', 'SHA2256', or 'SHA2512'.
|
||||
* @param secret The shared key secret.
|
||||
*/
|
||||
export function keyStretcher(
|
||||
cipherType: CipherType | string,
|
||||
hashType: HashType | string,
|
||||
secret: Uint8Array | string
|
||||
): Promise<StretchPair>;
|
||||
|
||||
/**
|
||||
* Converts a protobuf serialized public key into its representative object.
|
||||
* @param buf The protobuf serialized public key.
|
||||
*/
|
||||
export function unmarshalPublicKey(buf: Uint8Array): PublicKey;
|
||||
|
||||
/**
|
||||
* Converts a public key object into a protobuf serialized public key.
|
||||
* @param key An RSA, Ed25519, or Secp256k1 public key object.
|
||||
* @param type One of the supported key types.
|
||||
*/
|
||||
export function marshalPublicKey(key: PublicKey, type?: KeyType | string): Uint8Array;
|
||||
|
||||
/**
|
||||
* Converts a protobuf serialized private key into its representative object.
|
||||
* @param buf The protobuf serialized private key.
|
||||
*/
|
||||
export function unmarshalPrivateKey(buf: Uint8Array): Promise<PrivateKey>;
|
||||
|
||||
/**
|
||||
* Converts a private key object into a protobuf serialized private key.
|
||||
* @param key An RSA, Ed25519, or Secp256k1 private key object.
|
||||
* @param type One of the supported key types.
|
||||
*/
|
||||
export function marshalPrivateKey(key: PrivateKey, type?: KeyType | string): Uint8Array;
|
||||
|
||||
/**
|
||||
* Converts a PEM password protected private key into its representative object.
|
||||
* @param pem Password protected private key in PEM format.
|
||||
* @param password The password used to protect the key.
|
||||
*/
|
||||
function _import(pem: string, password: string, format?: string): Promise<supportedKeys.rsa.RsaPrivateKey>;
|
||||
export { _import as import };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Uint8Array populated by random bytes.
|
||||
* @param The size of the random bytes Uint8Array.
|
||||
*/
|
||||
export function randomBytes(number: number): Uint8Array;
|
||||
|
||||
/**
|
||||
* Computes the Password-Based Key Derivation Function 2.
|
||||
* @param password The password.
|
||||
* @param salt The salt.
|
||||
* @param iterations Number of iterations to use.
|
||||
* @param keySize The size of the output key in bytes.
|
||||
* @param hash The hash name ('sha1', 'sha2-512, ...)
|
||||
*/
|
||||
export function pbkdf2(
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
iterations: number,
|
||||
keySize: number,
|
||||
hash: string
|
||||
): Uint8Array;
|
78
src/index.js
78
src/index.js
@@ -1,73 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const protobuf = require('protocol-buffers')
|
||||
const pbm = protobuf(require('./crypto.proto'))
|
||||
const c = require('./crypto')
|
||||
const hmac = require('./hmac')
|
||||
const aes = require('./aes')
|
||||
const keys = require('./keys')
|
||||
|
||||
exports.hmac = c.hmac
|
||||
exports.aes = c.aes
|
||||
exports.webcrypto = c.webcrypto
|
||||
|
||||
const keys = exports.keys = require('./keys')
|
||||
exports.keyStretcher = require('./key-stretcher')
|
||||
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
||||
|
||||
// Generates a keypair of the given type and bitsize
|
||||
exports.generateKeyPair = (type, bits, cb) => {
|
||||
let key = keys[type.toLowerCase()]
|
||||
if (!key) {
|
||||
return cb(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
|
||||
key.generateKeyPair(bits, cb)
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized public key into its
|
||||
// representative object
|
||||
exports.unmarshalPublicKey = (buf) => {
|
||||
const decoded = pbm.PublicKey.decode(buf)
|
||||
|
||||
switch (decoded.Type) {
|
||||
case pbm.KeyType.RSA:
|
||||
return keys.rsa.unmarshalRsaPublicKey(decoded.Data)
|
||||
default:
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a public key object into a protobuf serialized public key
|
||||
exports.marshalPublicKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
|
||||
// for now only rsa is supported
|
||||
if (type !== 'rsa') {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
return key.bytes
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized private key into its
|
||||
// representative object
|
||||
exports.unmarshalPrivateKey = (buf, callback) => {
|
||||
const decoded = pbm.PrivateKey.decode(buf)
|
||||
|
||||
switch (decoded.Type) {
|
||||
case pbm.KeyType.RSA:
|
||||
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback)
|
||||
default:
|
||||
callback(new Error('invalid or unsupported key type'))
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a private key object into a protobuf serialized private key
|
||||
exports.marshalPrivateKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
|
||||
// for now only rsa is supported
|
||||
if (type !== 'rsa') {
|
||||
throw new Error('invalid or unsupported key type')
|
||||
}
|
||||
|
||||
return key.bytes
|
||||
}
|
||||
exports.aes = aes
|
||||
exports.hmac = hmac
|
||||
exports.keys = keys
|
||||
exports.randomBytes = require('./random-bytes')
|
||||
exports.pbkdf2 = require('./pbkdf2')
|
||||
|
@@ -1,108 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('./crypto')
|
||||
const whilst = require('async/whilst')
|
||||
|
||||
const cipherMap = {
|
||||
'AES-128': {
|
||||
ivSize: 16,
|
||||
keySize: 16
|
||||
},
|
||||
'AES-256': {
|
||||
ivSize: 16,
|
||||
keySize: 32
|
||||
},
|
||||
Blowfish: {
|
||||
ivSize: 8,
|
||||
cipherKeySize: 32
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a set of keys for each party by stretching the shared key.
|
||||
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
||||
module.exports = (cipherType, hash, secret, callback) => {
|
||||
const cipher = cipherMap[cipherType]
|
||||
|
||||
if (!cipher) {
|
||||
return callback(new Error('unkown cipherType passed'))
|
||||
}
|
||||
|
||||
if (!hash) {
|
||||
return callback(new Error('unkown hashType passed'))
|
||||
}
|
||||
|
||||
const cipherKeySize = cipher.keySize
|
||||
const ivSize = cipher.ivSize
|
||||
const hmacKeySize = 20
|
||||
const seed = Buffer.from('key expansion')
|
||||
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
|
||||
|
||||
crypto.hmac.create(hash, secret, (err, m) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
m.digest(seed, (err, a) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
let result = []
|
||||
let j = 0
|
||||
|
||||
whilst(
|
||||
() => j < resultLength,
|
||||
stretch,
|
||||
finish
|
||||
)
|
||||
|
||||
function stretch (cb) {
|
||||
m.digest(Buffer.concat([a, seed]), (err, b) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
let todo = b.length
|
||||
|
||||
if (j + todo > resultLength) {
|
||||
todo = resultLength - j
|
||||
}
|
||||
|
||||
result.push(b)
|
||||
|
||||
j += todo
|
||||
|
||||
m.digest(a, (err, _a) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
a = _a
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function finish (err) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
const half = resultLength / 2
|
||||
const resultBuffer = Buffer.concat(result)
|
||||
const r1 = resultBuffer.slice(0, half)
|
||||
const r2 = resultBuffer.slice(half, resultLength)
|
||||
|
||||
const createKey = (res) => ({
|
||||
iv: res.slice(0, ivSize),
|
||||
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
|
||||
macKey: res.slice(ivSize + cipherKeySize)
|
||||
})
|
||||
|
||||
callback(null, {
|
||||
k1: createKey(r1),
|
||||
k2: createKey(r2)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
121
src/keys/ecdh-browser.js
Normal file
121
src/keys/ecdh-browser.js
Normal file
@@ -0,0 +1,121 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const { base64urlToBuffer } = require('../util')
|
||||
const validateCurveType = require('./validate-curve-type')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
const uint8ArrayConcat = require('uint8arrays/concat')
|
||||
const uint8ArrayEquals = require('uint8arrays/equals')
|
||||
|
||||
const bits = {
|
||||
'P-256': 256,
|
||||
'P-384': 384,
|
||||
'P-521': 521
|
||||
}
|
||||
|
||||
exports.generateEphmeralKeyPair = async function (curve) {
|
||||
validateCurveType(Object.keys(bits), curve)
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
true,
|
||||
['deriveBits']
|
||||
)
|
||||
|
||||
// forcePrivate is used for testing only
|
||||
const genSharedKey = async (theirPub, forcePrivate) => {
|
||||
let privateKey
|
||||
|
||||
if (forcePrivate) {
|
||||
privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPrivateKey(curve, forcePrivate),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
['deriveBits']
|
||||
)
|
||||
} else {
|
||||
privateKey = pair.privateKey
|
||||
}
|
||||
|
||||
const keys = [
|
||||
await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPublicKey(curve, theirPub),
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
},
|
||||
false,
|
||||
[]
|
||||
),
|
||||
privateKey
|
||||
]
|
||||
|
||||
const buffer = await webcrypto.get().subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve,
|
||||
public: keys[0]
|
||||
},
|
||||
keys[1],
|
||||
bits[curve]
|
||||
)
|
||||
|
||||
return new Uint8Array(buffer, buffer.byteOffset, buffer.byteLength)
|
||||
}
|
||||
|
||||
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
|
||||
return {
|
||||
key: marshalPublicKey(publicKey),
|
||||
genSharedKey
|
||||
}
|
||||
}
|
||||
|
||||
const curveLengths = {
|
||||
'P-256': 32,
|
||||
'P-384': 48,
|
||||
'P-521': 66
|
||||
}
|
||||
|
||||
// Marshal converts a jwk encodec ECDH public key into the
|
||||
// form specified in section 4.3.6 of ANSI X9.62. (This is the format
|
||||
// go-ipfs uses)
|
||||
function marshalPublicKey (jwk) {
|
||||
const byteLen = curveLengths[jwk.crv]
|
||||
|
||||
return uint8ArrayConcat([
|
||||
Uint8Array.from([4]), // uncompressed point
|
||||
base64urlToBuffer(jwk.x, byteLen),
|
||||
base64urlToBuffer(jwk.y, byteLen)
|
||||
], 1 + byteLen * 2)
|
||||
}
|
||||
|
||||
// Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
|
||||
function unmarshalPublicKey (curve, key) {
|
||||
const byteLen = curveLengths[curve]
|
||||
|
||||
if (uint8ArrayEquals(!key.slice(0, 1), Uint8Array.from([4]))) {
|
||||
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
|
||||
}
|
||||
|
||||
return {
|
||||
kty: 'EC',
|
||||
crv: curve,
|
||||
x: uint8ArrayToString(key.slice(1, byteLen + 1), 'base64url'),
|
||||
y: uint8ArrayToString(key.slice(1 + byteLen), 'base64url'),
|
||||
ext: true
|
||||
}
|
||||
}
|
||||
|
||||
const unmarshalPrivateKey = (curve, key) => ({
|
||||
...unmarshalPublicKey(curve, key.public),
|
||||
d: uint8ArrayToString(key.private, 'base64url')
|
||||
})
|
28
src/keys/ecdh.js
Normal file
28
src/keys/ecdh.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const validateCurveType = require('./validate-curve-type')
|
||||
|
||||
const curves = {
|
||||
'P-256': 'prime256v1',
|
||||
'P-384': 'secp384r1',
|
||||
'P-521': 'secp521r1'
|
||||
}
|
||||
|
||||
exports.generateEphmeralKeyPair = async function (curve) { // eslint-disable-line require-await
|
||||
validateCurveType(Object.keys(curves), curve)
|
||||
|
||||
const ecdh = crypto.createECDH(curves[curve])
|
||||
ecdh.generateKeys()
|
||||
|
||||
return {
|
||||
key: ecdh.getPublicKey(),
|
||||
async genSharedKey (theirPub, forcePrivate) { // eslint-disable-line require-await
|
||||
if (forcePrivate) {
|
||||
ecdh.setPrivateKey(forcePrivate.private)
|
||||
}
|
||||
|
||||
return ecdh.computeSecret(theirPub)
|
||||
}
|
||||
}
|
||||
}
|
151
src/keys/ed25519-class.js
Normal file
151
src/keys/ed25519-class.js
Normal file
@@ -0,0 +1,151 @@
|
||||
'use strict'
|
||||
|
||||
const sha = require('multihashing-async/src/sha')
|
||||
const protobuf = require('protons')
|
||||
const errcode = require('err-code')
|
||||
const uint8ArrayEquals = require('uint8arrays/equals')
|
||||
const mh = require('multihashes')
|
||||
const crypto = require('./ed25519')
|
||||
const pbm = protobuf(require('./keys.proto'))
|
||||
const exporter = require('./exporter')
|
||||
|
||||
class Ed25519PublicKey {
|
||||
constructor (key) {
|
||||
this._key = ensureKey(key, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
async verify (data, sig) { // eslint-disable-line require-await
|
||||
return crypto.hashAndVerify(this._key, sig, data)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return this._key
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PublicKey.encode({
|
||||
Type: pbm.KeyType.Ed25519,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
}
|
||||
|
||||
class Ed25519PrivateKey {
|
||||
// key - 64 byte Uint8Array containing private key
|
||||
// publicKey - 32 byte Uint8Array containing public key
|
||||
constructor (key, publicKey) {
|
||||
this._key = ensureKey(key, crypto.privateKeyLength)
|
||||
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
|
||||
}
|
||||
|
||||
async sign (message) { // eslint-disable-line require-await
|
||||
return crypto.hashAndSign(this._key, message)
|
||||
}
|
||||
|
||||
get public () {
|
||||
return new Ed25519PublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return this._key
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PrivateKey.encode({
|
||||
Type: pbm.KeyType.Ed25519,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the key.
|
||||
*
|
||||
* The key id is the base58 encoding of the SHA-256 multihash of its public key.
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*
|
||||
* @returns {Promise<String>}
|
||||
*/
|
||||
async id () {
|
||||
const encoding = mh.encode(this.public.bytes, 'identity')
|
||||
return await mh.toB58String(encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key into a password protected `format`
|
||||
*
|
||||
* @param {string} password - The password to encrypt the key
|
||||
* @param {string} [format=libp2p-key] - The format in which to export as
|
||||
* @returns {Promise<Uint8Array>} The encrypted private key
|
||||
*/
|
||||
async export (password, format = 'libp2p-key') { // eslint-disable-line require-await
|
||||
if (format === 'libp2p-key') {
|
||||
return exporter.export(this.bytes, password)
|
||||
} else {
|
||||
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unmarshalEd25519PrivateKey (bytes) {
|
||||
// Try the old, redundant public key version
|
||||
if (bytes.length > crypto.privateKeyLength) {
|
||||
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
|
||||
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
|
||||
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
|
||||
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
|
||||
}
|
||||
|
||||
bytes = ensureKey(bytes, crypto.privateKeyLength)
|
||||
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
|
||||
const publicKeyBytes = bytes.slice(crypto.publicKeyLength)
|
||||
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
|
||||
}
|
||||
|
||||
function unmarshalEd25519PublicKey (bytes) {
|
||||
bytes = ensureKey(bytes, crypto.publicKeyLength)
|
||||
return new Ed25519PublicKey(bytes)
|
||||
}
|
||||
|
||||
async function generateKeyPair () {
|
||||
const { privateKey, publicKey } = await crypto.generateKey()
|
||||
return new Ed25519PrivateKey(privateKey, publicKey)
|
||||
}
|
||||
|
||||
async function generateKeyPairFromSeed (seed) {
|
||||
const { privateKey, publicKey } = await crypto.generateKeyFromSeed(seed)
|
||||
return new Ed25519PrivateKey(privateKey, publicKey)
|
||||
}
|
||||
|
||||
function ensureKey (key, length) {
|
||||
key = Uint8Array.from(key || [])
|
||||
if (key.length !== length) {
|
||||
throw errcode(new Error(`Key must be a Uint8Array of length ${length}, got ${key.length}`), 'ERR_INVALID_KEY_TYPE')
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Ed25519PublicKey,
|
||||
Ed25519PrivateKey,
|
||||
unmarshalEd25519PrivateKey,
|
||||
unmarshalEd25519PublicKey,
|
||||
generateKeyPair,
|
||||
generateKeyPairFromSeed
|
||||
}
|
24
src/keys/ed25519.js
Normal file
24
src/keys/ed25519.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
require('node-forge/lib/ed25519')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
exports.publicKeyLength = forge.pki.ed25519.constants.PUBLIC_KEY_BYTE_LENGTH
|
||||
exports.privateKeyLength = forge.pki.ed25519.constants.PRIVATE_KEY_BYTE_LENGTH
|
||||
|
||||
exports.generateKey = async function () { // eslint-disable-line require-await
|
||||
return forge.pki.ed25519.generateKeyPair()
|
||||
}
|
||||
|
||||
// seed should be a 32 byte uint8array
|
||||
exports.generateKeyFromSeed = async function (seed) { // eslint-disable-line require-await
|
||||
return forge.pki.ed25519.generateKeyPair({ seed })
|
||||
}
|
||||
|
||||
exports.hashAndSign = async function (key, msg) { // eslint-disable-line require-await
|
||||
return forge.pki.ed25519.sign({ message: msg, privateKey: key })
|
||||
// return Uint8Array.from(nacl.sign.detached(msg, key))
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line require-await
|
||||
return forge.pki.ed25519.verify({ signature: sig, message: msg, publicKey: key })
|
||||
}
|
@@ -1,11 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('./crypto')
|
||||
const ecdh = require('./ecdh')
|
||||
|
||||
// Generates an ephemeral public key and returns a function that will compute
|
||||
// the shared secret key.
|
||||
//
|
||||
// Focuses only on ECDH now, but can be made more general in the future.
|
||||
module.exports = (curve, callback) => {
|
||||
crypto.ecdh.generateEphmeralKeyPair(curve, callback)
|
||||
}
|
||||
module.exports = async (curve) => ecdh.generateEphmeralKeyPair(curve) // eslint-disable-line require-await
|
22
src/keys/exporter.js
Normal file
22
src/keys/exporter.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict'
|
||||
|
||||
const multibase = require('multibase')
|
||||
const ciphers = require('../ciphers/aes-gcm')
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Exports the given PrivateKey as a base64 encoded string.
|
||||
* The PrivateKey is encrypted via a password derived PBKDF2 key
|
||||
* leveraging the aes-gcm cipher algorithm.
|
||||
*
|
||||
* @param {Uint8Array} privateKey The PrivateKey protobuf
|
||||
* @param {string} password
|
||||
* @returns {Promise<string>} A base64 encoded string
|
||||
*/
|
||||
export: async function (privateKey, password) {
|
||||
const cipher = ciphers.create()
|
||||
const encryptedKey = await cipher.encrypt(privateKey, password)
|
||||
const base64 = multibase.names.base64
|
||||
return base64.encode(encryptedKey)
|
||||
}
|
||||
}
|
22
src/keys/importer.js
Normal file
22
src/keys/importer.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict'
|
||||
|
||||
const multibase = require('multibase')
|
||||
const ciphers = require('../ciphers/aes-gcm')
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Attempts to decrypt a base64 encoded PrivateKey string
|
||||
* with the given password. The privateKey must have been exported
|
||||
* using the same password and underlying cipher (aes-gcm)
|
||||
*
|
||||
* @param {string} privateKey A base64 encoded encrypted key
|
||||
* @param {string} password
|
||||
* @returns {Promise<Uint8Array>} The private key protobuf
|
||||
*/
|
||||
import: async function (privateKey, password) {
|
||||
const base64 = multibase.names.base64
|
||||
const encryptedKey = base64.decode(privateKey)
|
||||
const cipher = ciphers.create()
|
||||
return await cipher.decrypt(encryptedKey, password)
|
||||
}
|
||||
}
|
@@ -1,5 +1,135 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
rsa: require('./rsa')
|
||||
const protobuf = require('protons')
|
||||
const keysPBM = protobuf(require('./keys.proto'))
|
||||
require('node-forge/lib/asn1')
|
||||
require('node-forge/lib/pbe')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const errcode = require('err-code')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
const importer = require('./importer')
|
||||
|
||||
exports = module.exports
|
||||
|
||||
const supportedKeys = {
|
||||
rsa: require('./rsa-class'),
|
||||
ed25519: require('./ed25519-class'),
|
||||
secp256k1: require('./secp256k1-class')(keysPBM, require('../random-bytes'))
|
||||
}
|
||||
|
||||
exports.supportedKeys = supportedKeys
|
||||
exports.keysPBM = keysPBM
|
||||
|
||||
const ErrMissingSecp256K1 = {
|
||||
message: 'secp256k1 support requires libp2p-crypto-secp256k1 package',
|
||||
code: 'ERR_MISSING_PACKAGE'
|
||||
}
|
||||
|
||||
function typeToKey (type) {
|
||||
const key = supportedKeys[type.toLowerCase()]
|
||||
if (!key) {
|
||||
const supported = Object.keys(supportedKeys).join(' / ')
|
||||
throw errcode(new Error(`invalid or unsupported key type ${type}. Must be ${supported}`), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
exports.keyStretcher = require('./key-stretcher')
|
||||
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
|
||||
|
||||
// Generates a keypair of the given type and bitsize
|
||||
exports.generateKeyPair = async (type, bits) => { // eslint-disable-line require-await
|
||||
return typeToKey(type).generateKeyPair(bits)
|
||||
}
|
||||
|
||||
// Generates a keypair of the given type and bitsize
|
||||
// seed is a 32 byte uint8array
|
||||
exports.generateKeyPairFromSeed = async (type, seed, bits) => { // eslint-disable-line require-await
|
||||
const key = typeToKey(type)
|
||||
if (type.toLowerCase() !== 'ed25519') {
|
||||
throw errcode(new Error('Seed key derivation is unimplemented for RSA or secp256k1'), 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
|
||||
}
|
||||
return key.generateKeyPairFromSeed(seed, bits)
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized public key into its
|
||||
// representative object
|
||||
exports.unmarshalPublicKey = (buf) => {
|
||||
const decoded = keysPBM.PublicKey.decode(buf)
|
||||
const data = decoded.Data
|
||||
|
||||
switch (decoded.Type) {
|
||||
case keysPBM.KeyType.RSA:
|
||||
return supportedKeys.rsa.unmarshalRsaPublicKey(data)
|
||||
case keysPBM.KeyType.Ed25519:
|
||||
return supportedKeys.ed25519.unmarshalEd25519PublicKey(data)
|
||||
case keysPBM.KeyType.Secp256k1:
|
||||
if (supportedKeys.secp256k1) {
|
||||
return supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(data)
|
||||
} else {
|
||||
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
|
||||
}
|
||||
default:
|
||||
typeToKey(decoded.Type) // throws because type is not supported
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a public key object into a protobuf serialized public key
|
||||
exports.marshalPublicKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
typeToKey(type) // check type
|
||||
return key.bytes
|
||||
}
|
||||
|
||||
// Converts a protobuf serialized private key into its
|
||||
// representative object
|
||||
exports.unmarshalPrivateKey = async (buf) => { // eslint-disable-line require-await
|
||||
const decoded = keysPBM.PrivateKey.decode(buf)
|
||||
const data = decoded.Data
|
||||
|
||||
switch (decoded.Type) {
|
||||
case keysPBM.KeyType.RSA:
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(data)
|
||||
case keysPBM.KeyType.Ed25519:
|
||||
return supportedKeys.ed25519.unmarshalEd25519PrivateKey(data)
|
||||
case keysPBM.KeyType.Secp256k1:
|
||||
if (supportedKeys.secp256k1) {
|
||||
return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data)
|
||||
} else {
|
||||
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
|
||||
}
|
||||
default:
|
||||
typeToKey(decoded.Type) // throws because type is not supported
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a private key object into a protobuf serialized private key
|
||||
exports.marshalPrivateKey = (key, type) => {
|
||||
type = (type || 'rsa').toLowerCase()
|
||||
typeToKey(type) // check type
|
||||
return key.bytes
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} encryptedKey
|
||||
* @param {string} password
|
||||
*/
|
||||
exports.import = async (encryptedKey, password) => { // eslint-disable-line require-await
|
||||
try {
|
||||
const key = await importer.import(encryptedKey, password)
|
||||
return exports.unmarshalPrivateKey(key)
|
||||
} catch (_) {
|
||||
// Ignore and try the old pem decrypt
|
||||
}
|
||||
|
||||
// Only rsa supports pem right now
|
||||
const key = forge.pki.decryptRsaPrivateKey(encryptedKey, password)
|
||||
if (key === null) {
|
||||
throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM')
|
||||
}
|
||||
let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
|
||||
der = uint8ArrayFromString(der.getBytes(), 'ascii')
|
||||
return supportedKeys.rsa.unmarshalRsaPrivateKey(der)
|
||||
}
|
||||
|
22
src/keys/jwk2pem.js
Normal file
22
src/keys/jwk2pem.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict'
|
||||
|
||||
require('node-forge/lib/rsa')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const { base64urlToBigInteger } = require('../util')
|
||||
|
||||
function convert (key, types) {
|
||||
return types.map(t => base64urlToBigInteger(key[t]))
|
||||
}
|
||||
|
||||
function jwk2priv (key) {
|
||||
return forge.pki.setRsaPrivateKey(...convert(key, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']))
|
||||
}
|
||||
|
||||
function jwk2pub (key) {
|
||||
return forge.pki.setRsaPublicKey(...convert(key, ['n', 'e']))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
jwk2pub,
|
||||
jwk2priv
|
||||
}
|
77
src/keys/key-stretcher.js
Normal file
77
src/keys/key-stretcher.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
const uint8ArrayConcat = require('uint8arrays/concat')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
const hmac = require('../hmac')
|
||||
|
||||
const cipherMap = {
|
||||
'AES-128': {
|
||||
ivSize: 16,
|
||||
keySize: 16
|
||||
},
|
||||
'AES-256': {
|
||||
ivSize: 16,
|
||||
keySize: 32
|
||||
},
|
||||
Blowfish: {
|
||||
ivSize: 8,
|
||||
cipherKeySize: 32
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a set of keys for each party by stretching the shared key.
|
||||
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
|
||||
module.exports = async (cipherType, hash, secret) => {
|
||||
const cipher = cipherMap[cipherType]
|
||||
|
||||
if (!cipher) {
|
||||
const allowed = Object.keys(cipherMap).join(' / ')
|
||||
throw errcode(new Error(`unknown cipher type '${cipherType}'. Must be ${allowed}`), 'ERR_INVALID_CIPHER_TYPE')
|
||||
}
|
||||
|
||||
if (!hash) {
|
||||
throw errcode(new Error('missing hash type'), 'ERR_MISSING_HASH_TYPE')
|
||||
}
|
||||
|
||||
const cipherKeySize = cipher.keySize
|
||||
const ivSize = cipher.ivSize
|
||||
const hmacKeySize = 20
|
||||
const seed = uint8ArrayFromString('key expansion')
|
||||
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
|
||||
|
||||
const m = await hmac.create(hash, secret)
|
||||
let a = await m.digest(seed)
|
||||
|
||||
const result = []
|
||||
let j = 0
|
||||
|
||||
while (j < resultLength) {
|
||||
const b = await m.digest(uint8ArrayConcat([a, seed]))
|
||||
let todo = b.length
|
||||
|
||||
if (j + todo > resultLength) {
|
||||
todo = resultLength - j
|
||||
}
|
||||
|
||||
result.push(b)
|
||||
j += todo
|
||||
a = await m.digest(a)
|
||||
}
|
||||
|
||||
const half = resultLength / 2
|
||||
const resultBuffer = uint8ArrayConcat(result)
|
||||
const r1 = resultBuffer.slice(0, half)
|
||||
const r2 = resultBuffer.slice(half, resultLength)
|
||||
|
||||
const createKey = (res) => ({
|
||||
iv: res.slice(0, ivSize),
|
||||
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
|
||||
macKey: res.slice(ivSize + cipherKeySize)
|
||||
})
|
||||
|
||||
return {
|
||||
k1: createKey(r1),
|
||||
k2: createKey(r2)
|
||||
}
|
||||
}
|
@@ -2,13 +2,13 @@
|
||||
|
||||
module.exports = `enum KeyType {
|
||||
RSA = 0;
|
||||
Ed25519 = 1;
|
||||
Secp256k1 = 2;
|
||||
}
|
||||
|
||||
message PublicKey {
|
||||
required KeyType Type = 1;
|
||||
required bytes Data = 2;
|
||||
}
|
||||
|
||||
message PrivateKey {
|
||||
required KeyType Type = 1;
|
||||
required bytes Data = 2;
|
153
src/keys/rsa-browser.js
Normal file
153
src/keys/rsa-browser.js
Normal file
@@ -0,0 +1,153 @@
|
||||
'use strict'
|
||||
|
||||
const webcrypto = require('../webcrypto')
|
||||
const randomBytes = require('../random-bytes')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
exports.utils = require('./rsa-utils')
|
||||
|
||||
exports.generateKey = async function (bits) {
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: bits,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
)
|
||||
|
||||
const keys = await exportKey(pair)
|
||||
|
||||
return {
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = async function (key) {
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
true,
|
||||
['sign']
|
||||
)
|
||||
|
||||
const pair = [
|
||||
privateKey,
|
||||
await derivePublicFromPrivate(key)
|
||||
]
|
||||
|
||||
const keys = await exportKey({
|
||||
privateKey: pair[0],
|
||||
publicKey: pair[1]
|
||||
})
|
||||
|
||||
return {
|
||||
privateKey: keys[0],
|
||||
publicKey: keys[1]
|
||||
}
|
||||
}
|
||||
|
||||
exports.getRandomValues = randomBytes
|
||||
|
||||
exports.hashAndSign = async function (key, msg) {
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
const sig = await webcrypto.get().subtle.sign(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
privateKey,
|
||||
Uint8Array.from(msg)
|
||||
)
|
||||
|
||||
return new Uint8Array(sig, sig.byteOffset, sig.byteLength)
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) {
|
||||
const publicKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
false,
|
||||
['verify']
|
||||
)
|
||||
|
||||
return webcrypto.get().subtle.verify(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
publicKey,
|
||||
sig,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
function exportKey (pair) {
|
||||
return Promise.all([
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
])
|
||||
}
|
||||
|
||||
function derivePublicFromPrivate (jwKey) {
|
||||
return webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
{
|
||||
kty: jwKey.kty,
|
||||
n: jwKey.n,
|
||||
e: jwKey.e
|
||||
},
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
RSA encryption/decryption for the browser with webcrypto workarround
|
||||
"bloody dark magic. webcrypto's why."
|
||||
|
||||
Explanation:
|
||||
- Convert JWK to nodeForge
|
||||
- Convert msg Uint8Array to nodeForge buffer: ByteBuffer is a "binary-string backed buffer", so let's make our Uint8Array a binary string
|
||||
- Convert resulting nodeForge buffer to Uint8Array: it returns a binary string, turn that into a Uint8Array
|
||||
|
||||
*/
|
||||
|
||||
const { jwk2pub, jwk2priv } = require('./jwk2pem')
|
||||
|
||||
function convertKey (key, pub, msg, handle) {
|
||||
const fkey = pub ? jwk2pub(key) : jwk2priv(key)
|
||||
const fmsg = uint8ArrayToString(Uint8Array.from(msg), 'ascii')
|
||||
const fomsg = handle(fmsg, fkey)
|
||||
return uint8ArrayFromString(fomsg, 'ascii')
|
||||
}
|
||||
|
||||
exports.encrypt = function (key, msg) {
|
||||
return convertKey(key, true, msg, (msg, key) => key.encrypt(msg))
|
||||
}
|
||||
|
||||
exports.decrypt = function (key, msg) {
|
||||
return convertKey(key, false, msg, (msg, key) => key.decrypt(msg))
|
||||
}
|
166
src/keys/rsa-class.js
Normal file
166
src/keys/rsa-class.js
Normal file
@@ -0,0 +1,166 @@
|
||||
'use strict'
|
||||
|
||||
const sha = require('multihashing-async/src/sha')
|
||||
const protobuf = require('protons')
|
||||
const errcode = require('err-code')
|
||||
const uint8ArrayEquals = require('uint8arrays/equals')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
|
||||
require('node-forge/lib/sha512')
|
||||
require('node-forge/lib/ed25519')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
|
||||
const crypto = require('./rsa')
|
||||
const pbm = protobuf(require('./keys.proto'))
|
||||
const exporter = require('./exporter')
|
||||
|
||||
class RsaPublicKey {
|
||||
constructor (key) {
|
||||
this._key = key
|
||||
}
|
||||
|
||||
async verify (data, sig) { // eslint-disable-line require-await
|
||||
return crypto.hashAndVerify(this._key, sig, data)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return crypto.utils.jwkToPkix(this._key)
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PublicKey.encode({
|
||||
Type: pbm.KeyType.RSA,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
encrypt (bytes) {
|
||||
return crypto.encrypt(this._key, bytes)
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
}
|
||||
|
||||
class RsaPrivateKey {
|
||||
// key - Object of the jwk format
|
||||
// publicKey - Uint8Array of the spki format
|
||||
constructor (key, publicKey) {
|
||||
this._key = key
|
||||
this._publicKey = publicKey
|
||||
}
|
||||
|
||||
genSecret () {
|
||||
return crypto.getRandomValues(16)
|
||||
}
|
||||
|
||||
async sign (message) { // eslint-disable-line require-await
|
||||
return crypto.hashAndSign(this._key, message)
|
||||
}
|
||||
|
||||
get public () {
|
||||
if (!this._publicKey) {
|
||||
throw errcode(new Error('public key not provided'), 'ERR_PUBKEY_NOT_PROVIDED')
|
||||
}
|
||||
|
||||
return new RsaPublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
decrypt (bytes) {
|
||||
return crypto.decrypt(this._key, bytes)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return crypto.utils.jwkToPkcs1(this._key)
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PrivateKey.encode({
|
||||
Type: pbm.KeyType.RSA,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
async hash () { // eslint-disable-line require-await
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the key.
|
||||
*
|
||||
* The key id is the base58 encoding of the SHA-256 multihash of its public key.
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*
|
||||
* @returns {Promise<String>}
|
||||
*/
|
||||
async id () {
|
||||
const hash = await this.public.hash()
|
||||
return uint8ArrayToString(hash, 'base58btc')
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key into a password protected PEM format
|
||||
*
|
||||
* @param {string} password - The password to read the encrypted PEM
|
||||
* @param {string} [format=pkcs-8] - The format in which to export as
|
||||
*/
|
||||
async export (password, format = 'pkcs-8') { // eslint-disable-line require-await
|
||||
if (format === 'pkcs-8') {
|
||||
const buffer = new forge.util.ByteBuffer(this.marshal())
|
||||
const asn1 = forge.asn1.fromDer(buffer)
|
||||
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
||||
|
||||
const options = {
|
||||
algorithm: 'aes256',
|
||||
count: 10000,
|
||||
saltSize: 128 / 8,
|
||||
prfAlgorithm: 'sha512'
|
||||
}
|
||||
return forge.pki.encryptRsaPrivateKey(privateKey, password, options)
|
||||
} else if (format === 'libp2p-key') {
|
||||
return exporter.export(this.bytes, password)
|
||||
} else {
|
||||
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function unmarshalRsaPrivateKey (bytes) {
|
||||
const jwk = crypto.utils.pkcs1ToJwk(bytes)
|
||||
const keys = await crypto.unmarshalPrivateKey(jwk)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
}
|
||||
|
||||
function unmarshalRsaPublicKey (bytes) {
|
||||
const jwk = crypto.utils.pkixToJwk(bytes)
|
||||
return new RsaPublicKey(jwk)
|
||||
}
|
||||
|
||||
async function fromJwk (jwk) {
|
||||
const keys = await crypto.unmarshalPrivateKey(jwk)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
}
|
||||
|
||||
async function generateKeyPair (bits) {
|
||||
const keys = await crypto.generateKey(bits)
|
||||
return new RsaPrivateKey(keys.privateKey, keys.publicKey)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RsaPublicKey,
|
||||
RsaPrivateKey,
|
||||
unmarshalRsaPublicKey,
|
||||
unmarshalRsaPrivateKey,
|
||||
generateKeyPair,
|
||||
fromJwk
|
||||
}
|
69
src/keys/rsa-utils.js
Normal file
69
src/keys/rsa-utils.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
require('node-forge/lib/asn1')
|
||||
require('node-forge/lib/rsa')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const { bigIntegerToUintBase64url, base64urlToBigInteger } = require('./../util')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
|
||||
// Convert a PKCS#1 in ASN1 DER format to a JWK key
|
||||
exports.pkcs1ToJwk = function (bytes) {
|
||||
const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
|
||||
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
||||
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||
return {
|
||||
kty: 'RSA',
|
||||
n: bigIntegerToUintBase64url(privateKey.n),
|
||||
e: bigIntegerToUintBase64url(privateKey.e),
|
||||
d: bigIntegerToUintBase64url(privateKey.d),
|
||||
p: bigIntegerToUintBase64url(privateKey.p),
|
||||
q: bigIntegerToUintBase64url(privateKey.q),
|
||||
dp: bigIntegerToUintBase64url(privateKey.dP),
|
||||
dq: bigIntegerToUintBase64url(privateKey.dQ),
|
||||
qi: bigIntegerToUintBase64url(privateKey.qInv),
|
||||
alg: 'RS256',
|
||||
kid: '2011-04-29'
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a JWK key into PKCS#1 in ASN1 DER format
|
||||
exports.jwkToPkcs1 = function (jwk) {
|
||||
const asn1 = forge.pki.privateKeyToAsn1({
|
||||
n: base64urlToBigInteger(jwk.n),
|
||||
e: base64urlToBigInteger(jwk.e),
|
||||
d: base64urlToBigInteger(jwk.d),
|
||||
p: base64urlToBigInteger(jwk.p),
|
||||
q: base64urlToBigInteger(jwk.q),
|
||||
dP: base64urlToBigInteger(jwk.dp),
|
||||
dQ: base64urlToBigInteger(jwk.dq),
|
||||
qInv: base64urlToBigInteger(jwk.qi)
|
||||
})
|
||||
|
||||
return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
|
||||
}
|
||||
|
||||
// Convert a PKCIX in ASN1 DER format to a JWK key
|
||||
exports.pkixToJwk = function (bytes) {
|
||||
const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
|
||||
const publicKey = forge.pki.publicKeyFromAsn1(asn1)
|
||||
|
||||
return {
|
||||
kty: 'RSA',
|
||||
n: bigIntegerToUintBase64url(publicKey.n),
|
||||
e: bigIntegerToUintBase64url(publicKey.e),
|
||||
alg: 'RS256',
|
||||
kid: '2011-04-29'
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a JWK key to PKCIX in ASN1 DER format
|
||||
exports.jwkToPkix = function (jwk) {
|
||||
const asn1 = forge.pki.publicKeyToAsn1({
|
||||
n: base64urlToBigInteger(jwk.n),
|
||||
e: base64urlToBigInteger(jwk.e)
|
||||
})
|
||||
|
||||
return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
|
||||
}
|
182
src/keys/rsa.js
182
src/keys/rsa.js
@@ -1,133 +1,83 @@
|
||||
'use strict'
|
||||
|
||||
const multihashing = require('multihashing-async')
|
||||
const protobuf = require('protocol-buffers')
|
||||
|
||||
const crypto = require('../crypto').rsa
|
||||
const pbm = protobuf(require('../crypto.proto'))
|
||||
|
||||
class RsaPublicKey {
|
||||
constructor (key) {
|
||||
this._key = key
|
||||
const crypto = require('crypto')
|
||||
const errcode = require('err-code')
|
||||
const randomBytes = require('../random-bytes')
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let keypair
|
||||
try {
|
||||
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'keypair') {
|
||||
throw new Error('Force keypair usage')
|
||||
}
|
||||
|
||||
verify (data, sig, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndVerify(this._key, sig, data, callback)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return crypto.jwkToPkix(this._key)
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return pbm.PublicKey.encode({
|
||||
Type: pbm.KeyType.RSA,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
encrypt (bytes) {
|
||||
return this._key.encrypt(bytes, 'RSAES-PKCS1-V1_5')
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
}
|
||||
}
|
||||
|
||||
class RsaPrivateKey {
|
||||
// key - Object of the jwk format
|
||||
// publicKey - Buffer of the spki format
|
||||
constructor (key, publicKey) {
|
||||
this._key = key
|
||||
this._publicKey = publicKey
|
||||
}
|
||||
|
||||
genSecret () {
|
||||
return crypto.getRandomValues(new Uint8Array(16))
|
||||
}
|
||||
|
||||
sign (message, callback) {
|
||||
ensure(callback)
|
||||
crypto.hashAndSign(this._key, message, callback)
|
||||
}
|
||||
|
||||
get public () {
|
||||
if (!this._publicKey) {
|
||||
throw new Error('public key not provided')
|
||||
const ursa = require('ursa-optional') // throws if not compiled
|
||||
keypair = ({ bits }) => {
|
||||
const key = ursa.generatePrivateKey(bits)
|
||||
return {
|
||||
private: key.toPrivatePem(),
|
||||
public: key.toPublicPem()
|
||||
}
|
||||
|
||||
return new RsaPublicKey(this._publicKey)
|
||||
}
|
||||
} catch (e) {
|
||||
if (process.env.LP2P_FORCE_CRYPTO_LIB === 'ursa') {
|
||||
throw e
|
||||
}
|
||||
|
||||
decrypt (msg, callback) {
|
||||
crypto.decrypt(this._key, msg, callback)
|
||||
}
|
||||
keypair = require('keypair')
|
||||
}
|
||||
const pemToJwk = require('pem-jwk').pem2jwk
|
||||
const jwkToPem = require('pem-jwk').jwk2pem
|
||||
|
||||
marshal () {
|
||||
return crypto.jwkToPkcs1(this._key)
|
||||
}
|
||||
exports.utils = require('./rsa-utils')
|
||||
|
||||
get bytes () {
|
||||
return pbm.PrivateKey.encode({
|
||||
Type: pbm.KeyType.RSA,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return this.bytes.equals(key.bytes)
|
||||
}
|
||||
|
||||
hash (callback) {
|
||||
ensure(callback)
|
||||
multihashing(this.bytes, 'sha2-256', callback)
|
||||
exports.generateKey = async function (bits) { // eslint-disable-line require-await
|
||||
const key = keypair({ bits })
|
||||
return {
|
||||
privateKey: pemToJwk(key.private),
|
||||
publicKey: pemToJwk(key.public)
|
||||
}
|
||||
}
|
||||
|
||||
function unmarshalRsaPrivateKey (bytes, callback) {
|
||||
const jwk = crypto.pkcs1ToJwk(bytes)
|
||||
crypto.unmarshalPrivateKey(jwk, (err, keys) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = async function (key) { // eslint-disable-line require-await
|
||||
if (!key) {
|
||||
throw errcode(new Error('Missing key parameter'), 'ERR_MISSING_KEY')
|
||||
}
|
||||
return {
|
||||
privateKey: key,
|
||||
publicKey: {
|
||||
kty: key.kty,
|
||||
n: key.n,
|
||||
e: key.e
|
||||
}
|
||||
|
||||
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||
})
|
||||
}
|
||||
|
||||
function unmarshalRsaPublicKey (bytes) {
|
||||
const jwk = crypto.pkixToJwk(bytes)
|
||||
|
||||
return new RsaPublicKey(jwk)
|
||||
}
|
||||
|
||||
function generateKeyPair (bits, cb) {
|
||||
crypto.generateKey(bits, (err, keys) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
cb(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
|
||||
})
|
||||
}
|
||||
|
||||
function ensure (cb) {
|
||||
if (typeof cb !== 'function') {
|
||||
throw new Error('callback is required')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RsaPublicKey,
|
||||
RsaPrivateKey,
|
||||
unmarshalRsaPublicKey,
|
||||
unmarshalRsaPrivateKey,
|
||||
generateKeyPair
|
||||
exports.getRandomValues = randomBytes
|
||||
|
||||
exports.hashAndSign = async function (key, msg) { // eslint-disable-line require-await
|
||||
const sign = crypto.createSign('RSA-SHA256')
|
||||
sign.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
return sign.sign(pem)
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) { // eslint-disable-line require-await
|
||||
const verify = crypto.createVerify('RSA-SHA256')
|
||||
verify.update(msg)
|
||||
const pem = jwkToPem(key)
|
||||
return verify.verify(pem, sig)
|
||||
}
|
||||
|
||||
const padding = crypto.constants.RSA_PKCS1_PADDING
|
||||
|
||||
exports.encrypt = function (key, bytes) {
|
||||
return crypto.publicEncrypt({ key: jwkToPem(key), padding }, bytes)
|
||||
}
|
||||
|
||||
exports.decrypt = function (key, bytes) {
|
||||
return crypto.privateDecrypt({ key: jwkToPem(key), padding }, bytes)
|
||||
}
|
||||
|
128
src/keys/secp256k1-class.js
Normal file
128
src/keys/secp256k1-class.js
Normal file
@@ -0,0 +1,128 @@
|
||||
'use strict'
|
||||
|
||||
const sha = require('multihashing-async/src/sha')
|
||||
const errcode = require('err-code')
|
||||
const uint8ArrayEquals = require('uint8arrays/equals')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
|
||||
const exporter = require('./exporter')
|
||||
|
||||
module.exports = (keysProtobuf, randomBytes, crypto) => {
|
||||
crypto = crypto || require('./secp256k1')(randomBytes)
|
||||
|
||||
class Secp256k1PublicKey {
|
||||
constructor (key) {
|
||||
crypto.validatePublicKey(key)
|
||||
this._key = key
|
||||
}
|
||||
|
||||
verify (data, sig) {
|
||||
return crypto.hashAndVerify(this._key, sig, data)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return crypto.compressPublicKey(this._key)
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return keysProtobuf.PublicKey.encode({
|
||||
Type: keysProtobuf.KeyType.Secp256k1,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
hash () {
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
}
|
||||
|
||||
class Secp256k1PrivateKey {
|
||||
constructor (key, publicKey) {
|
||||
this._key = key
|
||||
this._publicKey = publicKey || crypto.computePublicKey(key)
|
||||
crypto.validatePrivateKey(this._key)
|
||||
crypto.validatePublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
sign (message) {
|
||||
return crypto.hashAndSign(this._key, message)
|
||||
}
|
||||
|
||||
get public () {
|
||||
return new Secp256k1PublicKey(this._publicKey)
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return this._key
|
||||
}
|
||||
|
||||
get bytes () {
|
||||
return keysProtobuf.PrivateKey.encode({
|
||||
Type: keysProtobuf.KeyType.Secp256k1,
|
||||
Data: this.marshal()
|
||||
})
|
||||
}
|
||||
|
||||
equals (key) {
|
||||
return uint8ArrayEquals(this.bytes, key.bytes)
|
||||
}
|
||||
|
||||
hash () {
|
||||
return sha.multihashing(this.bytes, 'sha2-256')
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the key.
|
||||
*
|
||||
* The key id is the base58 encoding of the SHA-256 multihash of its public key.
|
||||
* The public key is a protobuf encoding containing a type and the DER encoding
|
||||
* of the PKCS SubjectPublicKeyInfo.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async id () {
|
||||
const hash = await this.public.hash()
|
||||
return uint8ArrayToString(hash, 'base58btc')
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the key into a password protected `format`
|
||||
*
|
||||
* @param {string} password - The password to encrypt the key
|
||||
* @param {string} [format=libp2p-key] - The format in which to export as
|
||||
* @returns {Promise<string>} The encrypted private key
|
||||
*/
|
||||
async export (password, format = 'libp2p-key') { // eslint-disable-line require-await
|
||||
if (format === 'libp2p-key') {
|
||||
return exporter.export(this.bytes, password)
|
||||
} else {
|
||||
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unmarshalSecp256k1PrivateKey (bytes) {
|
||||
return new Secp256k1PrivateKey(bytes)
|
||||
}
|
||||
|
||||
function unmarshalSecp256k1PublicKey (bytes) {
|
||||
return new Secp256k1PublicKey(bytes)
|
||||
}
|
||||
|
||||
async function generateKeyPair () {
|
||||
const privateKeyBytes = await crypto.generateKey()
|
||||
return new Secp256k1PrivateKey(privateKeyBytes)
|
||||
}
|
||||
|
||||
return {
|
||||
Secp256k1PublicKey,
|
||||
Secp256k1PrivateKey,
|
||||
unmarshalSecp256k1PrivateKey,
|
||||
unmarshalSecp256k1PublicKey,
|
||||
generateKeyPair
|
||||
}
|
||||
}
|
69
src/keys/secp256k1.js
Normal file
69
src/keys/secp256k1.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
const secp256k1 = require('secp256k1')
|
||||
const sha = require('multihashing-async/src/sha')
|
||||
const HASH_ALGORITHM = 'sha2-256'
|
||||
|
||||
module.exports = (randomBytes) => {
|
||||
const privateKeyLength = 32
|
||||
|
||||
function generateKey () {
|
||||
let privateKey
|
||||
do {
|
||||
privateKey = randomBytes(32)
|
||||
} while (!secp256k1.privateKeyVerify(privateKey))
|
||||
return privateKey
|
||||
}
|
||||
|
||||
async function hashAndSign (key, msg) {
|
||||
const digest = await sha.digest(msg, HASH_ALGORITHM)
|
||||
const sig = secp256k1.ecdsaSign(digest, key)
|
||||
return secp256k1.signatureExport(sig.signature)
|
||||
}
|
||||
|
||||
async function hashAndVerify (key, sig, msg) {
|
||||
const digest = await sha.digest(msg, HASH_ALGORITHM)
|
||||
sig = secp256k1.signatureImport(sig)
|
||||
return secp256k1.ecdsaVerify(sig, digest, key)
|
||||
}
|
||||
|
||||
function compressPublicKey (key) {
|
||||
if (!secp256k1.publicKeyVerify(key)) {
|
||||
throw new Error('Invalid public key')
|
||||
}
|
||||
return secp256k1.publicKeyConvert(key, true)
|
||||
}
|
||||
|
||||
function decompressPublicKey (key) {
|
||||
return secp256k1.publicKeyConvert(key, false)
|
||||
}
|
||||
|
||||
function validatePrivateKey (key) {
|
||||
if (!secp256k1.privateKeyVerify(key)) {
|
||||
throw new Error('Invalid private key')
|
||||
}
|
||||
}
|
||||
|
||||
function validatePublicKey (key) {
|
||||
if (!secp256k1.publicKeyVerify(key)) {
|
||||
throw new Error('Invalid public key')
|
||||
}
|
||||
}
|
||||
|
||||
function computePublicKey (privateKey) {
|
||||
validatePrivateKey(privateKey)
|
||||
return secp256k1.publicKeyCreate(privateKey)
|
||||
}
|
||||
|
||||
return {
|
||||
generateKey,
|
||||
privateKeyLength,
|
||||
hashAndSign,
|
||||
hashAndVerify,
|
||||
compressPublicKey,
|
||||
decompressPublicKey,
|
||||
validatePrivateKey,
|
||||
validatePublicKey,
|
||||
computePublicKey
|
||||
}
|
||||
}
|
10
src/keys/validate-curve-type.js
Normal file
10
src/keys/validate-curve-type.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
|
||||
module.exports = function (curveTypes, type) {
|
||||
if (!curveTypes.includes(type)) {
|
||||
const names = curveTypes.join(' / ')
|
||||
throw errcode(new Error(`Unknown curve: ${type}. Must be ${names}`), 'ERR_INVALID_CURVE')
|
||||
}
|
||||
}
|
45
src/pbkdf2.js
Normal file
45
src/pbkdf2.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict'
|
||||
|
||||
const forgePbkdf2 = require('node-forge/lib/pbkdf2')
|
||||
const forgeUtil = require('node-forge/lib/util')
|
||||
const errcode = require('err-code')
|
||||
|
||||
/**
|
||||
* Maps an IPFS hash name to its node-forge equivalent.
|
||||
*
|
||||
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const hashName = {
|
||||
sha1: 'sha1',
|
||||
'sha2-256': 'sha256',
|
||||
'sha2-512': 'sha512'
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the Password-Based Key Derivation Function 2.
|
||||
*
|
||||
* @param {string} password
|
||||
* @param {string} salt
|
||||
* @param {number} iterations
|
||||
* @param {number} keySize (in bytes)
|
||||
* @param {string} hash - The hash name ('sha1', 'sha2-512, ...)
|
||||
* @returns {string} - A new password
|
||||
*/
|
||||
function pbkdf2 (password, salt, iterations, keySize, hash) {
|
||||
const hasher = hashName[hash]
|
||||
if (!hasher) {
|
||||
const types = Object.keys(hashName).join(' / ')
|
||||
throw errcode(new Error(`Hash '${hash}' is unknown or not supported. Must be ${types}`), 'ERR_UNSUPPORTED_HASH_TYPE')
|
||||
}
|
||||
const dek = forgePbkdf2(
|
||||
password,
|
||||
salt,
|
||||
iterations,
|
||||
keySize,
|
||||
hasher)
|
||||
return forgeUtil.encode64(dek)
|
||||
}
|
||||
|
||||
module.exports = pbkdf2
|
10
src/random-bytes.js
Normal file
10
src/random-bytes.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
const randomBytes = require('iso-random-stream/src/random')
|
||||
const errcode = require('err-code')
|
||||
|
||||
module.exports = function (length) {
|
||||
if (isNaN(length) || length <= 0) {
|
||||
throw errcode(new Error('random bytes length must be a Number bigger than 0'), 'ERR_INVALID_LENGTH')
|
||||
}
|
||||
return randomBytes(length)
|
||||
}
|
43
src/util.js
Normal file
43
src/util.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
require('node-forge/lib/util')
|
||||
require('node-forge/lib/jsbn')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
const uint8ArrayConcat = require('uint8arrays/concat')
|
||||
|
||||
exports.bigIntegerToUintBase64url = (num, len) => {
|
||||
// Call `.abs()` to convert to unsigned
|
||||
let buf = Uint8Array.from(num.abs().toByteArray()) // toByteArray converts to big endian
|
||||
|
||||
// toByteArray() gives us back a signed array, which will include a leading 0
|
||||
// byte if the most significant bit of the number is 1:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
|
||||
// Our number will always be positive so we should remove the leading padding.
|
||||
buf = buf[0] === 0 ? buf.slice(1) : buf
|
||||
|
||||
if (len != null) {
|
||||
if (buf.length > len) throw new Error('byte array longer than desired length')
|
||||
buf = uint8ArrayConcat([new Uint8Array(len - buf.length), buf])
|
||||
}
|
||||
|
||||
return uint8ArrayToString(buf, 'base64url')
|
||||
}
|
||||
|
||||
// Convert a base64url encoded string to a BigInteger
|
||||
exports.base64urlToBigInteger = str => {
|
||||
const buf = exports.base64urlToBuffer(str)
|
||||
return new forge.jsbn.BigInteger(uint8ArrayToString(buf, 'base16'), 16)
|
||||
}
|
||||
|
||||
exports.base64urlToBuffer = (str, len) => {
|
||||
let buf = uint8ArrayFromString(str, 'base64urlpad')
|
||||
|
||||
if (len != null) {
|
||||
if (buf.length > len) throw new Error('byte array longer than desired length')
|
||||
buf = uint8ArrayConcat([new Uint8Array(len - buf.length), buf])
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
24
src/webcrypto.js
Normal file
24
src/webcrypto.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
'use strict'
|
||||
|
||||
// Check native crypto exists and is enabled (In insecure context `self.crypto`
|
||||
// exists but `self.crypto.subtle` does not).
|
||||
exports.get = (win = self) => {
|
||||
const nativeCrypto = win.crypto || win.msCrypto
|
||||
|
||||
if (!nativeCrypto || !nativeCrypto.subtle) {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
'Missing Web Crypto API. ' +
|
||||
'The most likely cause of this error is that this page is being accessed ' +
|
||||
'from an insecure context (i.e. not HTTPS). For more information and ' +
|
||||
'possible resolutions see ' +
|
||||
'https://github.com/libp2p/js-libp2p-crypto/blob/master/README.md#web-crypto-api'
|
||||
),
|
||||
{ code: 'ERR_MISSING_WEB_CRYPTO' }
|
||||
)
|
||||
}
|
||||
|
||||
return nativeCrypto
|
||||
}
|
115
test/aes.spec.js
115
test/aes.spec.js
@@ -1,115 +0,0 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
const series = require('async/series')
|
||||
|
||||
const crypto = require('../src')
|
||||
const fixtures = require('./fixtures/aes')
|
||||
const goFixtures = require('./fixtures/go-aes')
|
||||
|
||||
const bytes = {
|
||||
16: 'AES-128',
|
||||
32: 'AES-256'
|
||||
}
|
||||
|
||||
describe('AES-CTR', () => {
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - encrypt and decrypt`, (done) => {
|
||||
const key = new Buffer(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Buffer(16)
|
||||
iv.fill(1)
|
||||
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist
|
||||
|
||||
series([
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher),
|
||||
encryptAndDecrypt(cipher)
|
||||
], done)
|
||||
})
|
||||
})
|
||||
})
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - fixed - encrypt and decrypt`, (done) => {
|
||||
const key = new Buffer(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Buffer(16)
|
||||
iv.fill(1)
|
||||
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist
|
||||
|
||||
series(fixtures[byte].inputs.map((rawIn, i) => (cb) => {
|
||||
const input = new Buffer(rawIn)
|
||||
const output = new Buffer(fixtures[byte].outputs[i])
|
||||
cipher.encrypt(input, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
expect(res).to.have.length(output.length)
|
||||
expect(res).to.be.eql(output)
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
expect(res).to.be.eql(input)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}), done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
if (!goFixtures[byte]) {
|
||||
return
|
||||
}
|
||||
|
||||
it(`${bytes[byte]} - go interop - encrypt and decrypt`, (done) => {
|
||||
const key = new Buffer(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Buffer(16)
|
||||
iv.fill(1)
|
||||
|
||||
crypto.aes.create(key, iv, (err, cipher) => {
|
||||
expect(err).to.not.exist
|
||||
|
||||
series(goFixtures[byte].inputs.map((rawIn, i) => (cb) => {
|
||||
const input = new Buffer(rawIn)
|
||||
const output = new Buffer(goFixtures[byte].outputs[i])
|
||||
cipher.encrypt(input, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
expect(res).to.have.length(output.length)
|
||||
expect(res).to.be.eql(output)
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
expect(res).to.be.eql(input)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}), done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function encryptAndDecrypt (cipher) {
|
||||
const data = new Buffer(100)
|
||||
data.fill(Math.ceil(Math.random() * 100))
|
||||
return (cb) => {
|
||||
cipher.encrypt(data, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
cipher.decrypt(res, (err, res) => {
|
||||
expect(err).to.not.exist
|
||||
expect(res).to.be.eql(data)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
111
test/aes/aes.spec.js
Normal file
111
test/aes/aes.spec.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const { expectErrCode } = require('../util')
|
||||
|
||||
const crypto = require('../../src')
|
||||
const fixtures = require('./../fixtures/aes')
|
||||
const goFixtures = require('./../fixtures/go-aes')
|
||||
|
||||
const bytes = {
|
||||
16: 'AES-128',
|
||||
32: 'AES-256'
|
||||
}
|
||||
|
||||
/** @typedef {import("libp2p-crypto").aes.Cipher} Cipher */
|
||||
|
||||
describe('AES-CTR', () => {
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - encrypt and decrypt`, async () => {
|
||||
const key = new Uint8Array(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Uint8Array(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
await encryptAndDecrypt(cipher)
|
||||
})
|
||||
})
|
||||
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
it(`${bytes[byte]} - fixed - encrypt and decrypt`, async () => {
|
||||
const key = new Uint8Array(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Uint8Array(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
|
||||
for (let i = 0; i < fixtures[byte].inputs.length; i++) {
|
||||
const rawIn = fixtures[byte].inputs[i]
|
||||
const input = Uint8Array.from(rawIn.data)
|
||||
const output = Uint8Array.from(fixtures[byte].outputs[i].data)
|
||||
const encrypted = await cipher.encrypt(input)
|
||||
expect(encrypted).to.have.length(output.length)
|
||||
expect(encrypted).to.eql(output)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
expect(decrypted).to.eql(input)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Object.keys(bytes).forEach((byte) => {
|
||||
if (!goFixtures[byte]) {
|
||||
return
|
||||
}
|
||||
|
||||
it(`${bytes[byte]} - go interop - encrypt and decrypt`, async () => {
|
||||
const key = new Uint8Array(parseInt(byte, 10))
|
||||
key.fill(5)
|
||||
|
||||
const iv = new Uint8Array(16)
|
||||
iv.fill(1)
|
||||
|
||||
const cipher = await crypto.aes.create(key, iv)
|
||||
|
||||
for (let i = 0; i < goFixtures[byte].inputs.length; i++) {
|
||||
const rawIn = goFixtures[byte].inputs[i]
|
||||
const input = Uint8Array.from(rawIn)
|
||||
const output = Uint8Array.from(goFixtures[byte].outputs[i])
|
||||
const encrypted = await cipher.encrypt(input)
|
||||
expect(encrypted).to.have.length(output.length)
|
||||
expect(encrypted).to.eql(output)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
expect(decrypted).to.eql(input)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('checks key length', () => {
|
||||
const key = new Uint8Array(5)
|
||||
const iv = new Uint8Array(16)
|
||||
return expectErrCode(crypto.aes.create(key, iv), 'ERR_INVALID_KEY_LENGTH')
|
||||
})
|
||||
})
|
||||
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {function(Cipher): Promise<void>}
|
||||
*/
|
||||
async function encryptAndDecrypt (cipher) {
|
||||
const data = new Uint8Array(100)
|
||||
data.fill(Math.ceil(Math.random() * 100))
|
||||
|
||||
const encrypted = await cipher.encrypt(data)
|
||||
const decrypted = await cipher.decrypt(encrypted)
|
||||
|
||||
expect(decrypted).to.be.eql(data)
|
||||
}
|
62
test/browser.js
Normal file
62
test/browser.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
const expect = chai.expect
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
const crypto = require('../')
|
||||
const webcrypto = require('../src/webcrypto')
|
||||
|
||||
async function expectMissingWebCrypto (fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (err) {
|
||||
expect(err.code).to.equal('ERR_MISSING_WEB_CRYPTO')
|
||||
return
|
||||
}
|
||||
throw new Error('Expected missing web crypto error')
|
||||
}
|
||||
|
||||
describe('Missing web crypto', () => {
|
||||
let webcryptoGet
|
||||
let rsaPrivateKey
|
||||
|
||||
before(async () => {
|
||||
rsaPrivateKey = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
})
|
||||
|
||||
before(() => {
|
||||
webcryptoGet = webcrypto.get
|
||||
webcrypto.get = () => webcryptoGet({})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
webcrypto.get = webcryptoGet
|
||||
})
|
||||
|
||||
it('should error for hmac create when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.hmac.create('SHA256', uint8ArrayFromString('secret')))
|
||||
})
|
||||
|
||||
it('should error for generate ephemeral key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateEphemeralKeyPair('P-256'))
|
||||
})
|
||||
|
||||
it('should error for generate rsa key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateKeyPair('rsa', 256))
|
||||
})
|
||||
|
||||
it('should error for unmarshal RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(rsaPrivateKey)))
|
||||
})
|
||||
|
||||
it('should error for sign RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.sign(uint8ArrayFromString('test')))
|
||||
})
|
||||
|
||||
it('should error for verify RSA public key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.public.verify(uint8ArrayFromString('test'), uint8ArrayFromString('test')))
|
||||
})
|
||||
})
|
132
test/crypto.spec.js
Normal file
132
test/crypto.spec.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const crypto = require('../src')
|
||||
const fixtures = require('./fixtures/go-key-rsa')
|
||||
const { expectErrCode } = require('./util')
|
||||
const uint8ArrayEquals = require('uint8arrays/equals')
|
||||
|
||||
/** @typedef {import("libp2p-crypto").PrivateKey} PrivateKey */
|
||||
|
||||
describe('libp2p-crypto', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let key
|
||||
before(async () => {
|
||||
key = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
})
|
||||
|
||||
it('marshalPublicKey and unmarshalPublicKey', () => {
|
||||
const key2 = crypto.keys.unmarshalPublicKey(
|
||||
crypto.keys.marshalPublicKey(key.public))
|
||||
|
||||
expect(key2.equals(key.public)).to.be.eql(true)
|
||||
|
||||
expect(() => {
|
||||
crypto.keys.marshalPublicKey(key.public, 'invalid-key-type')
|
||||
}).to.throw()
|
||||
})
|
||||
|
||||
it('marshalPrivateKey and unmarshalPrivateKey', async () => {
|
||||
expect(() => {
|
||||
crypto.keys.marshalPrivateKey(key, 'invalid-key-type')
|
||||
}).to.throw()
|
||||
|
||||
const key2 = await crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(key))
|
||||
|
||||
expect(key2.equals(key)).to.be.eql(true)
|
||||
expect(key2.public.equals(key.public)).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('generateKeyPair', () => {
|
||||
return expectErrCode(crypto.keys.generateKeyPair('invalid-key-type', 512), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
})
|
||||
|
||||
it('generateKeyPairFromSeed', () => {
|
||||
var seed = crypto.randomBytes(32)
|
||||
return expectErrCode(crypto.keys.generateKeyPairFromSeed('invalid-key-type', seed, 512), 'ERR_UNSUPPORTED_KEY_TYPE')
|
||||
})
|
||||
|
||||
// marshalled keys seem to be slightly different
|
||||
// unsure as to if this is just a difference in encoding
|
||||
// or a bug
|
||||
describe('go interop', () => {
|
||||
it('unmarshals private key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
|
||||
const hash = fixtures.private.hash
|
||||
expect(fixtures.private.key).to.eql(key.bytes)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.eql(hash)
|
||||
})
|
||||
|
||||
it('unmarshals public key', async () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.public.key)
|
||||
const hash = fixtures.public.hash
|
||||
expect(crypto.keys.marshalPublicKey(key)).to.eql(fixtures.public.key)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.eql(hash)
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, private key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
|
||||
const marshalled = crypto.keys.marshalPrivateKey(key)
|
||||
expect(marshalled).to.eql(fixtures.private.key)
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, public key', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.public.key)
|
||||
const marshalled = crypto.keys.marshalPublicKey(key)
|
||||
expect(uint8ArrayEquals(fixtures.public.key, marshalled)).to.eql(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('pbkdf2', () => {
|
||||
it('generates a derived password using sha1', () => {
|
||||
const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'sha1')
|
||||
expect(p1).to.exist()
|
||||
expect(p1).to.be.a('string')
|
||||
})
|
||||
|
||||
it('generates a derived password using sha2-512', () => {
|
||||
const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'sha2-512')
|
||||
expect(p1).to.exist()
|
||||
expect(p1).to.be.a('string')
|
||||
})
|
||||
|
||||
it('generates the same derived password with the same options', () => {
|
||||
const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 10, 512 / 8, 'sha1')
|
||||
const p2 = crypto.pbkdf2('password', 'at least 16 character salt', 10, 512 / 8, 'sha1')
|
||||
const p3 = crypto.pbkdf2('password', 'at least 16 character salt', 11, 512 / 8, 'sha1')
|
||||
expect(p2).to.equal(p1)
|
||||
expect(p3).to.not.equal(p2)
|
||||
})
|
||||
|
||||
it('throws on invalid hash name', () => {
|
||||
const fn = () => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx')
|
||||
expect(fn).to.throw().with.property('code', 'ERR_UNSUPPORTED_HASH_TYPE')
|
||||
})
|
||||
})
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('throws with invalid number passed', () => {
|
||||
expect(() => {
|
||||
crypto.randomBytes(-1)
|
||||
}).to.throw()
|
||||
})
|
||||
|
||||
it('generates different random things', () => {
|
||||
const buf1 = crypto.randomBytes(10)
|
||||
expect(buf1.length).to.equal(10)
|
||||
const buf2 = crypto.randomBytes(10)
|
||||
expect(buf1).to.not.eql(buf2)
|
||||
})
|
||||
})
|
||||
})
|
@@ -1,53 +0,0 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
const parallel = require('async/parallel')
|
||||
|
||||
const fixtures = require('./fixtures/go-elliptic-key')
|
||||
const crypto = require('../src')
|
||||
|
||||
const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why
|
||||
const lengths = {
|
||||
'P-256': 65,
|
||||
'P-384': 97,
|
||||
'P-521': 133
|
||||
}
|
||||
|
||||
describe('generateEphemeralKeyPair', () => {
|
||||
curves.forEach((curve) => {
|
||||
it(`generate and shared key ${curve}`, (done) => {
|
||||
parallel([
|
||||
(cb) => crypto.generateEphemeralKeyPair(curve, cb),
|
||||
(cb) => crypto.generateEphemeralKeyPair(curve, cb)
|
||||
], (err, keys) => {
|
||||
expect(err).to.not.exist
|
||||
expect(keys[0].key).to.have.length(lengths[curve])
|
||||
expect(keys[1].key).to.have.length(lengths[curve])
|
||||
|
||||
keys[0].genSharedKey(keys[1].key, (err, shared) => {
|
||||
expect(err).to.not.exist
|
||||
expect(shared).to.have.length(32)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('generates a shared secret', (done) => {
|
||||
const curve = fixtures.curve
|
||||
|
||||
crypto.generateEphemeralKeyPair(curve, (err, alice) => {
|
||||
expect(err).to.not.exist
|
||||
|
||||
alice.genSharedKey(fixtures.bob.public, (err, s1) => {
|
||||
expect(err).to.not.exist
|
||||
expect(s1).to.have.length(32)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
6
test/fixtures/go-elliptic-key.js
vendored
6
test/fixtures/go-elliptic-key.js
vendored
@@ -3,10 +3,10 @@
|
||||
module.exports = {
|
||||
curve: 'P-256',
|
||||
bob: {
|
||||
private: [
|
||||
private: Uint8Array.from([
|
||||
181, 217, 162, 151, 225, 36, 53, 253, 107, 66, 27, 27, 232, 72, 0, 0, 103, 167, 84, 62, 203, 91, 97, 137, 131, 193, 230, 126, 98, 242, 216, 170
|
||||
],
|
||||
public: new Buffer([
|
||||
]),
|
||||
public: Uint8Array.from([
|
||||
4, 53, 59, 128, 56, 162, 250, 72, 141, 206, 117, 232, 57, 96, 39, 39, 247, 7, 27, 57, 251, 232, 120, 186, 21, 239, 176, 139, 195, 129, 125, 85, 11, 188, 191, 32, 227, 0, 6, 163, 101, 68, 208, 1, 43, 131, 124, 112, 102, 91, 104, 79, 16, 119, 152, 208, 4, 147, 155, 83, 20, 146, 104, 55, 90
|
||||
])
|
||||
}
|
||||
|
42
test/fixtures/go-key-ed25519.js
vendored
Normal file
42
test/fixtures/go-key-ed25519.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
// Generation code from https://github.com/libp2p/js-libp2p-crypto/issues/175#issuecomment-634467463
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "crypto/rand"
|
||||
// "fmt"
|
||||
// "strings"
|
||||
|
||||
// "github.com/libp2p/go-libp2p-core/crypto"
|
||||
// )
|
||||
|
||||
// func main() {
|
||||
// priv, pub, _ := crypto.GenerateEd25519Key(rand.Reader)
|
||||
// pubkeyBytes, _ := pub.Bytes()
|
||||
// privkeyBytes, _ := priv.Bytes()
|
||||
// data := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
// sig, _ := priv.Sign(data)
|
||||
// fmt.Println("{\n publicKey: Uint8Array.from(", strings.Replace(fmt.Sprint(pubkeyBytes), " ", ",", -1), "),")
|
||||
// fmt.Println(" privateKey: Uint8Array.from(", strings.Replace(fmt.Sprint(privkeyBytes), " ", ",", -1), "),")
|
||||
// fmt.Println(" data: Uint8Array.from(", strings.Replace(fmt.Sprint(data), " ", ",", -1), "),")
|
||||
// fmt.Println(" signature: Uint8Array.from(", strings.Replace(fmt.Sprint(sig), " ", ",", -1), ")\n}")
|
||||
// }
|
||||
//
|
||||
|
||||
// The legacy key unnecessarily appends the publickey. (It's already included) See https://github.com/libp2p/js-libp2p-crypto/issues/175
|
||||
redundantPubKey: {
|
||||
privateKey: Uint8Array.from([8, 1, 18, 96, 201, 208, 1, 110, 176, 16, 230, 37, 66, 184, 149, 252, 78, 56, 206, 136, 2, 38, 118, 152, 226, 197, 117, 200, 54, 189, 156, 218, 184, 7, 118, 57, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
|
||||
publicKey: Uint8Array.from([8, 1, 18, 32, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
|
||||
data: Uint8Array.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
|
||||
signature: Uint8Array.from([7, 230, 175, 164, 228, 58, 78, 208, 62, 243, 73, 142, 83, 195, 176, 217, 166, 62, 41, 165, 168, 164, 75, 179, 163, 86, 102, 32, 18, 84, 150, 237, 39, 207, 213, 20, 134, 237, 50, 41, 176, 183, 229, 133, 38, 255, 42, 228, 68, 186, 100, 14, 175, 156, 243, 118, 125, 125, 120, 212, 124, 103, 252, 12])
|
||||
},
|
||||
verify: {
|
||||
publicKey: Uint8Array.from([8, 1, 18, 32, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
|
||||
privateKey: Uint8Array.from([8, 1, 18, 64, 232, 56, 175, 20, 240, 160, 19, 47, 92, 88, 115, 221, 164, 13, 36, 162, 158, 136, 247, 31, 29, 231, 76, 143, 12, 91, 193, 4, 88, 33, 67, 23, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
|
||||
data: Uint8Array.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
|
||||
signature: Uint8Array.from([160, 125, 30, 62, 213, 189, 239, 92, 87, 76, 205, 169, 251, 149, 187, 57, 96, 85, 175, 213, 22, 132, 229, 60, 196, 18, 117, 194, 12, 174, 135, 31, 39, 168, 174, 103, 78, 55, 37, 222, 37, 172, 222, 239, 153, 63, 197, 152, 67, 167, 191, 215, 161, 212, 216, 163, 81, 77, 45, 228, 151, 79, 101, 1])
|
||||
}
|
||||
}
|
14
test/fixtures/go-key-rsa.js
vendored
14
test/fixtures/go-key-rsa.js
vendored
@@ -2,29 +2,29 @@
|
||||
|
||||
module.exports = {
|
||||
private: {
|
||||
hash: new Buffer([
|
||||
hash: Uint8Array.from([
|
||||
18, 32, 168, 125, 165, 65, 34, 157, 209, 4, 24, 158, 80, 196, 125, 86, 103, 0, 228, 145, 109, 252, 153, 7, 189, 9, 16, 37, 239, 36, 48, 78, 214, 212
|
||||
]),
|
||||
key: new Buffer([
|
||||
key: Uint8Array.from([
|
||||
8, 0, 18, 192, 2, 48, 130, 1, 60, 2, 1, 0, 2, 65, 0, 230, 157, 160, 242, 74, 222, 87, 0, 77, 180, 91, 175, 217, 166, 2, 95, 193, 239, 195, 140, 224, 57, 84, 207, 46, 172, 113, 196, 20, 133, 117, 205, 45, 7, 224, 41, 40, 195, 254, 124, 14, 84, 223, 147, 67, 198, 48, 36, 53, 161, 112, 46, 153, 90, 19, 123, 94, 247, 5, 116, 1, 238, 32, 15, 2, 3, 1, 0, 1, 2, 65, 0, 191, 59, 140, 255, 254, 23, 123, 91, 148, 19, 240, 71, 213, 26, 181, 51, 68, 181, 150, 153, 214, 65, 148, 83, 45, 103, 239, 250, 225, 237, 125, 173, 111, 244, 37, 124, 87, 178, 86, 10, 14, 207, 63, 105, 213, 37, 81, 23, 230, 4, 222, 179, 144, 40, 252, 163, 190, 7, 241, 221, 28, 54, 225, 209, 2, 33, 0, 235, 132, 229, 150, 99, 182, 176, 194, 198, 65, 210, 160, 184, 70, 82, 49, 235, 199, 14, 11, 92, 66, 237, 45, 220, 72, 235, 1, 244, 145, 205, 57, 2, 33, 0, 250, 171, 146, 180, 188, 194, 14, 152, 52, 64, 38, 52, 158, 86, 46, 109, 66, 100, 122, 43, 88, 167, 143, 98, 104, 143, 160, 60, 171, 185, 31, 135, 2, 33, 0, 206, 47, 255, 203, 100, 170, 137, 31, 75, 240, 78, 84, 212, 95, 4, 16, 158, 73, 27, 27, 136, 255, 50, 163, 166, 169, 211, 204, 87, 111, 217, 201, 2, 33, 0, 177, 51, 194, 213, 3, 175, 7, 84, 47, 115, 189, 206, 106, 180, 47, 195, 203, 48, 110, 112, 224, 14, 43, 189, 124, 127, 51, 222, 79, 226, 225, 87, 2, 32, 67, 23, 190, 222, 106, 22, 115, 139, 217, 244, 178, 53, 153, 99, 5, 176, 72, 77, 193, 61, 67, 134, 37, 238, 69, 66, 159, 28, 39, 5, 238, 125
|
||||
])
|
||||
},
|
||||
public: {
|
||||
hash: new Buffer([
|
||||
hash: Uint8Array.from([
|
||||
18, 32, 112, 151, 163, 167, 204, 243, 175, 123, 208, 162, 90, 84, 199, 174, 202, 110, 0, 119, 27, 202, 7, 149, 161, 251, 215, 168, 163, 54, 93, 54, 195, 20
|
||||
]),
|
||||
key: new Buffer([
|
||||
key: Uint8Array.from([
|
||||
8, 0, 18, 94, 48, 92, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 75, 0, 48, 72, 2, 65, 0, 230, 157, 160, 242, 74, 222, 87, 0, 77, 180, 91, 175, 217, 166, 2, 95, 193, 239, 195, 140, 224, 57, 84, 207, 46, 172, 113, 196, 20, 133, 117, 205, 45, 7, 224, 41, 40, 195, 254, 124, 14, 84, 223, 147, 67, 198, 48, 36, 53, 161, 112, 46, 153, 90, 19, 123, 94, 247, 5, 116, 1, 238, 32, 15, 2, 3, 1, 0, 1
|
||||
])
|
||||
},
|
||||
verify: {
|
||||
signature: new Buffer([
|
||||
signature: Uint8Array.from([
|
||||
3, 116, 81, 57, 91, 194, 7, 1, 230, 236, 229, 142, 36, 209, 208, 107, 47, 52, 164, 236, 139, 35, 155, 97, 43, 64, 145, 91, 19, 218, 149, 63, 99, 164, 191, 110, 145, 37, 18, 7, 98, 112, 144, 35, 29, 186, 169, 150, 165, 88, 145, 170, 197, 110, 42, 163, 188, 10, 42, 63, 34, 93, 91, 94, 199, 110, 10, 82, 238, 80, 93, 93, 77, 130, 22, 216, 229, 172, 36, 229, 82, 162, 20, 78, 19, 46, 82, 243, 43, 80, 115, 125, 145, 231, 194, 224, 30, 187, 55, 228, 74, 52, 203, 191, 254, 148, 136, 218, 62, 147, 171, 130, 251, 181, 105, 29, 238, 207, 197, 249, 61, 105, 202, 172, 160, 174, 43, 124, 115, 130, 169, 30, 76, 41, 52, 200, 2, 26, 53, 190, 43, 20, 203, 10, 217, 250, 47, 102, 92, 103, 197, 22, 108, 184, 74, 218, 82, 202, 180, 98, 13, 114, 12, 92, 1, 139, 150, 170, 8, 92, 32, 116, 168, 219, 157, 162, 28, 77, 29, 29, 74, 136, 144, 49, 173, 245, 253, 76, 167, 200, 169, 163, 7, 49, 133, 120, 99, 191, 53, 10, 66, 26, 234, 240, 139, 235, 134, 30, 55, 248, 150, 100, 242, 150, 159, 198, 44, 78, 150, 7, 133, 139, 59, 76, 3, 225, 94, 13, 89, 122, 34, 95, 95, 107, 74, 169, 171, 169, 222, 25, 191, 182, 148, 116, 66, 67, 102, 12, 193, 217, 247, 243, 148, 233, 161, 157
|
||||
]),
|
||||
data: new Buffer([
|
||||
data: Uint8Array.from([
|
||||
10, 16, 27, 128, 228, 220, 147, 176, 53, 105, 175, 171, 32, 213, 35, 236, 203, 60, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 24, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 44, 66, 108, 111, 119, 102, 105, 115, 104, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 10, 16, 220, 83, 240, 105, 6, 203, 78, 83, 210, 115, 6, 106, 98, 82, 1, 161, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 185, 234, 19, 191, 164, 33, 65, 94, 87, 42, 74, 83, 224, 25, 142, 44, 26, 7, 92, 242, 189, 42, 170, 197, 178, 92, 45, 240, 107, 141, 128, 59, 122, 252, 48, 140, 4, 85, 85, 203, 3, 197, 8, 127, 120, 98, 44, 169, 135, 196, 70, 137, 117, 180, 177, 134, 170, 35, 165, 88, 105, 30, 114, 138, 11, 96, 68, 99, 18, 149, 223, 166, 105, 12, 176, 77, 48, 214, 22, 236, 17, 154, 213, 209, 158, 169, 202, 5, 100, 210, 83, 90, 201, 38, 205, 246, 231, 106, 63, 86, 222, 143, 157, 173, 62, 4, 85, 232, 20, 188, 6, 209, 186, 132, 192, 117, 146, 181, 233, 26, 0, 240, 138, 206, 91, 170, 114, 137, 217, 132, 139, 242, 144, 213, 103, 101, 190, 146, 188, 250, 188, 134, 255, 70, 125, 78, 65, 136, 239, 190, 206, 139, 155, 140, 163, 233, 170, 247, 205, 87, 209, 19, 29, 173, 10, 147, 43, 28, 90, 46, 6, 197, 217, 186, 66, 68, 126, 76, 64, 184, 8, 170, 23, 79, 243, 223, 119, 133, 118, 50, 226, 44, 246, 176, 10, 161, 219, 83, 54, 68, 248, 5, 14, 177, 114, 54, 63, 11, 71, 136, 142, 56, 151, 123, 230, 61, 80, 15, 180, 42, 49, 220, 148, 99, 231, 20, 230, 220, 85, 207, 187, 37, 210, 137, 171, 125, 71, 14, 53, 100, 91, 83, 209, 50, 132, 165, 253, 25, 161, 5, 97, 164, 163, 83, 95, 53, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 15, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 4, 97, 54, 203, 112, 136, 34, 231, 162, 19, 154, 131, 27, 105, 26, 121, 238, 120, 25, 203, 66, 232, 53, 198, 20, 19, 96, 119, 218, 90, 64, 170, 3, 132, 116, 1, 87, 116, 232, 165, 161, 198, 117, 167, 60, 145, 1, 253, 108, 50, 150, 117, 8, 140, 133, 48, 30, 236, 36, 84, 186, 22, 144, 87, 101
|
||||
]),
|
||||
publicKey: new Buffer([
|
||||
publicKey: Uint8Array.from([
|
||||
8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1
|
||||
])
|
||||
}
|
||||
|
31
test/fixtures/go-key-secp256k1.js
vendored
Normal file
31
test/fixtures/go-key-secp256k1.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict'
|
||||
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
// The keypair and signature below were generated in a gore repl session (https://github.com/motemen/gore)
|
||||
// using the secp256k1 fork of go-libp2p-crypto by github user @vyzo
|
||||
//
|
||||
// gore> :import github.com/vyzo/go-libp2p-crypto
|
||||
// gore> :import crypto/rand
|
||||
// gore> :import io/ioutil
|
||||
// gore> priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Secp256k1, 256, rand.Reader)
|
||||
// gore> privBytes, err := priv.Bytes()
|
||||
// gore> pubBytes, err := pub.Bytes()
|
||||
// gore> msg := []byte("hello! and welcome to some awesome crypto primitives")
|
||||
// gore> sig, err := priv.Sign(msg)
|
||||
// gore> ioutil.WriteFile("/tmp/secp-go-priv.bin", privBytes, 0644)
|
||||
// gore> ioutil.WriteFile("/tmp/secp-go-pub.bin", pubBytes, 0644)
|
||||
// gore> ioutil.WriteFile("/tmp/secp-go-sig.bin", sig, 0644)
|
||||
//
|
||||
// The generated files were then read in a node repl with e.g.:
|
||||
// > fs.readFileSync('/tmp/secp-go-pub.bin').toString('hex')
|
||||
// '08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf'
|
||||
//
|
||||
// and the results copy/pasted in here
|
||||
|
||||
module.exports = {
|
||||
privateKey: uint8ArrayFromString('08021220358f15db8c2014d570e8e3a622454e2273975a3cca443ec0c45375b13d381d18', 'base16'),
|
||||
publicKey: uint8ArrayFromString('08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf', 'base16'),
|
||||
message: uint8ArrayFromString('hello! and welcome to some awesome crypto primitives'),
|
||||
signature: uint8ArrayFromString('304402200e4c629e9f5d99439115e60989cd40087f6978c36078b0b50cf3d30af5c38d4102204110342c8e7f0809897c1c7a66e49e1c6b7cb0a6ed6993640ec2fe742c1899a9', 'base16')
|
||||
}
|
14
test/fixtures/go-stretch-key.js
vendored
14
test/fixtures/go-stretch-key.js
vendored
@@ -3,28 +3,28 @@
|
||||
module.exports = [{
|
||||
cipher: 'AES-256',
|
||||
hash: 'SHA256',
|
||||
secret: new Buffer([
|
||||
secret: Uint8Array.from([
|
||||
195, 191, 209, 165, 209, 201, 127, 122, 136, 111, 31, 66, 111, 68, 38, 155, 216, 204, 46, 181, 200, 188, 170, 204, 104, 74, 239, 251, 173, 114, 222, 234
|
||||
]),
|
||||
k1: {
|
||||
iv: new Buffer([
|
||||
iv: Uint8Array.from([
|
||||
208, 132, 203, 169, 253, 52, 40, 83, 161, 91, 17, 71, 33, 136, 67, 96
|
||||
]),
|
||||
cipherKey: new Buffer([
|
||||
cipherKey: Uint8Array.from([
|
||||
156, 48, 241, 157, 92, 248, 153, 186, 114, 127, 195, 114, 106, 104, 215, 133, 35, 11, 131, 137, 123, 70, 74, 26, 15, 60, 189, 32, 67, 221, 115, 137
|
||||
]),
|
||||
macKey: new Buffer([
|
||||
macKey: Uint8Array.from([
|
||||
6, 179, 91, 245, 224, 56, 153, 120, 77, 140, 29, 5, 15, 213, 187, 65, 137, 230, 202, 120
|
||||
])
|
||||
},
|
||||
k2: {
|
||||
iv: new Buffer([
|
||||
iv: Uint8Array.from([
|
||||
236, 17, 34, 141, 90, 106, 197, 56, 197, 184, 157, 135, 91, 88, 112, 19
|
||||
]),
|
||||
cipherKey: new Buffer([
|
||||
cipherKey: Uint8Array.from([
|
||||
151, 145, 195, 219, 76, 195, 102, 109, 187, 231, 100, 150, 132, 245, 251, 130, 254, 37, 178, 55, 227, 34, 114, 39, 238, 34, 2, 193, 107, 130, 32, 87
|
||||
]),
|
||||
macKey: new Buffer([
|
||||
macKey: Uint8Array.from([
|
||||
3, 229, 77, 212, 241, 217, 23, 113, 220, 126, 38, 255, 18, 117, 108, 205, 198, 89, 1, 236
|
||||
])
|
||||
}
|
||||
|
10
test/fixtures/secp256k1.js
vendored
Normal file
10
test/fixtures/secp256k1.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
module.exports = {
|
||||
// protobuf marshaled key pair generated with libp2p-crypto-secp256k1
|
||||
// and marshaled with libp2p-crypto.marshalPublicKey / marshalPrivateKey
|
||||
pbmPrivateKey: uint8ArrayFromString('08021220e0600103010000000100000000000000be1dc82c2e000000e8d6030301000000', 'base16'),
|
||||
pbmPublicKey: uint8ArrayFromString('0802122103a9a7272a726fa083abf31ba44037f8347fbc5e5d3113d62a7c6bc26752fd8ee1', 'base16')
|
||||
}
|
33
test/helpers/test-garbage-error-handling.js
Normal file
33
test/helpers/test-garbage-error-handling.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
const util = require('util')
|
||||
const garbage = [uint8ArrayFromString('00010203040506070809', 'base16'), {}, null, false, undefined, true, 1, 0, uint8ArrayFromString(''), 'aGVsbG93b3JsZA==', 'helloworld', '']
|
||||
|
||||
function doTests (fncName, fnc, num, skipBuffersAndStrings) {
|
||||
if (!num) {
|
||||
num = 1
|
||||
}
|
||||
|
||||
garbage.forEach((garbage) => {
|
||||
if (skipBuffersAndStrings && (garbage instanceof Uint8Array || (typeof garbage) === 'string')) {
|
||||
// skip this garbage because it's a Uint8Array or a String and we were told do do that
|
||||
return
|
||||
}
|
||||
const args = []
|
||||
for (let i = 0; i < num; i++) {
|
||||
args.push(garbage)
|
||||
}
|
||||
it(fncName + '(' + args.map(garbage => util.inspect(garbage)).join(', ') + ')', async () => {
|
||||
try {
|
||||
await fnc.apply(null, args)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { doTests }
|
@@ -1,24 +0,0 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
const crypto = require('../src')
|
||||
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
|
||||
describe('HMAC', () => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${hash} - sign and verify`, (done) => {
|
||||
crypto.hmac.create(hash, new Buffer('secret'), (err, hmac) => {
|
||||
expect(err).to.not.exist
|
||||
|
||||
hmac.digest(new Buffer('hello world'), (err, sig) => {
|
||||
expect(err).to.not.exist
|
||||
expect(sig).to.have.length(hmac.length)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
23
test/hmac/hmac.spec.js
Normal file
23
test/hmac/hmac.spec.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
const crypto = require('../../src')
|
||||
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
|
||||
describe('HMAC', () => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${hash} - sign and verify`, async () => {
|
||||
const hmac = await crypto.hmac.create(hash, uint8ArrayFromString('secret'))
|
||||
const sig = await hmac.digest(uint8ArrayFromString('hello world'))
|
||||
expect(sig).to.have.length(hmac.length)
|
||||
})
|
||||
})
|
||||
})
|
@@ -1,101 +0,0 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
|
||||
const crypto = require('../src')
|
||||
const fixtures = require('./fixtures/go-key-rsa')
|
||||
|
||||
describe('libp2p-crypto', () => {
|
||||
let key
|
||||
before((done) => {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('marshalPublicKey and unmarshalPublicKey', () => {
|
||||
const key2 = crypto.unmarshalPublicKey(crypto.marshalPublicKey(key.public))
|
||||
|
||||
expect(key2.equals(key.public)).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
|
||||
crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key), (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(key2.equals(key)).to.be.eql(true)
|
||||
expect(key2.public.equals(key.public)).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// marshalled keys seem to be slightly different
|
||||
// unsure as to if this is just a difference in encoding
|
||||
// or a bug
|
||||
describe('go interop', () => {
|
||||
it('unmarshals private key', (done) => {
|
||||
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const hash = fixtures.private.hash
|
||||
expect(fixtures.private.key).to.be.eql(key.bytes)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.be.eql(hash)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshals public key', (done) => {
|
||||
const key = crypto.unmarshalPublicKey(fixtures.public.key)
|
||||
const hash = fixtures.public.hash
|
||||
|
||||
expect(crypto.marshalPublicKey(key)).to.be.eql(fixtures.public.key)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.be.eql(hash)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, private key', (done) => {
|
||||
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
const marshalled = crypto.marshalPrivateKey(key)
|
||||
expect(marshalled).to.be.eql(fixtures.private.key)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('unmarshal -> marshal, public key', () => {
|
||||
const key = crypto.unmarshalPublicKey(fixtures.public.key)
|
||||
const marshalled = crypto.marshalPublicKey(key)
|
||||
expect(
|
||||
fixtures.public.key.equals(marshalled)
|
||||
).to.be.eql(
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
@@ -1,74 +0,0 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
|
||||
const crypto = require('../src')
|
||||
const fixtures = require('./fixtures/go-stretch-key')
|
||||
|
||||
describe('keyStretcher', () => {
|
||||
describe('generate', () => {
|
||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
let res
|
||||
let secret
|
||||
|
||||
before((done) => {
|
||||
crypto.generateEphemeralKeyPair('P-256', (err, _res) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
res = _res
|
||||
res.genSharedKey(res.key, (err, _secret) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
secret = _secret
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ciphers.forEach((cipher) => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${cipher} - ${hash}`, (done) => {
|
||||
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(keys.k1).to.exist
|
||||
expect(keys.k2).to.exist
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
fixtures.forEach((test) => {
|
||||
it(`${test.cipher} - ${test.hash}`, (done) => {
|
||||
const cipher = test.cipher
|
||||
const hash = test.hash
|
||||
const secret = test.secret
|
||||
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
||||
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
||||
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
||||
|
||||
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
||||
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
||||
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
185
test/keys/ed25519.spec.js
Normal file
185
test/keys/ed25519.spec.js
Normal file
@@ -0,0 +1,185 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
const crypto = require('../../src')
|
||||
const ed25519 = crypto.keys.supportedKeys.ed25519
|
||||
const fixtures = require('../fixtures/go-key-ed25519')
|
||||
|
||||
const testGarbage = require('../helpers/test-garbage-error-handling')
|
||||
|
||||
/** @typedef {import("libp2p-crypto").PrivateKey} PrivateKey */
|
||||
|
||||
describe('ed25519', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {PrivateKey}
|
||||
*/
|
||||
let key
|
||||
before(async () => {
|
||||
key = await crypto.keys.generateKeyPair('Ed25519', 512)
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
expect(key).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
})
|
||||
|
||||
it('generates a valid key from seed', async () => {
|
||||
var seed = crypto.randomBytes(32)
|
||||
const seededkey = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
expect(seededkey).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
|
||||
const digest = await seededkey.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
})
|
||||
|
||||
it('generates the same key from the same seed', async () => {
|
||||
const seed = crypto.randomBytes(32)
|
||||
const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(true)
|
||||
expect(seededkey1.public.equals(seededkey2.public)).to.eql(true)
|
||||
})
|
||||
|
||||
it('generates different keys for different seeds', async () => {
|
||||
const seed1 = crypto.randomBytes(32)
|
||||
const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed1, 512)
|
||||
const seed2 = crypto.randomBytes(32)
|
||||
const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed2, 512)
|
||||
expect(seededkey1.equals(seededkey2)).to.eql(false)
|
||||
expect(seededkey1.public.equals(seededkey2.public)).to.eql(false)
|
||||
})
|
||||
|
||||
it('signs', async () => {
|
||||
const text = crypto.randomBytes(512)
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
expect(res).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('encoding', async () => {
|
||||
const keyMarshal = key.marshal()
|
||||
const key2 = await ed25519.unmarshalEd25519PrivateKey(keyMarshal)
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = ed25519.unmarshalEd25519PublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
})
|
||||
|
||||
it('key id', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
|
||||
const id = await key.id()
|
||||
expect(id).to.eql('12D3KooWLqLxEfJ9nDdEe8Kh8PFvNPQRYDQBwyL7CMM7HhVd5LsX')
|
||||
})
|
||||
|
||||
it('should export a password encrypted libp2p-key', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('Ed25519')
|
||||
const encryptedKey = await key.export('my secret')
|
||||
// Import the key
|
||||
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
|
||||
expect(key.equals(importedKey)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should fail to import libp2p-key with wrong password', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('Ed25519')
|
||||
const encryptedKey = await key.export('my secret', 'libp2p-key')
|
||||
try {
|
||||
await crypto.keys.import(encryptedKey, 'not my secret')
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
return
|
||||
}
|
||||
expect.fail('should have thrown')
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
it('equals itself', () => {
|
||||
expect(
|
||||
key.equals(key)
|
||||
).to.eql(
|
||||
true
|
||||
)
|
||||
|
||||
expect(
|
||||
key.public.equals(key.public)
|
||||
).to.eql(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('not equals other key', async () => {
|
||||
const key2 = await crypto.keys.generateKeyPair('Ed25519', 512)
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(data, sig)
|
||||
expect(valid).to.eql(true)
|
||||
})
|
||||
|
||||
it('fails to verify for different data', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
|
||||
expect(valid).to.be.eql(false)
|
||||
})
|
||||
|
||||
describe('throws error instead of crashing', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
testGarbage.doTests('key.verify', key.verify.bind(key), 2, null)
|
||||
testGarbage.doTests('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys), null, null)
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
// @ts-check
|
||||
it('verifies with data from go', async () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
|
||||
expect(ok).to.eql(true)
|
||||
})
|
||||
|
||||
it('does not include the redundant public key when marshalling privatekey', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey)
|
||||
const bytes = key.marshal()
|
||||
expect(bytes.length).to.equal(64)
|
||||
expect(bytes.slice(32)).to.eql(key.public.marshal())
|
||||
})
|
||||
|
||||
it('verifies with data from go with redundant public key', async () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.redundantPubKey.publicKey)
|
||||
const ok = await key.verify(fixtures.redundantPubKey.data, fixtures.redundantPubKey.signature)
|
||||
expect(ok).to.eql(true)
|
||||
})
|
||||
|
||||
it('generates the same signature as go', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
|
||||
const sig = await key.sign(fixtures.verify.data)
|
||||
expect(sig).to.eql(fixtures.verify.signature)
|
||||
})
|
||||
|
||||
it('generates the same signature as go with redundant public key', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey)
|
||||
const sig = await key.sign(fixtures.redundantPubKey.data)
|
||||
expect(sig).to.eql(fixtures.redundantPubKey.signature)
|
||||
})
|
||||
})
|
||||
})
|
77
test/keys/ephemeral-keys.spec.js
Normal file
77
test/keys/ephemeral-keys.spec.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const fixtures = require('../fixtures/go-elliptic-key')
|
||||
const crypto = require('../../src')
|
||||
|
||||
const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {Record<string, number>}
|
||||
*/
|
||||
const lengths = {
|
||||
'P-256': 65,
|
||||
'P-384': 97,
|
||||
'P-521': 133
|
||||
}
|
||||
const secretLengths = {
|
||||
'P-256': 32,
|
||||
'P-384': 48,
|
||||
'P-521': 66
|
||||
}
|
||||
|
||||
describe('generateEphemeralKeyPair', () => {
|
||||
curves.forEach((curve) => {
|
||||
it(`generate and shared key ${curve}`, async () => {
|
||||
const keys = await Promise.all([
|
||||
crypto.keys.generateEphemeralKeyPair(curve),
|
||||
crypto.keys.generateEphemeralKeyPair(curve)
|
||||
])
|
||||
|
||||
expect(keys[0].key).to.have.length(lengths[curve])
|
||||
expect(keys[1].key).to.have.length(lengths[curve])
|
||||
|
||||
const shared = await keys[0].genSharedKey(keys[1].key)
|
||||
expect(shared).to.have.length(secretLengths[curve])
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('generates a shared secret', async () => {
|
||||
const curve = fixtures.curve
|
||||
|
||||
const keys = await Promise.all([
|
||||
crypto.keys.generateEphemeralKeyPair(curve),
|
||||
crypto.keys.generateEphemeralKeyPair(curve)
|
||||
])
|
||||
|
||||
const alice = keys[0]
|
||||
const bob = keys[1]
|
||||
bob.key = fixtures.bob.public
|
||||
|
||||
const secrets = await Promise.all([
|
||||
alice.genSharedKey(bob.key),
|
||||
bob.genSharedKey(alice.key, fixtures.bob)
|
||||
])
|
||||
|
||||
expect(secrets[0]).to.eql(secrets[1])
|
||||
expect(secrets[0]).to.have.length(32)
|
||||
})
|
||||
})
|
||||
|
||||
it('handles bad curve name', async () => {
|
||||
try {
|
||||
await crypto.keys.generateEphemeralKeyPair('bad name')
|
||||
} catch (err) {
|
||||
expect(err.code).equals('ERR_INVALID_CURVE')
|
||||
return
|
||||
}
|
||||
expect.fail('Did not throw error')
|
||||
})
|
||||
})
|
66
test/keys/key-stretcher.spec.js
Normal file
66
test/keys/key-stretcher.spec.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const { expectErrCode } = require('../util')
|
||||
const crypto = require('../../src')
|
||||
const fixtures = require('../fixtures/go-stretch-key')
|
||||
|
||||
describe('keyStretcher', () => {
|
||||
describe('generate', () => {
|
||||
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
|
||||
const hashes = ['SHA1', 'SHA256', 'SHA512']
|
||||
let res
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {Uint8Array}
|
||||
*/
|
||||
let secret
|
||||
|
||||
before(async () => {
|
||||
res = await crypto.keys.generateEphemeralKeyPair('P-256')
|
||||
secret = await res.genSharedKey(res.key)
|
||||
})
|
||||
|
||||
ciphers.forEach((cipher) => {
|
||||
hashes.forEach((hash) => {
|
||||
it(`${cipher} - ${hash}`, async () => {
|
||||
const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
expect(keys.k1).to.exist()
|
||||
expect(keys.k2).to.exist()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('handles invalid cipher type', () => {
|
||||
return expectErrCode(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret'), 'ERR_INVALID_CIPHER_TYPE')
|
||||
})
|
||||
|
||||
it('handles missing hash type', () => {
|
||||
return expectErrCode(crypto.keys.keyStretcher('AES-128', '', 'secret'), 'ERR_MISSING_HASH_TYPE')
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
fixtures.forEach((test) => {
|
||||
it(`${test.cipher} - ${test.hash}`, async () => {
|
||||
const cipher = test.cipher
|
||||
const hash = test.hash
|
||||
const secret = test.secret
|
||||
const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
|
||||
|
||||
expect(keys.k1.iv).to.be.eql(test.k1.iv)
|
||||
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
|
||||
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
|
||||
|
||||
expect(keys.k2.iv).to.be.eql(test.k2.iv)
|
||||
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
|
||||
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
53
test/keys/rsa-crypto-libs.js
Normal file
53
test/keys/rsa-crypto-libs.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint-env mocha */
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
chai.use(require('chai-string'))
|
||||
|
||||
const LIBS = ['ursa', 'keypair']
|
||||
|
||||
describe('RSA crypto libs', function () {
|
||||
this.timeout(20 * 1000)
|
||||
|
||||
LIBS.forEach(lib => {
|
||||
describe(lib, () => {
|
||||
let crypto
|
||||
let rsa
|
||||
|
||||
before(() => {
|
||||
process.env.LP2P_FORCE_CRYPTO_LIB = lib
|
||||
|
||||
for (const path in require.cache) { // clear module cache
|
||||
if (path.endsWith('.js')) {
|
||||
delete require.cache[path]
|
||||
}
|
||||
}
|
||||
|
||||
crypto = require('../../src')
|
||||
rsa = crypto.keys.supportedKeys.rsa
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
})
|
||||
|
||||
after(() => {
|
||||
for (const path in require.cache) { // clear module cache
|
||||
if (path.endsWith('.js')) {
|
||||
delete require.cache[path]
|
||||
}
|
||||
}
|
||||
|
||||
delete process.env.LP2P_FORCE_CRYPTO_LIB
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
403
test/keys/rsa.spec.js
Normal file
403
test/keys/rsa.spec.js
Normal file
@@ -0,0 +1,403 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
chai.use(require('chai-string'))
|
||||
const { expectErrCode } = require('../util')
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
|
||||
const crypto = require('../../src')
|
||||
const rsa = crypto.keys.supportedKeys.rsa
|
||||
const fixtures = require('../fixtures/go-key-rsa')
|
||||
|
||||
const testGarbage = require('../helpers/test-garbage-error-handling')
|
||||
|
||||
/** @typedef {import('libp2p-crypto').keys.supportedKeys.rsa.RsaPrivateKey} RsaPrivateKey */
|
||||
|
||||
describe('RSA', function () {
|
||||
this.timeout(20 * 1000)
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {RsaPrivateKey}
|
||||
*/
|
||||
let key
|
||||
|
||||
before(async () => {
|
||||
key = await rsa.generateKeyPair(512)
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
})
|
||||
|
||||
it('signs', async () => {
|
||||
const text = key.genSecret()
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
expect(res).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('encoding', async () => {
|
||||
const keyMarshal = key.marshal()
|
||||
const key2 = await rsa.unmarshalRsaPrivateKey(keyMarshal)
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
})
|
||||
|
||||
it('key id', async () => {
|
||||
const key = await crypto.keys.unmarshalPrivateKey(uint8ArrayFromString('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64pad'))
|
||||
const id = await key.id()
|
||||
expect(id).to.eql('QmQgsppVMDUpe83wcAqaemKbYvHeF127gnSFQ1xFnBodVw')
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
it('equals itself', () => {
|
||||
expect(key.equals(key)).to.eql(true)
|
||||
|
||||
expect(key.public.equals(key.public)).to.eql(true)
|
||||
})
|
||||
|
||||
it('not equals other key', async () => {
|
||||
const key2 = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(data, sig)
|
||||
expect(valid).to.be.eql(true)
|
||||
})
|
||||
|
||||
it('encrypt and decrypt', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const enc = await key.public.encrypt(data)
|
||||
const dec = await key.decrypt(enc)
|
||||
expect(dec).to.be.eql(data)
|
||||
})
|
||||
|
||||
it('encrypt decrypt browser/node interop', async () => {
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
const id = await crypto.keys.unmarshalPrivateKey(uint8ArrayFromString('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64pad'))
|
||||
|
||||
const msg = uint8ArrayFromString('hello')
|
||||
|
||||
// browser
|
||||
const dec1 = id.decrypt(uint8ArrayFromString('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64pad'))
|
||||
expect(dec1).to.be.eql(msg)
|
||||
// node
|
||||
const dec2 = id.decrypt(uint8ArrayFromString('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64pad'))
|
||||
expect(dec2).to.be.eql(msg)
|
||||
})
|
||||
|
||||
it('fails to verify for different data', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
|
||||
expect(valid).to.be.eql(false)
|
||||
})
|
||||
|
||||
describe('export and import', () => {
|
||||
it('password protected PKCS #8', async () => {
|
||||
const pem = await key.export('my secret', 'pkcs-8')
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
const clone = await crypto.keys.import(pem, 'my secret')
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
})
|
||||
|
||||
it('defaults to PKCS #8', async () => {
|
||||
const pem = await key.export('another secret')
|
||||
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
||||
const clone = await crypto.keys.import(pem, 'another secret')
|
||||
expect(clone).to.exist()
|
||||
expect(key.equals(clone)).to.eql(true)
|
||||
})
|
||||
|
||||
it('should export a password encrypted libp2p-key', async () => {
|
||||
const encryptedKey = await key.export('my secret', 'libp2p-key')
|
||||
// Import the key
|
||||
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
|
||||
expect(key.equals(importedKey)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should fail to import libp2p-key with wrong password', async () => {
|
||||
const encryptedKey = await key.export('my secret', 'libp2p-key')
|
||||
try {
|
||||
await crypto.keys.import(encryptedKey, 'not my secret')
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
return
|
||||
}
|
||||
expect.fail('should have thrown')
|
||||
})
|
||||
|
||||
it('needs correct password', async () => {
|
||||
const pem = await key.export('another secret')
|
||||
try {
|
||||
await crypto.keys.import(pem, 'not the secret')
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('handles invalid export type', () => {
|
||||
return expectErrCode(key.export('secret', 'invalid-type'), 'ERR_INVALID_EXPORT_FORMAT')
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error instead of crashing', () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
testGarbage.doTests('key.verify', key.verify.bind(key), 2, true)
|
||||
testGarbage.doTests(
|
||||
'crypto.keys.unmarshalPrivateKey',
|
||||
crypto.keys.unmarshalPrivateKey.bind(crypto.keys),
|
||||
null,
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('verifies with data from go', async () => {
|
||||
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
|
||||
expect(ok).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('openssl interop', () => {
|
||||
it('can read a private key', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:3072
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
*/
|
||||
const pem = `-----BEGIN PRIVATE KEY-----
|
||||
MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDp0Whyqa8KmdvK
|
||||
0MsQGJEBzDAEHAZc0C6cr0rkb6Xwo+yB5kjZBRDORk0UXtYGE1pYt4JhUTmMzcWO
|
||||
v2xTIsdbVMQlNtput2U8kIqS1cSTkX5HxOJtCiIzntMzuR/bGPSOexkyFQ8nCUqb
|
||||
ROS7cln/ixprra2KMAKldCApN3ue2jo/JI1gyoS8sekhOASAa0ufMPpC+f70sc75
|
||||
Y53VLnGBNM43iM/2lsK+GI2a13d6rRy86CEM/ygnh/EDlyNDxo+SQmy6GmSv/lmR
|
||||
xgWQE2dIfK504KIxFTOphPAQAr9AsmcNnCQLhbz7YTsBz8WcytHGQ0Z5pnBQJ9AV
|
||||
CX9E6DFHetvs0CNLVw1iEO06QStzHulmNEI/3P8I1TIxViuESJxSu3pSNwG1bSJZ
|
||||
+Qee24vvlz/slBzK5gZWHvdm46v7vl5z7SA+whncEtjrswd8vkJk9fI/YTUbgOC0
|
||||
HWMdc2t/LTZDZ+LUSZ/b2n5trvdJSsOKTjEfuf0wICC08pUUk8MCAwEAAQKCAYEA
|
||||
ywve+DQCneIezHGk5cVvp2/6ApeTruXalJZlIxsRr3eq2uNwP4X2oirKpPX2RjBo
|
||||
NMKnpnsyzuOiu+Pf3hJFrTpfWzHXXm5Eq+OZcwnQO5YNY6XGO4qhSNKT9ka9Mzbo
|
||||
qRKdPrCrB+s5rryVJXKYVSInP3sDSQ2IPsYpZ6GW6Mv56PuFCpjTzElzejV7M0n5
|
||||
0bRmn+MZVMVUR54KYiaCywFgUzmr3yfs1cfcsKqMRywt2J58lRy/chTLZ6LILQMv
|
||||
4V01neVJiRkTmUfIWvc1ENIFM9QJlky9AvA5ASvwTTRz8yOnxoOXE/y4OVyOePjT
|
||||
cz9eumu9N5dPuUIMmsYlXmRNaeGZPD9bIgKY5zOlfhlfZSuOLNH6EHBNr6JAgfwL
|
||||
pdP43sbg2SSNKpBZ0iSMvpyTpbigbe3OyhnFH/TyhcC2Wdf62S9/FRsvjlRPbakW
|
||||
YhKAA2kmJoydcUDO5ccEga8b7NxCdhRiczbiU2cj70pMIuOhDlGAznyxsYbtyxaB
|
||||
AoHBAPy6Cbt6y1AmuId/HYfvms6i8B+/frD1CKyn+sUDkPf81xSHV7RcNrJi1S1c
|
||||
V55I0y96HulsR+GmcAW1DF3qivWkdsd/b4mVkizd/zJm3/Dm8p8QOnNTtdWvYoEB
|
||||
VzfAhBGaR/xflSLxZh2WE8ZHQ3IcRCXV9ZFgJ7PMeTprBJXzl0lTptvrHyo9QK1v
|
||||
obLrL/KuXWS0ql1uSnJr1vtDI5uW8WU4GDENeU5b/CJHpKpjVxlGg+7pmLknxlBl
|
||||
oBnZnQKBwQDs2Ky29qZ69qnPWowKceMJ53Z6uoUeSffRZ7xuBjowpkylasEROjuL
|
||||
nyAihIYB7fd7R74CnRVYLI+O2qXfNKJ8HN+TgcWv8LudkRcnZDSvoyPEJAPyZGfr
|
||||
olRCXD3caqtarlZO7vXSAl09C6HcL2KZ8FuPIEsuO0Aw25nESMg9eVMaIC6s2eSU
|
||||
NUt6xfZw1JC0c+f0LrGuFSjxT2Dr5WKND9ageI6afuauMuosjrrOMl2g0dMcSnVz
|
||||
KrtYa7Wi1N8CgcBFnuJreUplDCWtfgEen40f+5b2yAQYr4fyOFxGxdK73jVJ/HbW
|
||||
wsh2n+9mDZg9jIZQ/+1gFGpA6V7W06dSf/hD70ihcKPDXSbloUpaEikC7jxMQWY4
|
||||
uwjOkwAp1bq3Kxu21a+bAKHO/H1LDTrpVlxoJQ1I9wYtRDXrvBpxU2XyASbeFmNT
|
||||
FhSByFn27Ve4OD3/NrWXtoVwM5/ioX6ZvUcj55McdTWE3ddbFNACiYX9QlyOI/TY
|
||||
bhWafDCPmU9fj6kCgcEAjyQEfi9jPj2FM0RODqH1zS6OdG31tfCOTYicYQJyeKSI
|
||||
/hAezwKaqi9phHMDancfcupQ89Nr6vZDbNrIFLYC3W+1z7hGeabMPNZLYAs3rE60
|
||||
dv4tRHlaNRbORazp1iTBmvRyRRI2js3O++3jzOb2eILDUyT5St+UU/LkY7R5EG4a
|
||||
w1df3idx9gCftXufDWHqcqT6MqFl0QgIzo5izS68+PPxitpRlR3M3Mr4rCU20Rev
|
||||
blphdF+rzAavYyj1hYuRAoHBANmxwbq+QqsJ19SmeGMvfhXj+T7fNZQFh2F0xwb2
|
||||
rMlf4Ejsnx97KpCLUkoydqAs2q0Ws9Nkx2VEVx5KfUD7fWhgbpdnEPnQkfeXv9sD
|
||||
vZTuAoqInN1+vj1TME6EKR/6D4OtQygSNpecv23EuqEvyXWqRVsRt9Qd2B0H4k7h
|
||||
gnjREs10u7zyqBIZH7KYVgyh27WxLr859ap8cKAH6Fb+UOPtZo3sUeeume60aebn
|
||||
4pMwXeXP+LO8NIfRXV8mgrm86g==
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, '')
|
||||
expect(key).to.exist()
|
||||
const id = await key.id()
|
||||
expect(id).to.equal('QmfWu2Xp8DZzCkZZzoPB9rcrq4R4RZid6AWE6kmrUAzuHy')
|
||||
})
|
||||
|
||||
// AssertionError: expected 'this only supports pkcs5PBES2' to not exist
|
||||
it.skip('can read a private encrypted key (v1)', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:1024
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
* -out foo.pem
|
||||
* openssl pkcs8 -in foo.pem -topk8 -passout pass:mypassword
|
||||
*/
|
||||
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIICoTAbBgkqhkiG9w0BBQMwDgQI2563Jugj/KkCAggABIICgPxHkKtUUE8EWevq
|
||||
eX9nTjqpbsv0QoXQMhegfxDELJLU8tj6V0bWNt7QDdfQ1n6FRgnNvNGick6gyqHH
|
||||
yH9qC2oXwkDFP7OrHp2NEZd7DHQLLc+L4KJ/0dzsiZ1U9no7XzQMUay9Bc918ADE
|
||||
pN2/EqigWkaG4gNjkAeKWr6+BNRevDXlSvls7YDboNcTiACi5zJkthivB9g3vT1m
|
||||
gPdN6Gf/mmqtBTDHeqj5QsmXYqeCyo5b26JgYsziABVZDHph4ekPUsTvudRpE9Ex
|
||||
baXwdYEAZxVpSbTvQ3A5qysjSZeM9ttfRTSSwL391q7dViz4+aujpk0Vj7piH+1B
|
||||
CkfO8/XudRdRlnOe+KjMidktKCsMGCIOW92IlfMvIQ/Zn1GTYj9bRXONFNJ2WPND
|
||||
UmCKnL7cmworwg/weRorrGKBWIGspU+tDASOPSvIGKo6Hoxm4CN1TpDRY7DAGlgm
|
||||
Y3TEbMYfpXyzkPjvAhJDt03D3J9PrTO6uM5d7YUaaTmJ2TQFQVF2Lc3Uz8lDJLs0
|
||||
ZYtfQ/4H+YY2RrX7ua7t6ArUcYXZtv0J4lRYWjwV8fGPUVc0d8xLJU0Yjf4BD7K8
|
||||
rsavHo9b5YvBUX7SgUyxAEembEOe3SjQ+gPu2U5wovcjUuC9eItEEsXGrx30BQ0E
|
||||
8BtK2+hp0eMkW5/BYckJkH+Yl8ypbzRGRRIZzLgeI4JveSx/mNhewfgTr+ORPThZ
|
||||
mBdkD5r+ixWF174naw53L8U9wF8kiK7pIE1N9TR4USEeovLwX6Ni/2MMDZedOfof
|
||||
2f77eUdLsK19/5/lcgAAYaXauXWhy2d2r3SayFrC9woy0lh2VLKRMBjcx1oWb7dp
|
||||
0uxzo5Y=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 aes-128-cbc)', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:1024
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
* -out foo.pem
|
||||
* openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword
|
||||
*/
|
||||
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA
|
||||
MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc
|
||||
O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3
|
||||
BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2
|
||||
BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs
|
||||
KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc
|
||||
0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP
|
||||
q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez
|
||||
qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/
|
||||
1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy
|
||||
V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn
|
||||
wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB
|
||||
PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF
|
||||
wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy
|
||||
ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj
|
||||
nPyn
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 aes-256-cbc)', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:1024
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
* -out foo.pem
|
||||
* openssl pkcs8 -in foo.pem -topk8 -v2 aes-256-cbc -passout pass:mypassword
|
||||
*/
|
||||
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIhuL894loRucCAggA
|
||||
MB0GCWCGSAFlAwQBKgQQEoEtsjW3iC9/u0uGvkxX7wSCAoAsX3l6JoR2OGbT8CkY
|
||||
YT3RQFqquOgItYOHw6E3tir2YrmxEAo99nxoL8pdto37KSC32eAGnfv5R1zmHHSx
|
||||
0M3/y2AWiCBTX95EEzdtGC1hK3PBa/qpp/xEmcrsjYN6NXxMAkhC0hMP/HdvqMAg
|
||||
ee7upvaYJsJcl8QLFNayAWr8b8cZA/RBhGEIRl59Eyj6nNtxDt3bCrfe06o1CPCV
|
||||
50/fRZEwFOi/C6GYvPN6MrPZO3ALBWgopLT2yQqycTKtfxYWIdOsMBkAjKf2D6Pk
|
||||
u2mqBsaP4b71jIIeT4euSJLsoJV+O39s8YHXtW8GtOqp7V5kIlnm90lZ9wzeLTZ7
|
||||
HJsD/jEdYto5J3YWm2wwEDccraffJSm7UDtJBvQdIx832kxeFCcGQjW38Zl1qqkg
|
||||
iTH1PLTypxj2ZuviS2EkXVFb/kVU6leWwOt6fqWFC58UvJKeCk/6veazz3PDnTWM
|
||||
92ClUqFd+CZn9VT4CIaJaAc6v5NLpPp+T9sRX9AtequPm7FyTeevY9bElfyk9gW9
|
||||
JDKgKxs6DGWDa16RL5vzwtU+G3o6w6IU+mEwa6/c+hN+pRFs/KBNLLSP9OHBx7BJ
|
||||
X/32Ft+VFhJaK+lQ+f+hve7od/bgKnz4c/Vtp7Dh51DgWgCpBgb8p0vqu02vTnxD
|
||||
BXtDv3h75l5PhvdWfVIzpMWRYFvPR+vJi066FjAz2sjYc0NMLSYtZWyWoIInjhoX
|
||||
Dp5CQujCtw/ZSSlwde1DKEWAW4SeDZAOQNvuz0rU3eosNUJxEmh3aSrcrRtDpw+Y
|
||||
mBUuWAZMpz7njBi7h+JDfmSW/GAaMwrVFC2gef5375R0TejAh+COAjItyoeYEvv8
|
||||
DQd8
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 des)', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:1024
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
* -out foo.pem
|
||||
* openssl pkcs8 -in foo.pem -topk8 -v2 des -passout pass:mypassword
|
||||
*/
|
||||
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIICwzA9BgkqhkiG9w0BBQ0wMDAbBgkqhkiG9w0BBQwwDgQI0lXp62ozXvwCAggA
|
||||
MBEGBSsOAwIHBAiR3Id5vH0u4wSCAoDQQYOrrkPFPIa0S5fQGXnJw1F/66g92Gs1
|
||||
TkGydn4ouabWb++Vbi2chee1oyZsN2l8YNzDi0Gb2PfjsGpg2aJk0a3/efgA0u6T
|
||||
leEH1dA/7Hr9NVspgHkaXpHt3X6wdbznLYJeAelfj7sDXpOkULGWCkCst0Txb6bi
|
||||
Oxv4c0yYykiuUrp+2xvHbF9c2PrcDb58u/OBZcCg3QB1gTugQKM+ZIBRhcTEFLrm
|
||||
8gWbzBfwYiUm6aJce4zoafP0NSlEOBbpbr73A08Q1IK6pISwltOUhhTvspSZnK41
|
||||
y2CHt5Drnpl1pfOw9Q0svO3VrUP+omxP1SFP17ZfaRGw2uHd08HJZs438x5dIQoH
|
||||
QgjlZ8A5rcT3FjnytSh3fln2ZxAGuObghuzmOEL/+8fkGER9QVjmQlsL6OMfB4j4
|
||||
ZAkLf74uaTdegF3SqDQaGUwWgk7LyualmUXWTBoeP9kRIsRQLGzAEmd6duBPypED
|
||||
HhKXP/ZFA1kVp3x1fzJ2llMFB3m1JBwy4PiohqrIJoR+YvKUvzVQtbOjxtCEAj87
|
||||
JFnlQj0wjTd6lfNn+okewMNjKINZx+08ui7XANNU/l18lHIIz3ssXJSmqMW+hRZ9
|
||||
9oB2tntLrnRMhkVZDVHadq7eMFOPu0rkekuaZm9CO2vu4V7Qa2h+gOoeczYza0H7
|
||||
A+qCKbprxyL8SKI5vug2hE+mfC1leXVRtUYm1DnE+oet99bFd0fN20NwTw0rOeRg
|
||||
0Z+/ZpQNizrXxfd3sU7zaJypWCxZ6TD/U/AKBtcb2gqmUjObZhbfbWq6jU2Ye//w
|
||||
EBqQkwAUXR1tNekF8CWLOrfC/wbLRxVRkayb8bQUfdgukLpz0bgw
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
})
|
||||
|
||||
it('can read a private encrypted key (v2 des3)', async () => {
|
||||
/*
|
||||
* Generated with
|
||||
* openssl genpkey -algorithm RSA
|
||||
* -pkeyopt rsa_keygen_bits:1024
|
||||
* -pkeyopt rsa_keygen_pubexp:65537
|
||||
* -out foo.pem
|
||||
* openssl pkcs8 -in foo.pem -topk8 -v2 des3 -passout pass:mypassword
|
||||
*/
|
||||
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQISznrfHd+D58CAggA
|
||||
MBQGCCqGSIb3DQMHBAhx0DnnUvDiHASCAoCceplm+Cmwlgvn4hNsv6e4c/S1iA7w
|
||||
2hU7Jt8JgRCIMWjP2FthXOAFLa2fD4g3qncYXcDAFBXNyoh25OgOwstO14YkxhDi
|
||||
wG4TeppGUt9IlyyCol6Z4WhQs1TGm5OcD5xDta+zBXsBnlgmKLD5ZXPEYB+3v/Dg
|
||||
SvM4sQz6NgkVHN52hchERsnknwSOghiK9mIBH0RZU5LgzlDy2VoBCiEPVdZ7m4F2
|
||||
dft5e82zFS58vwDeNN/0r7fC54TyJf/8k3q94+4Hp0mseZ67LR39cvnEKuDuFROm
|
||||
kLPLekWt5R2NGdunSQlA79BkrNB1ADruO8hQOOHMO9Y3/gNPWLKk+qrfHcUni+w3
|
||||
Ofq+rdfakHRb8D6PUmsp3wQj6fSOwOyq3S50VwP4P02gKcZ1om1RvEzTbVMyL3sh
|
||||
hZcVB3vViu3DO2/56wo29lPVTpj9bSYjw/CO5jNpPBab0B/Gv7JAR0z4Q8gn6OPy
|
||||
qf+ddyW4Kcb6QUtMrYepghDthOiS3YJV/zCNdL3gTtVs5Ku9QwQ8FeM0/5oJZPlC
|
||||
TxGuOFEJnYRWqIdByCP8mp/qXS5alSR4uoYQSd7vZG4vkhkPNSAwux/qK1IWfqiW
|
||||
3XlZzrbD//9IzFVqGRs4nRIFq85ULK0zAR57HEKIwGyn2brEJzrxpV6xsHBp+m4w
|
||||
6r0+PtwuWA0NauTCUzJ1biUdH8t0TgBL6YLaMjlrfU7JstH3TpcZzhJzsjfy0+zV
|
||||
NT2TO3kSzXpQ5M2VjOoHPm2fqxD/js+ThDB3QLi4+C7HqakfiTY1lYzXl9/vayt6
|
||||
DUD29r9pYL9ErB9tYko2rat54EY7k7Ts6S5jf+8G7Zz234We1APhvqaG
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
`
|
||||
const key = await crypto.keys.import(pem, 'mypassword')
|
||||
expect(key).to.exist()
|
||||
})
|
||||
})
|
||||
})
|
287
test/keys/secp256k1.spec.js
Normal file
287
test/keys/secp256k1.spec.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const crypto = require('../../src')
|
||||
const secp256k1 = crypto.keys.supportedKeys.secp256k1
|
||||
const keysPBM = crypto.keys.keysPBM
|
||||
const randomBytes = crypto.randomBytes
|
||||
const secp256k1Crypto = require('../../src/keys/secp256k1')(randomBytes)
|
||||
const uint8ArrayFromString = require('uint8arrays/from-string')
|
||||
const fixtures = require('../fixtures/go-key-secp256k1')
|
||||
|
||||
describe('secp256k1 keys', () => {
|
||||
let key
|
||||
|
||||
before(async () => {
|
||||
key = await secp256k1.generateKeyPair()
|
||||
})
|
||||
|
||||
it('generates a valid key', async () => {
|
||||
expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
|
||||
expect(key.public).to.be.an.instanceof(secp256k1.Secp256k1PublicKey)
|
||||
|
||||
const digest = await key.hash()
|
||||
expect(digest).to.have.length(34)
|
||||
|
||||
const publicDigest = await key.public.hash()
|
||||
expect(publicDigest).to.have.length(34)
|
||||
})
|
||||
|
||||
it('optionally accepts a `bits` argument when generating a key', async () => {
|
||||
const _key = await secp256k1.generateKeyPair()
|
||||
expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
|
||||
})
|
||||
|
||||
it('signs', async () => {
|
||||
const text = randomBytes(512)
|
||||
const sig = await key.sign(text)
|
||||
const res = await key.public.verify(text, sig)
|
||||
expect(res).to.equal(true)
|
||||
})
|
||||
|
||||
it('encoding', async () => {
|
||||
const keyMarshal = key.marshal()
|
||||
const key2 = await secp256k1.unmarshalSecp256k1PrivateKey(keyMarshal)
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(keyMarshal).to.eql(keyMarshal2)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = secp256k1.unmarshalSecp256k1PublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(pkMarshal).to.eql(pkMarshal2)
|
||||
})
|
||||
|
||||
it('key id', async () => {
|
||||
const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
|
||||
const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data)
|
||||
const id = await key.id()
|
||||
expect(id).to.eql('QmPCyMBGEyifPtx5aa6k6wkY9N1eBf9vHK1eKfNc35q9uq')
|
||||
})
|
||||
|
||||
it('should export a password encrypted libp2p-key', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('secp256k1')
|
||||
const encryptedKey = await key.export('my secret')
|
||||
// Import the key
|
||||
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
|
||||
expect(key.equals(importedKey)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should fail to import libp2p-key with wrong password', async () => {
|
||||
const key = await crypto.keys.generateKeyPair('secp256k1')
|
||||
const encryptedKey = await key.export('my secret', 'libp2p-key')
|
||||
try {
|
||||
await crypto.keys.import(encryptedKey, 'not my secret')
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
return
|
||||
}
|
||||
expect.fail('should have thrown')
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
it('equals itself', () => {
|
||||
expect(key.equals(key)).to.eql(true)
|
||||
|
||||
expect(key.public.equals(key.public)).to.eql(true)
|
||||
})
|
||||
|
||||
it('not equals other key', async () => {
|
||||
const key2 = await secp256k1.generateKeyPair()
|
||||
expect(key.equals(key2)).to.eql(false)
|
||||
expect(key2.equals(key)).to.eql(false)
|
||||
expect(key.public.equals(key2.public)).to.eql(false)
|
||||
expect(key2.public.equals(key.public)).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(data, sig)
|
||||
expect(valid).to.eql(true)
|
||||
})
|
||||
|
||||
it('fails to verify for different data', async () => {
|
||||
const data = uint8ArrayFromString('hello world')
|
||||
const sig = await key.sign(data)
|
||||
const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
|
||||
expect(valid).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('key generation error', () => {
|
||||
let generateKey
|
||||
let secp256k1
|
||||
|
||||
before(() => {
|
||||
generateKey = secp256k1Crypto.generateKey
|
||||
secp256k1 = require('../../src/keys/secp256k1-class')(keysPBM, randomBytes, secp256k1Crypto)
|
||||
secp256k1Crypto.generateKey = () => { throw new Error('Error generating key') }
|
||||
})
|
||||
|
||||
after(() => {
|
||||
secp256k1Crypto.generateKey = generateKey
|
||||
})
|
||||
|
||||
it('returns an error if key generation fails', async () => {
|
||||
try {
|
||||
await secp256k1.generateKeyPair()
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal('Error generating key')
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('handles generation of invalid key', () => {
|
||||
let generateKey
|
||||
let secp256k1
|
||||
|
||||
before(() => {
|
||||
generateKey = secp256k1Crypto.generateKey
|
||||
secp256k1 = require('../../src/keys/secp256k1-class')(keysPBM, randomBytes, secp256k1Crypto)
|
||||
secp256k1Crypto.generateKey = () => uint8ArrayFromString('not a real key')
|
||||
})
|
||||
|
||||
after(() => {
|
||||
secp256k1Crypto.generateKey = generateKey
|
||||
})
|
||||
|
||||
it('returns an error if key generator returns an invalid key', async () => {
|
||||
try {
|
||||
await secp256k1.generateKeyPair()
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32')
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('crypto functions', () => {
|
||||
let privKey
|
||||
let pubKey
|
||||
|
||||
before(async () => {
|
||||
privKey = await secp256k1Crypto.generateKey()
|
||||
pubKey = secp256k1Crypto.computePublicKey(privKey)
|
||||
})
|
||||
|
||||
it('generates valid keys', () => {
|
||||
expect(() => {
|
||||
secp256k1Crypto.validatePrivateKey(privKey)
|
||||
secp256k1Crypto.validatePublicKey(pubKey)
|
||||
}).to.not.throw()
|
||||
})
|
||||
|
||||
it('does not validate an invalid key', () => {
|
||||
expect(() => secp256k1Crypto.validatePublicKey(uint8ArrayFromString('42'))).to.throw()
|
||||
expect(() => secp256k1Crypto.validatePrivateKey(uint8ArrayFromString('42'))).to.throw()
|
||||
})
|
||||
|
||||
it('validates a correct signature', async () => {
|
||||
const sig = await secp256k1Crypto.hashAndSign(privKey, uint8ArrayFromString('hello'))
|
||||
const valid = await secp256k1Crypto.hashAndVerify(pubKey, sig, uint8ArrayFromString('hello'))
|
||||
expect(valid).to.equal(true)
|
||||
})
|
||||
|
||||
it('errors if given a null Uint8Array to sign', async () => {
|
||||
try {
|
||||
await secp256k1Crypto.hashAndSign(privKey, null)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('errors when signing with an invalid key', async () => {
|
||||
try {
|
||||
await secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello'))
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32')
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('errors if given a null Uint8Array to validate', async () => {
|
||||
const sig = await secp256k1Crypto.hashAndSign(privKey, uint8ArrayFromString('hello'))
|
||||
|
||||
try {
|
||||
await secp256k1Crypto.hashAndVerify(privKey, sig, null)
|
||||
} catch (err) {
|
||||
return // expected
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('errors when validating a message with an invalid signature', async () => {
|
||||
try {
|
||||
await secp256k1Crypto.hashAndVerify(pubKey, uint8ArrayFromString('invalid-sig'), uint8ArrayFromString('hello'))
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal('Signature could not be parsed')
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('errors when signing with an invalid key', async () => {
|
||||
try {
|
||||
await secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello'))
|
||||
} catch (err) {
|
||||
return expect(err.message).to.equal('Expected private key to be an Uint8Array with length 32')
|
||||
}
|
||||
throw new Error('Expected error to be thrown')
|
||||
})
|
||||
|
||||
it('throws when compressing an invalid public key', () => {
|
||||
expect(() => secp256k1Crypto.compressPublicKey(uint8ArrayFromString('42'))).to.throw()
|
||||
})
|
||||
|
||||
it('throws when decompressing an invalid public key', () => {
|
||||
expect(() => secp256k1Crypto.decompressPublicKey(uint8ArrayFromString('42'))).to.throw()
|
||||
})
|
||||
|
||||
it('compresses/decompresses a valid public key', () => {
|
||||
const decompressed = secp256k1Crypto.decompressPublicKey(pubKey)
|
||||
expect(decompressed).to.exist()
|
||||
expect(decompressed.length).to.be.eql(65)
|
||||
const recompressed = secp256k1Crypto.compressPublicKey(decompressed)
|
||||
expect(recompressed).to.eql(pubKey)
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('loads a private key marshaled by go-libp2p-crypto', async () => {
|
||||
// we need to first extract the key data from the protobuf, which is
|
||||
// normally handled by js-libp2p-crypto
|
||||
const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
|
||||
expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1)
|
||||
|
||||
const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data)
|
||||
expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
|
||||
expect(key.bytes).to.eql(fixtures.privateKey)
|
||||
})
|
||||
|
||||
it('loads a public key marshaled by go-libp2p-crypto', () => {
|
||||
const decoded = keysPBM.PublicKey.decode(fixtures.publicKey)
|
||||
expect(decoded.Type).to.be.eql(keysPBM.KeyType.Secp256k1)
|
||||
|
||||
const key = secp256k1.unmarshalSecp256k1PublicKey(decoded.Data)
|
||||
expect(key).to.be.an.instanceof(secp256k1.Secp256k1PublicKey)
|
||||
expect(key.bytes).to.eql(fixtures.publicKey)
|
||||
})
|
||||
|
||||
it('generates the same signature as go-libp2p-crypto', async () => {
|
||||
const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
|
||||
expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1)
|
||||
|
||||
const key = await secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data)
|
||||
const sig = await key.sign(fixtures.message)
|
||||
expect(sig).to.eql(fixtures.signature)
|
||||
})
|
||||
})
|
3
test/node.js
Normal file
3
test/node.js
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
require('./keys/rsa-crypto-libs')
|
27
test/random-bytes.spec.js
Normal file
27
test/random-bytes.spec.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const randomBytes = require('../src/random-bytes')
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('produces random bytes', () => {
|
||||
expect(randomBytes(16)).to.have.length(16)
|
||||
})
|
||||
|
||||
it('throws if length is 0', () => {
|
||||
expect(() => randomBytes(0)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
|
||||
it('throws if length is < 0', () => {
|
||||
expect(() => randomBytes(-1)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
|
||||
it('throws if length is not a number', () => {
|
||||
expect(() => randomBytes('hi')).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
|
||||
})
|
||||
})
|
177
test/rsa.spec.js
177
test/rsa.spec.js
@@ -1,177 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const expect = require('chai').expect
|
||||
|
||||
const crypto = require('../src')
|
||||
const rsa = crypto.keys.rsa
|
||||
const fixtures = require('./fixtures/go-key-rsa')
|
||||
|
||||
describe('RSA', () => {
|
||||
let key
|
||||
before((done) => {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
|
||||
if (err) return done(err)
|
||||
key = _key
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a valid key', (done) => {
|
||||
expect(
|
||||
key
|
||||
).to.be.an.instanceof(
|
||||
rsa.RsaPrivateKey
|
||||
)
|
||||
|
||||
key.hash((err, digest) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(digest).to.have.length(34)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('signs', (done) => {
|
||||
const text = key.genSecret()
|
||||
|
||||
key.sign(text, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(text, sig, (err, res) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
expect(res).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('encoding', (done) => {
|
||||
const keyMarshal = key.marshal()
|
||||
rsa.unmarshalRsaPrivateKey(keyMarshal, (err, key2) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const keyMarshal2 = key2.marshal()
|
||||
|
||||
expect(
|
||||
keyMarshal
|
||||
).to.be.eql(
|
||||
keyMarshal2
|
||||
)
|
||||
|
||||
const pk = key.public
|
||||
const pkMarshal = pk.marshal()
|
||||
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
|
||||
const pkMarshal2 = pk2.marshal()
|
||||
|
||||
expect(
|
||||
pkMarshal
|
||||
).to.be.eql(
|
||||
pkMarshal2
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('key equals', () => {
|
||||
it('equals itself', () => {
|
||||
expect(
|
||||
key.equals(key)
|
||||
).to.be.eql(
|
||||
true
|
||||
)
|
||||
|
||||
expect(
|
||||
key.public.equals(key.public)
|
||||
).to.be.eql(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('not equals other key', (done) => {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, key2) => {
|
||||
if (err) return done(err)
|
||||
|
||||
expect(
|
||||
key.equals(key2)
|
||||
).to.be.eql(
|
||||
false
|
||||
)
|
||||
|
||||
expect(
|
||||
key2.equals(key)
|
||||
).to.be.eql(
|
||||
false
|
||||
)
|
||||
|
||||
expect(
|
||||
key.public.equals(key2.public)
|
||||
).to.be.eql(
|
||||
false
|
||||
)
|
||||
|
||||
expect(
|
||||
key2.public.equals(key.public)
|
||||
).to.be.eql(
|
||||
false
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sign and verify', (done) => {
|
||||
const data = new Buffer('hello world')
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(data, sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('fails to verify for different data', (done) => {
|
||||
const data = new Buffer('hello world')
|
||||
key.sign(data, (err, sig) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
key.public.verify(new Buffer('hello'), sig, (err, valid) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(valid).to.be.eql(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('go interop', () => {
|
||||
it('verifies with data from go', (done) => {
|
||||
const key = crypto.unmarshalPublicKey(fixtures.verify.publicKey)
|
||||
|
||||
key.verify(fixtures.verify.data, fixtures.verify.signature, (err, ok) => {
|
||||
if (err) throw err
|
||||
expect(err).to.not.exist
|
||||
expect(ok).to.be.eql(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
38
test/util.spec.js
Normal file
38
test/util.spec.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
require('node-forge/lib/jsbn')
|
||||
const forge = require('node-forge/lib/forge')
|
||||
const util = require('../src/util')
|
||||
|
||||
describe('Util', () => {
|
||||
let bn
|
||||
|
||||
before(() => {
|
||||
bn = new forge.jsbn.BigInteger('dead', 16)
|
||||
})
|
||||
|
||||
it('should convert BigInteger to a uint base64url encoded string', () => {
|
||||
expect(util.bigIntegerToUintBase64url(bn)).to.eql('3q0')
|
||||
})
|
||||
|
||||
it('should convert BigInteger to a uint base64url encoded string with padding', () => {
|
||||
const bnpad = new forge.jsbn.BigInteger('ff', 16)
|
||||
expect(util.bigIntegerToUintBase64url(bnpad, 2)).to.eql('AP8')
|
||||
})
|
||||
|
||||
it('should convert base64url encoded string to BigInteger', () => {
|
||||
const num = util.base64urlToBigInteger('3q0')
|
||||
expect(num.equals(bn)).to.be.true()
|
||||
})
|
||||
|
||||
it('should convert base64url encoded string to Uint8Array with padding', () => {
|
||||
const buf = util.base64urlToBuffer('AP8', 2)
|
||||
expect(Uint8Array.from([0, 255])).to.eql(buf)
|
||||
})
|
||||
})
|
21
test/util/index.js
Normal file
21
test/util/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {function(any, string): Promise<void>}
|
||||
*/
|
||||
const expectErrCode = async (p, code) => {
|
||||
try {
|
||||
await p
|
||||
} catch (err) {
|
||||
expect(err).to.have.property('code', code)
|
||||
return
|
||||
}
|
||||
expect.fail(`Expected error with code ${code} but no error thrown`)
|
||||
}
|
||||
|
||||
module.exports = { expectErrCode }
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"target": "ES5",
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"libp2p-crypto": [
|
||||
"./src",
|
||||
"../../src",
|
||||
"../src"
|
||||
]
|
||||
},
|
||||
"types": ["node", "mocha", "chai"],
|
||||
"noEmit": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": ["./src/index.d.ts",],
|
||||
"include": ["./test/**/*.spec.js"]
|
||||
}
|
Reference in New Issue
Block a user