mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-03-30 15:08:32 +00:00
Compare commits
86 Commits
v6.0.0-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
88310fe69d | ||
![]() |
c2526c8a88 | ||
![]() |
1848f51a4c | ||
![]() |
4762d2c762 | ||
![]() |
d5689894f6 | ||
![]() |
6d4a86295e | ||
![]() |
8a2062d342 | ||
![]() |
e9fe979649 | ||
![]() |
1ab6f27fc9 | ||
![]() |
a7660cc43b | ||
![]() |
b583bcad23 | ||
![]() |
a3a9e06802 | ||
![]() |
965e63b672 | ||
![]() |
96b13a468b | ||
![]() |
432856ff0e | ||
![]() |
b2bd8a0fdd | ||
![]() |
6db98f1e47 | ||
![]() |
8e5da78971 | ||
![]() |
a5d894f514 | ||
![]() |
a16160fc66 | ||
![]() |
abe750cf7c | ||
![]() |
2a8969b437 | ||
![]() |
bf85deedb8 | ||
![]() |
6c3b02872d | ||
![]() |
4d2d8740dc | ||
![]() |
bbdaad0cba | ||
![]() |
daeaf6b1da | ||
![]() |
67faffafff | ||
![]() |
f75447afaa | ||
![]() |
121b478312 | ||
![]() |
088d5f3638 | ||
![]() |
ac1bfc0d60 | ||
![]() |
287104aafb | ||
![]() |
2d65d1d553 | ||
![]() |
3f060660c2 | ||
![]() |
01b62399af | ||
![]() |
dd01ee00cb | ||
![]() |
a5645e1d6c | ||
![]() |
09800741f0 | ||
![]() |
31a7e2616b | ||
![]() |
42d504a69a | ||
![]() |
fb72ea449a | ||
![]() |
f9a3e54364 | ||
![]() |
d3e75de23d | ||
![]() |
12274a1543 | ||
![]() |
0138b69356 | ||
![]() |
821f260ba9 | ||
![]() |
09095ced4f | ||
![]() |
d7f5736d67 | ||
![]() |
693adb417e | ||
![]() |
013dffce70 | ||
![]() |
59c809c943 | ||
![]() |
4ddadd4f53 | ||
![]() |
ae5698c621 | ||
![]() |
4b017f6c67 | ||
![]() |
e924a50c31 | ||
![]() |
88f20974dd | ||
![]() |
05fbc63732 | ||
![]() |
3cdaab7894 | ||
![]() |
e58c02d5ee | ||
![]() |
a57bffc84a | ||
![]() |
5ee854140a | ||
![]() |
ada794cab6 | ||
![]() |
e80d71bdfc | ||
![]() |
e454faab0c | ||
![]() |
6ac17dc71c | ||
![]() |
148fff91e8 | ||
![]() |
ccb040ae96 | ||
![]() |
2b9a07e840 | ||
![]() |
0255fcba86 | ||
![]() |
f2818429db | ||
![]() |
8d8033383b | ||
![]() |
a3839f6db5 | ||
![]() |
1bcce67c68 | ||
![]() |
e7b7f6c6b1 | ||
![]() |
ab8445116c | ||
![]() |
f36be640cc | ||
![]() |
2f185481a7 | ||
![]() |
5fd7ef370f | ||
![]() |
79014f00f0 | ||
![]() |
5c583341d7 | ||
![]() |
db82968b48 | ||
![]() |
7698790d1c | ||
![]() |
bcaaa7e2d2 | ||
![]() |
fca699373a | ||
![]() |
efb0324330 |
33
.github/dependabot.yml
vendored
Normal file
33
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
# The redundant target-branch directive is needed to set two different update schedules for npm,
|
||||
# working around a dependabot limitation:
|
||||
# see https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219 .
|
||||
target-branch: main
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
allow:
|
||||
- dependency-name: "playwright"
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
allow:
|
||||
- dependency-name: "@noble*"
|
||||
- dependency-name: "fflate"
|
||||
versioning-strategy: increase
|
||||
groups:
|
||||
# Any packages matching the pattern @noble* where the highest resolvable
|
||||
# version is minor or patch will be grouped together.
|
||||
# Grouping rules apply to version updates only.
|
||||
noble:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@noble*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
3
.github/test-suite/config.json.template
vendored
3
.github/test-suite/config.json.template
vendored
@ -27,6 +27,9 @@
|
||||
},
|
||||
{
|
||||
"path": "__RNP_SOP__"
|
||||
},
|
||||
{
|
||||
"path": "__RSOP__"
|
||||
}
|
||||
],
|
||||
"rlimits": {
|
||||
|
3
.github/test-suite/prepare_config.sh
vendored
3
.github/test-suite/prepare_config.sh
vendored
@ -7,7 +7,8 @@ cat $CONFIG_TEMPLATE \
|
||||
| sed "s@__OPENPGPJS_MAIN__@${OPENPGPJS_MAIN}@g" \
|
||||
| sed "s@__SQOP__@${SQOP}@g" \
|
||||
| sed "s@__GPGME_SOP__@${GPGME_SOP}@g" \
|
||||
| sed "s@__GOSOP_V2__@${GOSOP_DIR_V2}/gosop@g" \
|
||||
| sed "s@__GOSOP_V2__@${GOSOP_V2}@g" \
|
||||
| sed "s@__SOP_OPENPGPJS__@${SOP_OPENPGPJS_V2}@g" \
|
||||
| sed "s@__RNP_SOP__@${RNP_SOP}@g" \
|
||||
| sed "s@__RSOP__@${RSOP}@g" \
|
||||
> $CONFIG_OUTPUT
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -2,7 +2,7 @@ name: Performance Regression Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, v6]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main, v6]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
4
.github/workflows/sop-test-suite.yml
vendored
4
.github/workflows/sop-test-suite.yml
vendored
@ -2,7 +2,7 @@ name: SOP interoperability test suite
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main, v6 ]
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
|
||||
@ -10,7 +10,7 @@ jobs:
|
||||
name: Run interoperability test suite
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.7
|
||||
image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.12
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.github_token }}
|
||||
|
41
.github/workflows/tests.yml
vendored
41
.github/workflows/tests.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main, v6]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build: # cache both dist and tests (non-lightweight only), based on commit hash
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false # if tests for one version fail, continue with the rest
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
node-version: [18.x, 20.x, 22.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
name: Node ${{ matrix.node-version }}
|
||||
@ -57,8 +57,15 @@ jobs:
|
||||
|
||||
test-browsers-latest:
|
||||
name: Browsers (latest)
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false # if tests for one version fail, continue with the rest
|
||||
matrix:
|
||||
# run on all main platforms to test platform-specific code, if present
|
||||
# (e.g. webkit's WebCrypto API implementation is different in macOS vs Linux)
|
||||
# TODO: windows-latest fails to fetch resources from the wtr server; investigate if the problem is with path declaration or permissions
|
||||
runner: ['ubuntu-latest', 'macos-latest']
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -79,17 +86,19 @@ jobs:
|
||||
npm pkg delete scripts.prepare
|
||||
npm ci
|
||||
|
||||
- name: Get Playwright version
|
||||
- name: Get Playwright version and cache location
|
||||
id: playwright-version
|
||||
run: |
|
||||
PLAYWRIGHT_VERSION=$(npm ls playwright | grep playwright | sed 's/.*@//')
|
||||
PLAYWRIGHT_VERSION=$(npm ls playwright --depth=0 | grep playwright | sed 's/.*@//')
|
||||
echo "version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
||||
PLAYWRIGHT_CACHE=${{ fromJSON('{"ubuntu-latest": "~/.cache/ms-playwright", "macos-latest": "~/Library/Caches/ms-playwright"}')[matrix.runner] }}
|
||||
echo "playwright_cache=$PLAYWRIGHT_CACHE" >> $GITHUB_OUTPUT
|
||||
- name: Check for cached browsers
|
||||
id: cache-playwright-browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-browsers-${{ steps.playwright-version.outputs.version }}
|
||||
path: ${{ steps.playwright-version.outputs.playwright_cache }}
|
||||
key: playwright-browsers-${{ matrix.runner }}-${{ steps.playwright-version.outputs.version }}
|
||||
- name: Install browsers
|
||||
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@ -97,15 +106,16 @@ jobs:
|
||||
npx playwright install --with-deps firefox
|
||||
|
||||
- name: Install WebKit # caching not possible, external shared libraries required
|
||||
if: ${{ matrix.runner == 'macos-latest' }} # do not install on ubuntu, since the X25519 WebCrypto implementation has issues
|
||||
run: npx playwright install --with-deps webkit
|
||||
|
||||
- name: Run browser tests
|
||||
run: npm run test-browser
|
||||
run: npm run test-browser:ci -- --static-logging
|
||||
|
||||
- name: Run browser tests (lightweight) # overwrite test/lib
|
||||
run: |
|
||||
npm run build-test --lightweight
|
||||
npm run test-browser
|
||||
npm run test-browser:ci -- --static-logging
|
||||
|
||||
test-browsers-compatibility:
|
||||
name: Browsers (older, on Browserstack)
|
||||
@ -119,6 +129,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
- name: Generate self-signed HTTPS certificates for web-test-runner server
|
||||
uses: kofemann/action-create-certificate@v0.0.4
|
||||
with:
|
||||
hostcert: '127.0.0.1.pem'
|
||||
hostkey: '127.0.0.1-key.pem'
|
||||
cachain: 'ca-chain.pem'
|
||||
- name: Adjust HTTPS certificates permissions
|
||||
run: sudo chown runner:docker *.pem
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
@ -139,12 +158,12 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run browserstack tests
|
||||
run: npm run test-browserstack
|
||||
run: npm run test-browserstack -- --static-logging
|
||||
|
||||
- name: Run browserstack tests (lightweight) # overwrite test/lib
|
||||
run: |
|
||||
npm run build-test --lightweight
|
||||
npm run test-browserstack
|
||||
npm run test-browserstack -- --static-logging
|
||||
env:
|
||||
LIGHTWEIGHT: true
|
||||
|
||||
|
86
README.md
86
README.md
@ -1,7 +1,7 @@
|
||||
OpenPGP.js [](https://automate.browserstack.com/public-build/N1l2eHFOanVBMU9wYWxJM3ZnWERnc1lidkt5UkRqa3BralV3SWVhOGpGTT0tLVljSjE4Z3dzVmdiQjl6RWgxb2c3T2c9PQ==--5864052cd523f751b6b907d547ac9c4c5f88c8a3) [](https://gitter.im/openpgpjs/openpgpjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
OpenPGP.js [](https://gitter.im/openpgpjs/openpgpjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
==========
|
||||
|
||||
[OpenPGP.js](https://openpgpjs.org/) is a JavaScript implementation of the OpenPGP protocol. It implements the [crypto-refresh](https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh) (superseding [RFC4880](https://tools.ietf.org/html/rfc4880) and [RFC4880bis](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10)).
|
||||
[OpenPGP.js](https://openpgpjs.org/) is a JavaScript implementation of the OpenPGP protocol. It implements [RFC 9580](https://datatracker.ietf.org/doc/rfc9580/) (superseding [RFC 4880](https://tools.ietf.org/html/rfc4880) and [RFC 4880bis](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10)).
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
@ -35,58 +35,60 @@ OpenPGP.js [ bundle works with recent versions of Chrome, Firefox, Edge and Safari 14+.
|
||||
|
||||
* The `dist/node/openpgp.min.mjs` (or `.cjs`) bundle works in Node.js v18+: it is used by default when you `import ... from 'openpgp'` (resp. `require('openpgp')`).
|
||||
* The `dist/node/openpgp.min.mjs` (or `.cjs`) bundle works in Node.js v18+: it is used by default when you `import ... from 'openpgp'` (or `require('openpgp')`, respectively).
|
||||
|
||||
* Support for the [Web Cryptography API](https://w3c.github.io/webcrypto/)'s `SubtleCrypto` is required.
|
||||
* In browsers, `SubtleCrypto` is only available in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
||||
* In supported versions of Node.js, `SubtleCrypto` is always available.
|
||||
|
||||
* Support for the [Web Streams API](https://streams.spec.whatwg.org/) is required.
|
||||
* In browsers: the latest versions of Chrome, Firefox, Edge and Safari support Streams, including `TransformStream`s.
|
||||
These are needed if you use the library with stream inputs.
|
||||
In previous versions of OpenPGP.js, Web Streams were automatically polyfilled by the library,
|
||||
but from v6 this task is left up to the library user, due to the more extensive browser support, and the
|
||||
polyfilling side-effects. If you're working with [older browsers versions which do not implement e.g. TransformStreams](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility), you can manually
|
||||
load the [Web Streams polyfill](https://github.com/MattiasBuelens/web-streams-polyfills).
|
||||
Please note that when you load the polyfills, the global `ReadableStream` property (if it exists) gets overwritten with the polyfill version.
|
||||
In some edge cases, you might need to use the native
|
||||
`ReadableStream` (for example when using it to create a `Response`
|
||||
object), in which case you should store a reference to it before loading
|
||||
the polyfills. There is also the [web-streams-adapter](https://github.com/MattiasBuelens/web-streams-adapter)
|
||||
library to convert back and forth between them.
|
||||
* In Node.js: OpenPGP.js v6 no longer supports native Node `Readable` streams in inputs, and instead expects (and outputs) [Node's Web Streams](https://nodejs.org/api/webstreams.html#class-readablestream). [Node v17+ includes utilities to convert from and to Web Streams](https://nodejs.org/api/stream.html#streamreadabletowebstreamreadable-options).
|
||||
|
||||
* Streaming support: the latest versions of Chrome, Firefox, Edge and Safari implement the
|
||||
[Streams specification](https://streams.spec.whatwg.org/), including `TransformStream`s.
|
||||
These are needed if you use the library with streamed inputs.
|
||||
In previous versions of OpenPGP.js, WebStreams were automatically polyfilled by the library,
|
||||
but from v6 this task is left up to the library user, due to the more extensive browser support, and the
|
||||
polyfilling side-effects. If you're working with [older browsers versions which do not implement e.g. TransformStreams](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), you can manually
|
||||
load [WebStream polyfill](https://github.com/MattiasBuelens/web-streams-polyfills).
|
||||
Please note that when you load the polyfills, the global `ReadableStream` property (if it exists) gets overwritten with the polyfill version.
|
||||
In some edge cases, you might need to use the native
|
||||
`ReadableStream` (for example when using it to create a `Response`
|
||||
object), in which case you should store a reference to it before loading
|
||||
the polyfills. There is also the [web-streams-adapter](https://github.com/MattiasBuelens/web-streams-adapter)
|
||||
library to convert back and forth between them.
|
||||
|
||||
### Performance
|
||||
|
||||
* Version 3.0.0 of the library introduces support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available. Elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported:
|
||||
* Version 3.0.0 of the library introduced support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available. Compared to RSA, elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported:
|
||||
|
||||
| Curve | Encryption | Signature | NodeCrypto | WebCrypto | Constant-Time |
|
||||
|:---------------:|:----------:|:---------:|:----------:|:---------:|:-----------------:|
|
||||
| curve25519 | ECDH | N/A | No | No | Algorithmically** |
|
||||
| ed25519 | N/A | EdDSA | No | No | Algorithmically** |
|
||||
| nistP256 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| nistP384 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| nistP521 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| brainpoolP256r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| brainpoolP384r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| secp256k1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| curve25519 | ECDH | N/A | No | No | Algorithmically |
|
||||
| ed25519 | N/A | EdDSA | No | Yes* | If native** |
|
||||
| nistP256 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| nistP384 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| nistP521 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| brainpoolP256r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| brainpoolP384r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| secp256k1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
|
||||
\* when available
|
||||
\** the curve25519 and ed25519 implementations are algorithmically constant-time, but may not be constant-time after optimizations of the JavaScript compiler
|
||||
\*** these curves are only constant-time if the underlying native implementation is available and constant-time
|
||||
\** these curves are only constant-time if the underlying native implementation is available and constant-time
|
||||
|
||||
* If the user's browser supports [native WebCrypto](https://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` API, this will be used. Under Node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used.
|
||||
* The platform's [native Web Crypto API](https://w3c.github.io/webcrypto/) is used for performance. On Node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is also used, in cases where it offers additional functionality.
|
||||
|
||||
* The library implements authenticated encryption (AEAD) as per the ["crypto refresh" draft standard](https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh) using AES-OCB, EAX, or GCM. This makes symmetric encryption faster on platforms with native implementations. However, since the specification is very recent and other OpenPGP implementations are in the process of adopting it, the feature is currently behind a flag. **Note: activating this setting can break compatibility with other OpenPGP implementations which have yet to implement the feature.** You can enable it by setting `openpgp.config.aeadProtect = true`.
|
||||
Note that this setting has a different effect from the one in OpenPGP.js v6, which implemented support for a provisional version of AEAD from [RFC4880bis](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10), which was modified in a later draft of the crypto refresh.
|
||||
* The library implements authenticated encryption (AEAD) as per [RFC 9580](https://datatracker.ietf.org/doc/rfc9580/) using AES-GCM, OCB, or EAX. This makes symmetric encryption faster on platforms with native implementations. However, since the specification is very recent and other OpenPGP implementations are in the process of adopting it, the feature is currently behind a flag. **Note: activating this setting can break compatibility with other OpenPGP implementations which have yet to implement the feature.** You can enable it by setting `openpgp.config.aeadProtect = true`.
|
||||
Note that this setting has a different effect from the one in OpenPGP.js v5, which implemented support for a provisional version of AEAD from [RFC 4880bis](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10), which was modified in RFC 9580.
|
||||
|
||||
You can change the AEAD mode by setting one of the following options:
|
||||
|
||||
```
|
||||
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.ocb; // Default (widest ecosystem support), non-native
|
||||
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.gcm; // Native in WebCrypto and Node.js
|
||||
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.gcm; // Default, native in WebCrypto and Node.js
|
||||
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.ocb; // Non-native, but supported across RFC 9580 implementations
|
||||
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.eax; // Native in Node.js
|
||||
```
|
||||
|
||||
* For environments that don't provide native crypto, the library falls back to [asm.js](https://caniuse.com/#feat=asmjs) AES and AEAD implementations.
|
||||
|
||||
|
||||
### Getting started
|
||||
|
||||
#### Node.js
|
||||
@ -387,14 +389,8 @@ Where the value can be any of:
|
||||
})();
|
||||
```
|
||||
|
||||
For more information on using ReadableStreams, see [the MDN Documentation on the
|
||||
Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).
|
||||
|
||||
You can also pass a [Node.js `Readable`
|
||||
stream](https://nodejs.org/api/stream.html#stream_class_stream_readable), in
|
||||
which case OpenPGP.js will return a Node.js `Readable` stream as well, which you
|
||||
can `.pipe()` to a `Writable` stream, for example.
|
||||
|
||||
For more information on using ReadableStreams (both in browsers and Node.js), see [the MDN Documentation on the
|
||||
Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) .
|
||||
|
||||
#### Streaming encrypt and decrypt *String* data with PGP keys
|
||||
|
||||
@ -668,7 +664,7 @@ To create your own build of the library, just run the following command after cl
|
||||
|
||||
npm install && npm test
|
||||
|
||||
For debugging browser errors, you can run `npm start` and open [`http://localhost:8080/test/unittests.html`](http://localhost:8080/test/unittests.html) in a browser, or run the following command:
|
||||
For debugging browser errors, run the following command:
|
||||
|
||||
npm run browsertest
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
154
docs/global.html
154
docs/global.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
400
docs/module-crypto_cipherMode.html
Normal file
400
docs/module-crypto_cipherMode.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
15
openpgp.d.ts
vendored
15
openpgp.d.ts
vendored
@ -94,7 +94,7 @@ export class PrivateKey extends PublicKey {
|
||||
public revoke(reason?: ReasonForRevocation, date?: Date, config?: Config): Promise<PrivateKey>;
|
||||
public isDecrypted(): boolean;
|
||||
public addSubkey(options: SubkeyOptions): Promise<PrivateKey>;
|
||||
public getDecryptionKeys(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<PrivateKey | Subkey>;
|
||||
public getDecryptionKeys(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<(PrivateKey | Subkey)[]>;
|
||||
public update(sourceKey: PublicKey, date?: Date, config?: Config): Promise<PrivateKey>;
|
||||
}
|
||||
|
||||
@ -340,6 +340,7 @@ interface Config {
|
||||
constantTimePKCS1Decryption: boolean;
|
||||
constantTimePKCS1DecryptionSupportedSymmetricAlgorithms: Set<enums.symmetric>;
|
||||
v6Keys: boolean;
|
||||
enableParsingV5Entities: boolean;
|
||||
preferredAEADAlgorithm: enums.aead;
|
||||
aeadChunkSizeByte: number;
|
||||
s2kType: enums.s2k.iterated | enums.s2k.argon2;
|
||||
@ -701,7 +702,7 @@ export type EllipticCurveName = 'ed25519Legacy' | 'curve25519Legacy' | 'nistP256
|
||||
interface GenerateKeyOptions {
|
||||
userIDs: MaybeArray<UserID>;
|
||||
passphrase?: string;
|
||||
type?: 'ecc' | 'rsa';
|
||||
type?: 'ecc' | 'rsa' | 'curve25519' | 'curve448';
|
||||
curve?: EllipticCurveName;
|
||||
rsaBits?: number;
|
||||
keyExpirationTime?: number;
|
||||
@ -712,14 +713,8 @@ interface GenerateKeyOptions {
|
||||
}
|
||||
export type KeyOptions = GenerateKeyOptions;
|
||||
|
||||
export interface SubkeyOptions {
|
||||
type?: 'ecc' | 'rsa';
|
||||
curve?: EllipticCurveName;
|
||||
rsaBits?: number;
|
||||
keyExpirationTime?: number;
|
||||
date?: Date;
|
||||
export interface SubkeyOptions extends Pick<GenerateKeyOptions, 'type' | 'curve' | 'rsaBits' | 'keyExpirationTime' | 'date' | 'config'> {
|
||||
sign?: boolean;
|
||||
config?: PartialConfig;
|
||||
}
|
||||
|
||||
export declare class KeyID {
|
||||
@ -921,6 +916,8 @@ export namespace enums {
|
||||
export enum aead {
|
||||
eax = 1,
|
||||
ocb = 2,
|
||||
gcm = 3,
|
||||
/** @deprecated use `gcm` instead */
|
||||
experimentalGCM = 100 // Private algorithm
|
||||
}
|
||||
|
||||
|
13845
package-lock.json
generated
13845
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
73
package.json
73
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openpgp",
|
||||
"description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.",
|
||||
"version": "6.0.0-beta.2",
|
||||
"version": "6.1.0",
|
||||
"license": "LGPL-3.0+",
|
||||
"homepage": "https://openpgpjs.org/",
|
||||
"engines": {
|
||||
@ -22,9 +22,9 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./openpgp.d.ts",
|
||||
"browser": "./dist/openpgp.min.mjs",
|
||||
"import": "./dist/node/openpgp.mjs",
|
||||
"require": "./dist/node/openpgp.min.cjs",
|
||||
"browser": "./dist/openpgp.min.mjs"
|
||||
"require": "./dist/node/openpgp.min.cjs"
|
||||
},
|
||||
"./lightweight": {
|
||||
"types": "./openpgp.d.ts",
|
||||
@ -49,35 +49,40 @@
|
||||
"test-type-definitions": "tsx test/typescript/definitions.ts",
|
||||
"benchmark-time": "node test/benchmarks/time.js",
|
||||
"benchmark-memory-usage": "node test/benchmarks/memory_usage.js",
|
||||
"start": "http-server",
|
||||
"prebrowsertest": "npm run build-test",
|
||||
"browsertest": "npm start -- -o test/unittests.html",
|
||||
"test-browser": "karma start test/karma.conf.cjs",
|
||||
"test-browserstack": "karma start test/karma.conf.cjs --browsers bs_safari_latest,bs_ios_14,bs_safari_14",
|
||||
"browsertest": "web-test-runner --config test/web-test-runner.config.js --group local --manual --open",
|
||||
"test-browser": "web-test-runner --config test/web-test-runner.config.js --group local --playwright --browsers chromium firefox webkit",
|
||||
"test-browser:ci": "web-test-runner --config test/web-test-runner.config.js --group headless:ci",
|
||||
"test-browserstack": "web-test-runner --config test/web-test-runner.browserstack.config.js",
|
||||
"coverage": "c8 npm test",
|
||||
"lint": "eslint .",
|
||||
"docs": "jsdoc --configure .jsdocrc.cjs --destination docs --recurse README.md src && printf '%s' 'docs.openpgpjs.org' > docs/CNAME",
|
||||
"preversion": "rm -rf dist docs node_modules && npm ci && npm test",
|
||||
"version": "npm run docs && git add -A docs",
|
||||
"postversion": "git push && git push --tags && npm publish"
|
||||
"postversion": "git push --follow-tags && npm publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@openpgp/asmcrypto.js": "^3.1.0",
|
||||
"@noble/ciphers": "^1.2.1",
|
||||
"@noble/curves": "^1.8.1",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@openpgp/jsdoc": "^3.6.11",
|
||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||
"@openpgp/tweetnacl": "^1.0.4-1",
|
||||
"@openpgp/web-stream-tools": "~0.1.3",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.8",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-replace": "^5.0.7",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@rollup/plugin-wasm": "^6.2.2",
|
||||
"@types/chai": "^4.3.16",
|
||||
"@typescript-eslint/parser": "^7.9.0",
|
||||
"@types/chai": "^4.3.19",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@web/test-runner": "^0.19.0",
|
||||
"@web/test-runner-browserstack": "^0.7.2",
|
||||
"@web/test-runner-mocha": "^0.9.0",
|
||||
"@web/test-runner-playwright": "^0.11.0",
|
||||
"argon2id": "^1.0.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"bn.js": "^5.2.1",
|
||||
@ -85,33 +90,29 @@
|
||||
"chai": "^4.4.1",
|
||||
"chai-as-promised": "^7.1.2",
|
||||
"eckey-utils": "^0.7.14",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-chai-friendly": "^0.7.4",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-unicorn": "^48.0.1",
|
||||
"fflate": "^0.7.4",
|
||||
"http-server": "^14.1.1",
|
||||
"karma": "^6.4.3",
|
||||
"karma-browserstack-launcher": "^1.6.0",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-firefox-launcher": "^2.1.3",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-webkit-launcher": "^2.4.0",
|
||||
"mocha": "^10.4.0",
|
||||
"playwright": "^1.44.0",
|
||||
"rollup": "^4.17.2",
|
||||
"sinon": "^17.0.1",
|
||||
"fflate": "^0.8.2",
|
||||
"mocha": "^10.7.3",
|
||||
"playwright": "^1.51.1",
|
||||
"rollup": "^4.24.2",
|
||||
"sinon": "^18.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.10.4",
|
||||
"typescript": "^5.5.2",
|
||||
"tslib": "^2.8.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3",
|
||||
"web-streams-polyfill": "^4.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@web/dev-server-core": "npm:@openpgp/wtr-dev-server-core@0.7.3-patch.1",
|
||||
"@web/test-runner-core": "npm:@openpgp/wtr-test-runner-core@0.13.4-patch.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/openpgpjs/openpgpjs"
|
||||
|
237
rollup.config.js
237
rollup.config.js
@ -23,13 +23,20 @@ const wasmOptions = {
|
||||
browser: { targetEnv: 'browser', maxFileSize: undefined } // always inlline (our wasm files are small)
|
||||
};
|
||||
|
||||
const getChunkFileName = (chunkInfo, extension) => {
|
||||
// index files result in chunks named simply 'index', so we rename them to include the package name
|
||||
if (chunkInfo.name === 'index') {
|
||||
const packageName = chunkInfo.facadeModuleId.split('/').at(-2); // assume index file is under the root folder
|
||||
return `${packageName}.${extension}`;
|
||||
const getChunkFileName = (chunkInfo, extension) => `[name].${extension}`;
|
||||
|
||||
/**
|
||||
* Dynamically imported modules which expose an index file as entrypoint end up with a chunk named `index`
|
||||
* by default. We want to preserve the module name instead.
|
||||
*/
|
||||
const setManualChunkName = chunkId => {
|
||||
if (chunkId.includes('seek-bzip')) {
|
||||
return 'seek-bzip';
|
||||
} else if (chunkId.includes('argon2id')) {
|
||||
return 'argon2id';
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return `[name].${extension}`;
|
||||
};
|
||||
|
||||
const banner =
|
||||
@ -50,112 +57,120 @@ const terserOptions = {
|
||||
}
|
||||
};
|
||||
|
||||
const nodeBuild = {
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ file: 'dist/node/openpgp.cjs', format: 'cjs', name: pkg.name, banner, intro },
|
||||
{ file: 'dist/node/openpgp.min.cjs', format: 'cjs', name: pkg.name, banner, intro, plugins: [terser(terserOptions)], sourcemap: true },
|
||||
{ file: 'dist/node/openpgp.mjs', format: 'es', banner, intro },
|
||||
{ file: 'dist/node/openpgp.min.mjs', format: 'es', banner, intro, plugins: [terser(terserOptions)], sourcemap: true }
|
||||
].map(options => ({ ...options, inlineDynamicImports: true })),
|
||||
plugins: [
|
||||
resolve({
|
||||
exportConditions: ['node'] // needed for resolution of noble-curves import of '@noble/crypto' in Node 18
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' }
|
||||
}),
|
||||
commonjs(),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`
|
||||
}),
|
||||
wasm(wasmOptions.node)
|
||||
]
|
||||
};
|
||||
|
||||
const fullBrowserBuild = {
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ file: 'dist/openpgp.js', format: 'iife', name: pkg.name, banner, intro },
|
||||
{ file: 'dist/openpgp.min.js', format: 'iife', name: pkg.name, banner, intro, plugins: [terser(terserOptions)], sourcemap: true },
|
||||
{ file: 'dist/openpgp.mjs', format: 'es', banner, intro },
|
||||
{ file: 'dist/openpgp.min.mjs', format: 'es', banner, intro, plugins: [terser(terserOptions)], sourcemap: true }
|
||||
].map(options => ({ ...options, inlineDynamicImports: true })),
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' } // to avoid js files being overwritten
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
};
|
||||
|
||||
const lightweightBrowserBuild = {
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ entryFileNames: 'openpgp.mjs', chunkFileNames: chunkInfo => getChunkFileName(chunkInfo, 'mjs') },
|
||||
{ entryFileNames: 'openpgp.min.mjs', chunkFileNames: chunkInfo => getChunkFileName(chunkInfo, 'min.mjs'), plugins: [terser(terserOptions)], sourcemap: true }
|
||||
].map(options => ({ ...options, dir: 'dist/lightweight', manualChunks: setManualChunkName, format: 'es', banner, intro })),
|
||||
preserveEntrySignatures: 'exports-only',
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/lightweight/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
};
|
||||
|
||||
const testBuild = {
|
||||
input: 'test/unittests.js',
|
||||
output: [
|
||||
{ file: 'test/lib/unittests-bundle.js', format: 'es', intro, sourcemap: true, inlineDynamicImports: true }
|
||||
],
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
plugins: [
|
||||
alias({
|
||||
entries: {
|
||||
openpgp: `./dist/${process.env.npm_config_lightweight ? 'lightweight/' : ''}openpgp.mjs`
|
||||
}
|
||||
}),
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './test/lib/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies),
|
||||
requireReturnsDefault: 'preferred'
|
||||
}),
|
||||
replace({
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
};
|
||||
|
||||
export default Object.assign([
|
||||
{
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ file: 'dist/openpgp.js', format: 'iife', name: pkg.name, banner, intro },
|
||||
{ file: 'dist/openpgp.min.js', format: 'iife', name: pkg.name, banner, intro, plugins: [terser(terserOptions)], sourcemap: true },
|
||||
{ file: 'dist/openpgp.mjs', format: 'es', banner, intro },
|
||||
{ file: 'dist/openpgp.min.mjs', format: 'es', banner, intro, plugins: [terser(terserOptions)], sourcemap: true }
|
||||
].map(options => ({ ...options, inlineDynamicImports: true })),
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' } // to avoid js files being overwritten
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ file: 'dist/node/openpgp.cjs', format: 'cjs', name: pkg.name, banner, intro },
|
||||
{ file: 'dist/node/openpgp.min.cjs', format: 'cjs', name: pkg.name, banner, intro, plugins: [terser(terserOptions)], sourcemap: true },
|
||||
{ file: 'dist/node/openpgp.mjs', format: 'es', banner, intro },
|
||||
{ file: 'dist/node/openpgp.min.mjs', format: 'es', banner, intro, plugins: [terser(terserOptions)], sourcemap: true }
|
||||
].map(options => ({ ...options, inlineDynamicImports: true })),
|
||||
plugins: [
|
||||
resolve({
|
||||
exportConditions: ['node'] // needed for resolution of noble-curves import of '@noble/crypto' in Node 18
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/tmp-ts' }
|
||||
}),
|
||||
commonjs(),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`
|
||||
}),
|
||||
wasm(wasmOptions.node)
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'src/index.js',
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
output: [
|
||||
{ dir: 'dist/lightweight', entryFileNames: 'openpgp.mjs', chunkFileNames: chunkInfo => getChunkFileName(chunkInfo, 'mjs'), format: 'es', banner, intro },
|
||||
{ dir: 'dist/lightweight', entryFileNames: 'openpgp.min.mjs', chunkFileNames: chunkInfo => getChunkFileName(chunkInfo, 'min.mjs'), format: 'es', banner, intro, plugins: [terser(terserOptions)], sourcemap: true }
|
||||
],
|
||||
preserveEntrySignatures: 'exports-only',
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './dist/lightweight/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies)
|
||||
}),
|
||||
replace({
|
||||
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'test/unittests.js',
|
||||
output: [
|
||||
{ file: 'test/lib/unittests-bundle.js', format: 'es', intro, sourcemap: true, inlineDynamicImports: true }
|
||||
],
|
||||
external: nodeBuiltinModules.concat(nodeDependencies),
|
||||
plugins: [
|
||||
alias({
|
||||
entries: {
|
||||
openpgp: `./dist/${process.env.npm_config_lightweight ? 'lightweight/' : ''}openpgp.mjs`
|
||||
}
|
||||
}),
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
compilerOptions: { outDir: './test/lib/tmp-ts' }
|
||||
}),
|
||||
commonjs({
|
||||
ignore: nodeBuiltinModules.concat(nodeDependencies),
|
||||
requireReturnsDefault: 'preferred'
|
||||
}),
|
||||
replace({
|
||||
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
|
||||
delimiters: ['', '']
|
||||
}),
|
||||
wasm(wasmOptions.browser)
|
||||
]
|
||||
}
|
||||
nodeBuild,
|
||||
fullBrowserBuild,
|
||||
lightweightBrowserBuild,
|
||||
testBuild
|
||||
].filter(config => {
|
||||
config.output = config.output.filter(output => {
|
||||
return (output.file || output.dir + '/' + output.entryFileNames).includes(
|
||||
|
@ -59,20 +59,22 @@ export class CleartextMessage {
|
||||
|
||||
/**
|
||||
* Sign the cleartext message
|
||||
* @param {Array<Key>} privateKeys - private keys with decrypted secret key data for signing
|
||||
* @param {Array<Key>} signingKeys - private keys with decrypted secret key data for signing
|
||||
* @param {Array<Key>} recipientKeys - recipient keys to get the signing preferences from
|
||||
* @param {Signature} [signature] - Any existing detached signature
|
||||
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to privateKeys[i]
|
||||
* @param {Date} [date] - The creation time of the signature that should be created
|
||||
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
|
||||
* @param {Array} [signingKeyIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
|
||||
* @param {Array} [recipientUserIDs] - User IDs associated with `recipientKeys` to get the signing preferences from
|
||||
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]
|
||||
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
||||
* @returns {Promise<CleartextMessage>} New cleartext message with signed content.
|
||||
* @async
|
||||
*/
|
||||
async sign(privateKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
|
||||
async sign(signingKeys, recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], config = defaultConfig) {
|
||||
const literalDataPacket = new LiteralDataPacket();
|
||||
literalDataPacket.setText(this.text);
|
||||
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, notations, true, config));
|
||||
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, date, signingUserIDs, recipientUserIDs, notations, true, config));
|
||||
return new CleartextMessage(this.text, newSignature);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ export default {
|
||||
* @memberof module:config
|
||||
* @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash}
|
||||
*/
|
||||
preferredHashAlgorithm: enums.hash.sha256,
|
||||
preferredHashAlgorithm: enums.hash.sha512,
|
||||
/**
|
||||
* @memberof module:config
|
||||
* @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric}
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/aes_kw
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { aeskw as nobleAesKW } from '@noble/ciphers/aes';
|
||||
import { getCipherParams } from './cipher';
|
||||
import util from '../util';
|
||||
|
||||
@ -55,7 +55,7 @@ export async function wrap(algo, key, dataToWrap) {
|
||||
util.printDebugError('Browser did not support operation: ' + err.message);
|
||||
}
|
||||
|
||||
return asmcryptoWrap(algo, key, dataToWrap);
|
||||
return nobleAesKW(key).encrypt(dataToWrap);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,7 +82,7 @@ export async function unwrap(algo, key, wrappedData) {
|
||||
throw err;
|
||||
}
|
||||
util.printDebugError('Browser did not support operation: ' + err.message);
|
||||
return asmcryptoUnwrap(algo, key, wrappedData);
|
||||
return nobleAesKW(key).decrypt(wrappedData);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -95,95 +95,3 @@ export async function unwrap(algo, key, wrappedData) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function asmcryptoWrap(aesAlgo, key, data) {
|
||||
const aesInstance = new AES_CBC(key, new Uint8Array(16), false);
|
||||
const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]);
|
||||
const P = unpack(data);
|
||||
let A = IV;
|
||||
const R = P;
|
||||
const n = P.length / 2;
|
||||
const t = new Uint32Array([0, 0]);
|
||||
let B = new Uint32Array(4);
|
||||
for (let j = 0; j <= 5; ++j) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
t[1] = n * j + (1 + i);
|
||||
// B = A
|
||||
B[0] = A[0];
|
||||
B[1] = A[1];
|
||||
// B = A || R[i]
|
||||
B[2] = R[2 * i];
|
||||
B[3] = R[2 * i + 1];
|
||||
// B = AES(K, B)
|
||||
B = unpack(aesInstance.encrypt(pack(B)));
|
||||
// A = MSB(64, B) ^ t
|
||||
A = B.subarray(0, 2);
|
||||
A[0] ^= t[0];
|
||||
A[1] ^= t[1];
|
||||
// R[i] = LSB(64, B)
|
||||
R[2 * i] = B[2];
|
||||
R[2 * i + 1] = B[3];
|
||||
}
|
||||
}
|
||||
return pack(A, R);
|
||||
}
|
||||
|
||||
function asmcryptoUnwrap(aesAlgo, key, data) {
|
||||
const aesInstance = new AES_CBC(key, new Uint8Array(16), false);
|
||||
const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]);
|
||||
const C = unpack(data);
|
||||
let A = C.subarray(0, 2);
|
||||
const R = C.subarray(2);
|
||||
const n = C.length / 2 - 1;
|
||||
const t = new Uint32Array([0, 0]);
|
||||
let B = new Uint32Array(4);
|
||||
for (let j = 5; j >= 0; --j) {
|
||||
for (let i = n - 1; i >= 0; --i) {
|
||||
t[1] = n * j + (i + 1);
|
||||
// B = A ^ t
|
||||
B[0] = A[0] ^ t[0];
|
||||
B[1] = A[1] ^ t[1];
|
||||
// B = (A ^ t) || R[i]
|
||||
B[2] = R[2 * i];
|
||||
B[3] = R[2 * i + 1];
|
||||
// B = AES-1(B)
|
||||
B = unpack(aesInstance.decrypt(pack(B)));
|
||||
// A = MSB(64, B)
|
||||
A = B.subarray(0, 2);
|
||||
// R[i] = LSB(64, B)
|
||||
R[2 * i] = B[2];
|
||||
R[2 * i + 1] = B[3];
|
||||
}
|
||||
}
|
||||
if (A[0] === IV[0] && A[1] === IV[1]) {
|
||||
return pack(R);
|
||||
}
|
||||
throw new Error('Key Data Integrity failed');
|
||||
}
|
||||
|
||||
function unpack(data) {
|
||||
const buffer = data.buffer;
|
||||
const view = new DataView(buffer);
|
||||
const arr = new Uint32Array(data.length / 4);
|
||||
for (let i = 0; i < data.length / 4; ++i) {
|
||||
arr[i] = view.getUint32(4 * i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function pack() {
|
||||
let length = 0;
|
||||
for (let k = 0; k < arguments.length; ++k) {
|
||||
length += 4 * arguments[k].length;
|
||||
}
|
||||
const buffer = new ArrayBuffer(length);
|
||||
const view = new DataView(buffer);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < arguments.length; ++i) {
|
||||
for (let j = 0; j < arguments[i].length; ++j) {
|
||||
view.setUint32(offset + 4 * j, arguments[i][j]);
|
||||
}
|
||||
offset += 4 * arguments[i].length;
|
||||
}
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ export async function getLegacyCipher(algo) {
|
||||
case enums.symmetric.twofish:
|
||||
case enums.symmetric.tripledes: {
|
||||
const { legacyCiphers } = await import('./legacy_ciphers');
|
||||
const cipher = legacyCiphers.get(algo);
|
||||
const algoName = enums.read(enums.symmetric, algo);
|
||||
const cipher = legacyCiphers.get(algoName);
|
||||
if (!cipher) {
|
||||
throw new Error('Unsupported cipher algorithm');
|
||||
}
|
||||
|
@ -3,15 +3,16 @@
|
||||
* Separate dynamic imports are not convenient as they result in multiple chunks.
|
||||
*/
|
||||
|
||||
import { TripleDES } from './des';
|
||||
import CAST5 from './cast5';
|
||||
import TwoFish from './twofish';
|
||||
import BlowFish from './blowfish';
|
||||
import enums from '../../enums';
|
||||
import { TripleDES as tripledes } from './des';
|
||||
import cast5 from './cast5';
|
||||
import twofish from './twofish';
|
||||
import blowfish from './blowfish';
|
||||
|
||||
export const legacyCiphers = new Map([
|
||||
[enums.symmetric.tripledes, TripleDES],
|
||||
[enums.symmetric.cast5, CAST5],
|
||||
[enums.symmetric.blowfish, BlowFish],
|
||||
[enums.symmetric.twofish, TwoFish]
|
||||
]);
|
||||
// We avoid importing 'enums' as this module is lazy loaded, and doing so could mess up
|
||||
// chunking for the lightweight build
|
||||
export const legacyCiphers = new Map(Object.entries({
|
||||
tripledes,
|
||||
cast5,
|
||||
twofish,
|
||||
blowfish
|
||||
}));
|
||||
|
@ -21,11 +21,13 @@
|
||||
* @module crypto/mode/cfb
|
||||
*/
|
||||
|
||||
import { AES_CFB } from '@openpgp/asmcrypto.js/aes/cfb.js';
|
||||
import * as stream from '@openpgp/web-stream-tools';
|
||||
import { cfb as nobleAesCfb, unsafe as nobleAesHelpers } from '@noble/ciphers/aes';
|
||||
|
||||
import { transform as streamTransform } from '@openpgp/web-stream-tools';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
import { getLegacyCipher, getCipherParams } from '../cipher';
|
||||
import { getRandomBytes } from '../random';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
@ -42,6 +44,20 @@ const nodeAlgos = {
|
||||
/* twofish is not implemented in OpenSSL */
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a random byte prefix for the specified algorithm
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms.
|
||||
* @param {module:enums.symmetric} algo - Symmetric encryption algorithm
|
||||
* @returns {Promise<Uint8Array>} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated.
|
||||
* @async
|
||||
*/
|
||||
export async function getPrefixRandom(algo) {
|
||||
const { blockSize } = getCipherParams(algo);
|
||||
const prefixrandom = await getRandomBytes(blockSize);
|
||||
const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]);
|
||||
return util.concat([prefixrandom, repeat]);
|
||||
}
|
||||
|
||||
/**
|
||||
* CFB encryption
|
||||
* @param {enums.symmetric} algo - block cipher algorithm
|
||||
@ -83,7 +99,7 @@ export async function encrypt(algo, key, plaintext, iv, config) {
|
||||
}
|
||||
return ciphertext.subarray(0, j);
|
||||
};
|
||||
return stream.transform(plaintext, process, process);
|
||||
return streamTransform(plaintext, process, process);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +142,7 @@ export async function decrypt(algo, key, ciphertext, iv) {
|
||||
}
|
||||
return plaintext.subarray(0, j);
|
||||
};
|
||||
return stream.transform(ciphertext, process, process);
|
||||
return streamTransform(ciphertext, process, process);
|
||||
}
|
||||
|
||||
class WebCryptoEncryptor {
|
||||
@ -174,17 +190,17 @@ class WebCryptoEncryptor {
|
||||
|
||||
const encryptedBlocks = await this._runCBC(toEncrypt);
|
||||
xorMut(encryptedBlocks, plaintext);
|
||||
this.prevBlock = encryptedBlocks.subarray(-this.blockSize).slice();
|
||||
this.prevBlock = encryptedBlocks.slice(-this.blockSize);
|
||||
|
||||
// take care of leftover data
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover).slice());
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover));
|
||||
this.i = leftover;
|
||||
|
||||
return encryptedBlocks;
|
||||
}
|
||||
|
||||
this.i += added.length;
|
||||
let encryptedBlock = new Uint8Array();
|
||||
let encryptedBlock;
|
||||
if (this.i === this.nextBlock.length) { // block ready to be encrypted
|
||||
const curBlock = this.nextBlock;
|
||||
encryptedBlock = await this._runCBC(this.prevBlock);
|
||||
@ -195,6 +211,8 @@ class WebCryptoEncryptor {
|
||||
const remaining = value.subarray(added.length);
|
||||
this.nextBlock.set(remaining, this.i);
|
||||
this.i += remaining.length;
|
||||
} else {
|
||||
encryptedBlock = new Uint8Array();
|
||||
}
|
||||
|
||||
return encryptedBlock;
|
||||
@ -237,22 +255,111 @@ class WebCryptoEncryptor {
|
||||
}
|
||||
}
|
||||
|
||||
async function aesEncrypt(algo, key, pt, iv) {
|
||||
if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys
|
||||
const cfb = new WebCryptoEncryptor(algo, key, iv);
|
||||
return util.isStream(pt) ? stream.transform(pt, value => cfb.encryptChunk(value), () => cfb.finish()) : cfb.encrypt(pt);
|
||||
} else {
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(pt, value => cfb.aes.AES_Encrypt_process(value), () => cfb.aes.AES_Encrypt_finish());
|
||||
class NobleStreamProcessor {
|
||||
constructor(forEncryption, algo, key, iv) {
|
||||
this.forEncryption = forEncryption;
|
||||
const { blockSize } = getCipherParams(algo);
|
||||
this.key = nobleAesHelpers.expandKeyLE(key);
|
||||
|
||||
if (iv.byteOffset % 4 !== 0) iv = iv.slice(); // aligned arrays required by noble-ciphers
|
||||
this.prevBlock = getUint32Array(iv);
|
||||
this.nextBlock = new Uint8Array(blockSize);
|
||||
this.i = 0; // pointer inside next block
|
||||
this.blockSize = blockSize;
|
||||
}
|
||||
|
||||
_runCFB(src) {
|
||||
const src32 = getUint32Array(src);
|
||||
const dst = new Uint8Array(src.length);
|
||||
const dst32 = getUint32Array(dst);
|
||||
for (let i = 0; i + 4 <= dst32.length; i += 4) {
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = nobleAesHelpers.encrypt(this.key, this.prevBlock[0], this.prevBlock[1], this.prevBlock[2], this.prevBlock[3]);
|
||||
dst32[i + 0] = src32[i + 0] ^ e0;
|
||||
dst32[i + 1] = src32[i + 1] ^ e1;
|
||||
dst32[i + 2] = src32[i + 2] ^ e2;
|
||||
dst32[i + 3] = src32[i + 3] ^ e3;
|
||||
this.prevBlock = (this.forEncryption ? dst32 : src32).slice(i, i + 4);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
async processChunk(value) {
|
||||
const missing = this.nextBlock.length - this.i;
|
||||
const added = value.subarray(0, missing);
|
||||
this.nextBlock.set(added, this.i);
|
||||
|
||||
if ((this.i + value.length) >= (2 * this.blockSize)) {
|
||||
const leftover = (value.length - missing) % this.blockSize;
|
||||
const toProcess = util.concatUint8Array([
|
||||
this.nextBlock,
|
||||
value.subarray(missing, value.length - leftover)
|
||||
]);
|
||||
|
||||
const processedBlocks = this._runCFB(toProcess);
|
||||
|
||||
// take care of leftover data
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover));
|
||||
this.i = leftover;
|
||||
|
||||
return processedBlocks;
|
||||
}
|
||||
|
||||
this.i += added.length;
|
||||
|
||||
let processedBlock;
|
||||
if (this.i === this.nextBlock.length) { // block ready to be encrypted
|
||||
processedBlock = this._runCFB(this.nextBlock);
|
||||
this.i = 0;
|
||||
|
||||
const remaining = value.subarray(added.length);
|
||||
this.nextBlock.set(remaining, this.i);
|
||||
this.i += remaining.length;
|
||||
} else {
|
||||
processedBlock = new Uint8Array();
|
||||
}
|
||||
|
||||
return processedBlock;
|
||||
}
|
||||
|
||||
async finish() {
|
||||
let result;
|
||||
if (this.i === 0) { // nothing more to encrypt
|
||||
result = new Uint8Array();
|
||||
} else {
|
||||
const processedBlock = this._runCFB(this.nextBlock);
|
||||
|
||||
result = processedBlock.subarray(0, this.i);
|
||||
}
|
||||
|
||||
this.clearSensitiveData();
|
||||
return result;
|
||||
}
|
||||
|
||||
clearSensitiveData() {
|
||||
this.nextBlock.fill(0);
|
||||
this.prevBlock.fill(0);
|
||||
this.key.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
function aesDecrypt(algo, key, ct, iv) {
|
||||
if (util.isStream(ct)) {
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(ct, value => cfb.aes.AES_Decrypt_process(value), () => cfb.aes.AES_Decrypt_finish());
|
||||
|
||||
async function aesEncrypt(algo, key, pt, iv) {
|
||||
if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys
|
||||
const cfb = new WebCryptoEncryptor(algo, key, iv);
|
||||
return util.isStream(pt) ? streamTransform(pt, value => cfb.encryptChunk(value), () => cfb.finish()) : cfb.encrypt(pt);
|
||||
} else if (util.isStream(pt)) { // async callbacks are not accepted by streamTransform unless the input is a stream
|
||||
const cfb = new NobleStreamProcessor(true, algo, key, iv);
|
||||
return streamTransform(pt, value => cfb.processChunk(value), () => cfb.finish());
|
||||
}
|
||||
return AES_CFB.decrypt(ct, key, iv);
|
||||
return nobleAesCfb(key, iv).encrypt(pt);
|
||||
}
|
||||
|
||||
async function aesDecrypt(algo, key, ct, iv) {
|
||||
if (util.isStream(ct)) {
|
||||
const cfb = new NobleStreamProcessor(false, algo, key, iv);
|
||||
return streamTransform(ct, value => cfb.processChunk(value), () => cfb.finish());
|
||||
}
|
||||
return nobleAesCfb(key, iv).decrypt(ct);
|
||||
}
|
||||
|
||||
function xorMut(a, b) {
|
||||
@ -262,14 +369,16 @@ function xorMut(a, b) {
|
||||
}
|
||||
}
|
||||
|
||||
const getUint32Array = arr => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
|
||||
function nodeEncrypt(algo, key, pt, iv) {
|
||||
const algoName = enums.read(enums.symmetric, algo);
|
||||
const cipherObj = new nodeCrypto.createCipheriv(nodeAlgos[algoName], key, iv);
|
||||
return stream.transform(pt, value => new Uint8Array(cipherObj.update(value)));
|
||||
return streamTransform(pt, value => new Uint8Array(cipherObj.update(value)));
|
||||
}
|
||||
|
||||
function nodeDecrypt(algo, key, ct, iv) {
|
||||
const algoName = enums.read(enums.symmetric, algo);
|
||||
const decipherObj = new nodeCrypto.createDecipheriv(nodeAlgos[algoName], key, iv);
|
||||
return stream.transform(ct, value => new Uint8Array(decipherObj.update(value)));
|
||||
return streamTransform(ct, value => new Uint8Array(decipherObj.update(value)));
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/mode/eax
|
||||
*/
|
||||
|
||||
import { AES_CTR } from '@openpgp/asmcrypto.js/aes/ctr.js';
|
||||
import { ctr as nobleAesCtr } from '@noble/ciphers/aes';
|
||||
import CMAC from '../cmac';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
@ -72,9 +72,8 @@ async function CTR(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// asm.js fallback
|
||||
return async function(pt, iv) {
|
||||
return AES_CTR.encrypt(pt, key, iv);
|
||||
return nobleAesCtr(key, iv).encrypt(pt);
|
||||
};
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/mode/gcm
|
||||
*/
|
||||
|
||||
import { AES_GCM } from '@openpgp/asmcrypto.js/aes/gcm.js';
|
||||
import { gcm as nobleAesGcm } from '@noble/ciphers/aes';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
|
||||
@ -74,7 +74,7 @@ async function GCM(cipher, key) {
|
||||
return {
|
||||
encrypt: async function(pt, iv, adata = new Uint8Array()) {
|
||||
if (webcryptoEmptyMessagesUnsupported && !pt.length) {
|
||||
return AES_GCM.encrypt(pt, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).encrypt(pt);
|
||||
}
|
||||
const ct = await webCrypto.encrypt({ name: ALGO, iv, additionalData: adata, tagLength: tagLength * 8 }, _key, pt);
|
||||
return new Uint8Array(ct);
|
||||
@ -82,7 +82,7 @@ async function GCM(cipher, key) {
|
||||
|
||||
decrypt: async function(ct, iv, adata = new Uint8Array()) {
|
||||
if (webcryptoEmptyMessagesUnsupported && ct.length === tagLength) {
|
||||
return AES_GCM.decrypt(ct, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).decrypt(ct);
|
||||
}
|
||||
try {
|
||||
const pt = await webCrypto.decrypt({ name: ALGO, iv, additionalData: adata, tagLength: tagLength * 8 }, _key, ct);
|
||||
@ -106,11 +106,11 @@ async function GCM(cipher, key) {
|
||||
|
||||
return {
|
||||
encrypt: async function(pt, iv, adata) {
|
||||
return AES_GCM.encrypt(pt, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).encrypt(pt);
|
||||
},
|
||||
|
||||
decrypt: async function(ct, iv, adata) {
|
||||
return AES_GCM.decrypt(ct, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).decrypt(ct);
|
||||
}
|
||||
};
|
||||
}
|
35
src/crypto/cipherMode/index.js
Normal file
35
src/crypto/cipherMode/index.js
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @fileoverview Cipher modes
|
||||
* @module crypto/cipherMode
|
||||
*/
|
||||
|
||||
export * as cfb from './cfb';
|
||||
import eax from './eax';
|
||||
import ocb from './ocb';
|
||||
import gcm from './gcm';
|
||||
import enums from '../../enums';
|
||||
|
||||
/**
|
||||
* Get implementation of the given AEAD mode
|
||||
* @param {enums.aead} algo
|
||||
* @param {Boolean} [acceptExperimentalGCM] - whether to allow the non-standard, legacy `experimentalGCM` algo
|
||||
* @returns {Object}
|
||||
* @throws {Error} on invalid algo
|
||||
*/
|
||||
export function getAEADMode(algo, acceptExperimentalGCM = false) {
|
||||
switch (algo) {
|
||||
case enums.aead.eax:
|
||||
return eax;
|
||||
case enums.aead.ocb:
|
||||
return ocb;
|
||||
case enums.aead.gcm:
|
||||
return gcm;
|
||||
case enums.aead.experimentalGCM:
|
||||
if (!acceptExperimentalGCM) {
|
||||
throw new Error('Unexpected non-standard `experimentalGCM` AEAD algorithm provided in `config.preferredAEADAlgorithm`: use `gcm` instead');
|
||||
}
|
||||
return gcm;
|
||||
default:
|
||||
throw new Error('Unsupported AEAD mode');
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@
|
||||
* @module crypto/mode/ocb
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { cbc as nobleAesCbc } from '@noble/ciphers/aes';
|
||||
import { getCipherParams } from '../cipher';
|
||||
import util from '../../util';
|
||||
|
||||
@ -73,8 +73,8 @@ async function OCB(cipher, key) {
|
||||
// `encipher` and `decipher` cannot be async, since `crypt` shares state across calls,
|
||||
// hence its execution cannot be broken up.
|
||||
// As a result, WebCrypto cannot currently be used for `encipher`.
|
||||
const encipher = block => AES_CBC.encrypt(block, key, false);
|
||||
const decipher = block => AES_CBC.decrypt(block, key, false);
|
||||
const encipher = block => nobleAesCbc(key, zeroBlock, { disablePadding: true }).encrypt(block);
|
||||
const decipher = block => nobleAesCbc(key, zeroBlock, { disablePadding: true }).decrypt(block);
|
||||
let mask;
|
||||
|
||||
constructKeyVariables(cipher, key);
|
@ -4,7 +4,7 @@
|
||||
* @module crypto/cmac
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { cbc as nobleAesCbc } from '@noble/ciphers/aes';
|
||||
import util from '../util';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
@ -97,8 +97,7 @@ async function CBC(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// asm.js fallback
|
||||
return async function(pt) {
|
||||
return AES_CBC.encrypt(pt, key, false, zeroBlock);
|
||||
return nobleAesCbc(key, zeroBlock, { disablePadding: true }).encrypt(pt);
|
||||
};
|
||||
}
|
||||
|
@ -23,8 +23,7 @@
|
||||
* @module crypto/crypto
|
||||
*/
|
||||
|
||||
import publicKey from './public_key';
|
||||
import mode from './mode';
|
||||
import { rsa, elliptic, elgamal, dsa } from './public_key';
|
||||
import { getRandomBytes } from './random';
|
||||
import { getCipherParams } from './cipher';
|
||||
import ECDHSymkey from '../type/ecdh_symkey';
|
||||
@ -51,16 +50,16 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, dat
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign: {
|
||||
const { n, e } = publicParams;
|
||||
const c = await publicKey.rsa.encrypt(data, n, e);
|
||||
const c = await rsa.encrypt(data, n, e);
|
||||
return { c };
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
const { p, g, y } = publicParams;
|
||||
return publicKey.elgamal.encrypt(data, p, g, y);
|
||||
return elgamal.encrypt(data, p, g, y);
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
const { oid, Q, kdfParams } = publicParams;
|
||||
const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt(
|
||||
const { publicKey: V, wrappedKey: C } = await elliptic.ecdh.encrypt(
|
||||
oid, kdfParams, data, Q, fingerprint);
|
||||
return { V, C: new ECDHSymkey(C) };
|
||||
}
|
||||
@ -71,7 +70,7 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, dat
|
||||
throw new Error('X25519 and X448 keys can only encrypt AES session keys');
|
||||
}
|
||||
const { A } = publicParams;
|
||||
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt(
|
||||
const { ephemeralPublicKey, wrappedKey } = await elliptic.ecdhX.encrypt(
|
||||
keyAlgo, data, A);
|
||||
const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey });
|
||||
return { ephemeralPublicKey, C };
|
||||
@ -102,19 +101,19 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
|
||||
const { c } = sessionKeyParams;
|
||||
const { n, e } = publicKeyParams;
|
||||
const { d, p, q, u } = privateKeyParams;
|
||||
return publicKey.rsa.decrypt(c, n, e, d, p, q, u, randomPayload);
|
||||
return rsa.decrypt(c, n, e, d, p, q, u, randomPayload);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
const { c1, c2 } = sessionKeyParams;
|
||||
const p = publicKeyParams.p;
|
||||
const x = privateKeyParams.x;
|
||||
return publicKey.elgamal.decrypt(c1, c2, p, x, randomPayload);
|
||||
return elgamal.decrypt(c1, c2, p, x, randomPayload);
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
const { oid, Q, kdfParams } = publicKeyParams;
|
||||
const { d } = privateKeyParams;
|
||||
const { V, C } = sessionKeyParams;
|
||||
return publicKey.elliptic.ecdh.decrypt(
|
||||
return elliptic.ecdh.decrypt(
|
||||
oid, kdfParams, V, C.data, Q, d, fingerprint);
|
||||
}
|
||||
case enums.publicKey.x25519:
|
||||
@ -125,7 +124,7 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
|
||||
if (C.algorithm !== null && !util.isAES(C.algorithm)) {
|
||||
throw new Error('AES session key expected');
|
||||
}
|
||||
return publicKey.elliptic.ecdhX.decrypt(
|
||||
return elliptic.ecdhX.decrypt(
|
||||
algo, ephemeralPublicKey, C.wrappedKey, A, k);
|
||||
}
|
||||
default:
|
||||
@ -338,22 +337,22 @@ export function generateParams(algo, bits, oid) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaSign:
|
||||
return publicKey.rsa.generate(bits, 65537).then(({ n, e, d, p, q, u }) => ({
|
||||
return rsa.generate(bits, 65537).then(({ n, e, d, p, q, u }) => ({
|
||||
privateParams: { d, p, q, u },
|
||||
publicParams: { n, e }
|
||||
}));
|
||||
case enums.publicKey.ecdsa:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
return elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
privateParams: { d: secret },
|
||||
publicParams: { oid: new OID(oid), Q }
|
||||
}));
|
||||
case enums.publicKey.eddsaLegacy:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
return elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
privateParams: { seed: secret },
|
||||
publicParams: { oid: new OID(oid), Q }
|
||||
}));
|
||||
case enums.publicKey.ecdh:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret, hash, cipher }) => ({
|
||||
return elliptic.generate(oid).then(({ oid, Q, secret, hash, cipher }) => ({
|
||||
privateParams: { d: secret },
|
||||
publicParams: {
|
||||
oid: new OID(oid),
|
||||
@ -363,13 +362,13 @@ export function generateParams(algo, bits, oid) {
|
||||
}));
|
||||
case enums.publicKey.ed25519:
|
||||
case enums.publicKey.ed448:
|
||||
return publicKey.elliptic.eddsa.generate(algo).then(({ A, seed }) => ({
|
||||
return elliptic.eddsa.generate(algo).then(({ A, seed }) => ({
|
||||
privateParams: { seed },
|
||||
publicParams: { A }
|
||||
}));
|
||||
case enums.publicKey.x25519:
|
||||
case enums.publicKey.x448:
|
||||
return publicKey.elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
|
||||
return elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
|
||||
privateParams: { k },
|
||||
publicParams: { A }
|
||||
}));
|
||||
@ -399,21 +398,21 @@ export async function validateParams(algo, publicParams, privateParams) {
|
||||
case enums.publicKey.rsaSign: {
|
||||
const { n, e } = publicParams;
|
||||
const { d, p, q, u } = privateParams;
|
||||
return publicKey.rsa.validateParams(n, e, d, p, q, u);
|
||||
return rsa.validateParams(n, e, d, p, q, u);
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
const { p, q, g, y } = publicParams;
|
||||
const { x } = privateParams;
|
||||
return publicKey.dsa.validateParams(p, q, g, y, x);
|
||||
return dsa.validateParams(p, q, g, y, x);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
const { p, g, y } = publicParams;
|
||||
const { x } = privateParams;
|
||||
return publicKey.elgamal.validateParams(p, g, y, x);
|
||||
return elgamal.validateParams(p, g, y, x);
|
||||
}
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.ecdh: {
|
||||
const algoModule = publicKey.elliptic[enums.read(enums.publicKey, algo)];
|
||||
const algoModule = elliptic[enums.read(enums.publicKey, algo)];
|
||||
const { oid, Q } = publicParams;
|
||||
const { d } = privateParams;
|
||||
return algoModule.validateParams(oid, Q, d);
|
||||
@ -421,39 +420,25 @@ export async function validateParams(algo, publicParams, privateParams) {
|
||||
case enums.publicKey.eddsaLegacy: {
|
||||
const { Q, oid } = publicParams;
|
||||
const { seed } = privateParams;
|
||||
return publicKey.elliptic.eddsaLegacy.validateParams(oid, Q, seed);
|
||||
return elliptic.eddsaLegacy.validateParams(oid, Q, seed);
|
||||
}
|
||||
case enums.publicKey.ed25519:
|
||||
case enums.publicKey.ed448: {
|
||||
const { A } = publicParams;
|
||||
const { seed } = privateParams;
|
||||
return publicKey.elliptic.eddsa.validateParams(algo, A, seed);
|
||||
return elliptic.eddsa.validateParams(algo, A, seed);
|
||||
}
|
||||
case enums.publicKey.x25519:
|
||||
case enums.publicKey.x448: {
|
||||
const { A } = publicParams;
|
||||
const { k } = privateParams;
|
||||
return publicKey.elliptic.ecdhX.validateParams(algo, A, k);
|
||||
return elliptic.ecdhX.validateParams(algo, A, k);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown public key algorithm.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random byte prefix for the specified algorithm
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms.
|
||||
* @param {module:enums.symmetric} algo - Symmetric encryption algorithm
|
||||
* @returns {Promise<Uint8Array>} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated.
|
||||
* @async
|
||||
*/
|
||||
export async function getPrefixRandom(algo) {
|
||||
const { blockSize } = getCipherParams(algo);
|
||||
const prefixrandom = await getRandomBytes(blockSize);
|
||||
const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]);
|
||||
return util.concat([prefixrandom, repeat]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating a session key for the specified symmetric algorithm
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms.
|
||||
@ -465,17 +450,6 @@ export function generateSessionKey(algo) {
|
||||
return getRandomBytes(keySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get implementation of the given AEAD mode
|
||||
* @param {enums.aead} algo
|
||||
* @returns {Object}
|
||||
* @throws {Error} on invalid algo
|
||||
*/
|
||||
export function getAEADMode(algo) {
|
||||
const algoName = enums.read(enums.aead, algo);
|
||||
return mode[algoName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given curve OID is supported
|
||||
* @param {module:type/oid} oid - EC object identifier
|
||||
@ -499,13 +473,13 @@ export function getCurvePayloadSize(algo, oid) {
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.ecdh:
|
||||
case enums.publicKey.eddsaLegacy:
|
||||
return new publicKey.elliptic.CurveWithOID(oid).payloadSize;
|
||||
return new elliptic.CurveWithOID(oid).payloadSize;
|
||||
case enums.publicKey.ed25519:
|
||||
case enums.publicKey.ed448:
|
||||
return publicKey.elliptic.eddsa.getPayloadSize(algo);
|
||||
return elliptic.eddsa.getPayloadSize(algo);
|
||||
case enums.publicKey.x25519:
|
||||
case enums.publicKey.x448:
|
||||
return publicKey.elliptic.ecdhX.getPayloadSize(algo);
|
||||
return elliptic.ecdhX.getPayloadSize(algo);
|
||||
default:
|
||||
throw new Error('Unknown elliptic algo');
|
||||
}
|
||||
@ -520,14 +494,12 @@ export function getPreferredCurveHashAlgo(algo, oid) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.eddsaLegacy:
|
||||
return publicKey.elliptic.getPreferredHashAlgo(oid);
|
||||
return elliptic.getPreferredHashAlgo(oid);
|
||||
case enums.publicKey.ed25519:
|
||||
case enums.publicKey.ed448:
|
||||
return publicKey.elliptic.eddsa.getPreferredHashAlgo(algo);
|
||||
return elliptic.eddsa.getPreferredHashAlgo(algo);
|
||||
default:
|
||||
throw new Error('Unknown elliptic signing algo');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export { getCipherParams };
|
||||
|
@ -5,8 +5,7 @@
|
||||
* @module crypto/hash
|
||||
*/
|
||||
|
||||
import * as stream from '@openpgp/web-stream-tools';
|
||||
import md5 from './md5';
|
||||
import { transform as streamTransform, isArrayStream, readToEnd as streamReadToEnd } from '@openpgp/web-stream-tools';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
|
||||
@ -20,7 +19,7 @@ function nodeHash(type) {
|
||||
}
|
||||
return async function (data) {
|
||||
const shasum = nodeCrypto.createHash(type);
|
||||
return stream.transform(data, value => {
|
||||
return streamTransform(data, value => {
|
||||
shasum.update(value);
|
||||
}, () => new Uint8Array(shasum.digest()));
|
||||
};
|
||||
@ -35,14 +34,14 @@ function nobleHash(nobleHashName, webCryptoHashName) {
|
||||
};
|
||||
|
||||
return async function(data) {
|
||||
if (stream.isArrayStream(data)) {
|
||||
data = await stream.readToEnd(data);
|
||||
if (isArrayStream(data)) {
|
||||
data = await streamReadToEnd(data);
|
||||
}
|
||||
if (util.isStream(data)) {
|
||||
const hash = await getNobleHash();
|
||||
|
||||
const hashInstance = hash.create();
|
||||
return stream.transform(data, value => {
|
||||
return streamTransform(data, value => {
|
||||
hashInstance.update(value);
|
||||
}, () => hashInstance.digest());
|
||||
} else if (webCrypto && webCryptoHashName) {
|
||||
@ -55,76 +54,72 @@ function nobleHash(nobleHashName, webCryptoHashName) {
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
const md5 = nodeHash('md5') || nobleHash('md5');
|
||||
const sha1 = nodeHash('sha1') || nobleHash('sha1', 'SHA-1');
|
||||
const sha224 = nodeHash('sha224') || nobleHash('sha224');
|
||||
const sha256 = nodeHash('sha256') || nobleHash('sha256', 'SHA-256');
|
||||
const sha384 = nodeHash('sha384') || nobleHash('sha384', 'SHA-384');
|
||||
const sha512 = nodeHash('sha512') || nobleHash('sha512', 'SHA-512');
|
||||
const ripemd = nodeHash('ripemd160') || nobleHash('ripemd160');
|
||||
const sha3_256 = nodeHash('sha3-256') || nobleHash('sha3_256');
|
||||
const sha3_512 = nodeHash('sha3-512') || nobleHash('sha3_512');
|
||||
|
||||
/** @see module:md5 */
|
||||
md5: nodeHash('md5') || md5,
|
||||
sha1: nodeHash('sha1') || nobleHash('sha1', 'SHA-1'),
|
||||
sha224: nodeHash('sha224') || nobleHash('sha224'),
|
||||
sha256: nodeHash('sha256') || nobleHash('sha256', 'SHA-256'),
|
||||
sha384: nodeHash('sha384') || nobleHash('sha384', 'SHA-384'),
|
||||
sha512: nodeHash('sha512') || nobleHash('sha512', 'SHA-512'),
|
||||
ripemd: nodeHash('ripemd160') || nobleHash('ripemd160'),
|
||||
sha3_256: nodeHash('sha3-256') || nobleHash('sha3_256'),
|
||||
sha3_512: nodeHash('sha3-512') || nobleHash('sha3_512'),
|
||||
|
||||
/**
|
||||
* Create a hash on the specified data using the specified algorithm
|
||||
* @param {module:enums.hash} algo - Hash algorithm type (see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
|
||||
* @param {Uint8Array} data - Data to be hashed
|
||||
* @returns {Promise<Uint8Array>} Hash value.
|
||||
*/
|
||||
digest: function(algo, data) {
|
||||
switch (algo) {
|
||||
case enums.hash.md5:
|
||||
return this.md5(data);
|
||||
case enums.hash.sha1:
|
||||
return this.sha1(data);
|
||||
case enums.hash.ripemd:
|
||||
return this.ripemd(data);
|
||||
case enums.hash.sha256:
|
||||
return this.sha256(data);
|
||||
case enums.hash.sha384:
|
||||
return this.sha384(data);
|
||||
case enums.hash.sha512:
|
||||
return this.sha512(data);
|
||||
case enums.hash.sha224:
|
||||
return this.sha224(data);
|
||||
case enums.hash.sha3_256:
|
||||
return this.sha3_256(data);
|
||||
case enums.hash.sha3_512:
|
||||
return this.sha3_512(data);
|
||||
default:
|
||||
throw new Error('Unsupported hash function');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the hash size in bytes of the specified hash algorithm type
|
||||
* @param {module:enums.hash} algo - Hash algorithm type (See {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
|
||||
* @returns {Integer} Size in bytes of the resulting hash.
|
||||
*/
|
||||
getHashByteLength: function(algo) {
|
||||
switch (algo) {
|
||||
case enums.hash.md5:
|
||||
return 16;
|
||||
case enums.hash.sha1:
|
||||
case enums.hash.ripemd:
|
||||
return 20;
|
||||
case enums.hash.sha256:
|
||||
return 32;
|
||||
case enums.hash.sha384:
|
||||
return 48;
|
||||
case enums.hash.sha512:
|
||||
return 64;
|
||||
case enums.hash.sha224:
|
||||
return 28;
|
||||
case enums.hash.sha3_256:
|
||||
return 32;
|
||||
case enums.hash.sha3_512:
|
||||
return 64;
|
||||
default:
|
||||
throw new Error('Invalid hash algorithm.');
|
||||
}
|
||||
/**
|
||||
* Create a hash on the specified data using the specified algorithm
|
||||
* @param {module:enums.hash} algo - Hash algorithm type (see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
|
||||
* @param {Uint8Array} data - Data to be hashed
|
||||
* @returns {Promise<Uint8Array>} Hash value.
|
||||
*/
|
||||
export function computeDigest(algo, data) {
|
||||
switch (algo) {
|
||||
case enums.hash.md5:
|
||||
return md5(data);
|
||||
case enums.hash.sha1:
|
||||
return sha1(data);
|
||||
case enums.hash.ripemd:
|
||||
return ripemd(data);
|
||||
case enums.hash.sha256:
|
||||
return sha256(data);
|
||||
case enums.hash.sha384:
|
||||
return sha384(data);
|
||||
case enums.hash.sha512:
|
||||
return sha512(data);
|
||||
case enums.hash.sha224:
|
||||
return sha224(data);
|
||||
case enums.hash.sha3_256:
|
||||
return sha3_256(data);
|
||||
case enums.hash.sha3_512:
|
||||
return sha3_512(data);
|
||||
default:
|
||||
throw new Error('Unsupported hash function');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash size in bytes of the specified hash algorithm type
|
||||
* @param {module:enums.hash} algo - Hash algorithm type (See {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
|
||||
* @returns {Integer} Size in bytes of the resulting hash.
|
||||
*/
|
||||
export function getHashByteLength(algo) {
|
||||
switch (algo) {
|
||||
case enums.hash.md5:
|
||||
return 16;
|
||||
case enums.hash.sha1:
|
||||
case enums.hash.ripemd:
|
||||
return 20;
|
||||
case enums.hash.sha256:
|
||||
return 32;
|
||||
case enums.hash.sha384:
|
||||
return 48;
|
||||
case enums.hash.sha512:
|
||||
return 64;
|
||||
case enums.hash.sha224:
|
||||
return 28;
|
||||
case enums.hash.sha3_256:
|
||||
return 32;
|
||||
case enums.hash.sha3_512:
|
||||
return 64;
|
||||
default:
|
||||
throw new Error('Invalid hash algorithm.');
|
||||
}
|
||||
}
|
||||
|
@ -1,201 +0,0 @@
|
||||
/**
|
||||
* A fast MD5 JavaScript implementation
|
||||
* Copyright (c) 2012 Joseph Myers
|
||||
* http://www.myersdaily.org/joseph/javascript/md5-text.html
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software
|
||||
* and its documentation for any purposes and without
|
||||
* fee is hereby granted provided that this copyright notice
|
||||
* appears in all copies.
|
||||
*
|
||||
* Of course, this soft is provided "as is" without express or implied
|
||||
* warranty of any kind.
|
||||
*/
|
||||
|
||||
import util from '../../util';
|
||||
|
||||
// MD5 Digest
|
||||
async function md5(entree) {
|
||||
const digest = md51(util.uint8ArrayToString(entree));
|
||||
return util.hexToUint8Array(hex(digest));
|
||||
}
|
||||
|
||||
function md5cycle(x, k) {
|
||||
let a = x[0];
|
||||
let b = x[1];
|
||||
let c = x[2];
|
||||
let d = x[3];
|
||||
|
||||
a = ff(a, b, c, d, k[0], 7, -680876936);
|
||||
d = ff(d, a, b, c, k[1], 12, -389564586);
|
||||
c = ff(c, d, a, b, k[2], 17, 606105819);
|
||||
b = ff(b, c, d, a, k[3], 22, -1044525330);
|
||||
a = ff(a, b, c, d, k[4], 7, -176418897);
|
||||
d = ff(d, a, b, c, k[5], 12, 1200080426);
|
||||
c = ff(c, d, a, b, k[6], 17, -1473231341);
|
||||
b = ff(b, c, d, a, k[7], 22, -45705983);
|
||||
a = ff(a, b, c, d, k[8], 7, 1770035416);
|
||||
d = ff(d, a, b, c, k[9], 12, -1958414417);
|
||||
c = ff(c, d, a, b, k[10], 17, -42063);
|
||||
b = ff(b, c, d, a, k[11], 22, -1990404162);
|
||||
a = ff(a, b, c, d, k[12], 7, 1804603682);
|
||||
d = ff(d, a, b, c, k[13], 12, -40341101);
|
||||
c = ff(c, d, a, b, k[14], 17, -1502002290);
|
||||
b = ff(b, c, d, a, k[15], 22, 1236535329);
|
||||
|
||||
a = gg(a, b, c, d, k[1], 5, -165796510);
|
||||
d = gg(d, a, b, c, k[6], 9, -1069501632);
|
||||
c = gg(c, d, a, b, k[11], 14, 643717713);
|
||||
b = gg(b, c, d, a, k[0], 20, -373897302);
|
||||
a = gg(a, b, c, d, k[5], 5, -701558691);
|
||||
d = gg(d, a, b, c, k[10], 9, 38016083);
|
||||
c = gg(c, d, a, b, k[15], 14, -660478335);
|
||||
b = gg(b, c, d, a, k[4], 20, -405537848);
|
||||
a = gg(a, b, c, d, k[9], 5, 568446438);
|
||||
d = gg(d, a, b, c, k[14], 9, -1019803690);
|
||||
c = gg(c, d, a, b, k[3], 14, -187363961);
|
||||
b = gg(b, c, d, a, k[8], 20, 1163531501);
|
||||
a = gg(a, b, c, d, k[13], 5, -1444681467);
|
||||
d = gg(d, a, b, c, k[2], 9, -51403784);
|
||||
c = gg(c, d, a, b, k[7], 14, 1735328473);
|
||||
b = gg(b, c, d, a, k[12], 20, -1926607734);
|
||||
|
||||
a = hh(a, b, c, d, k[5], 4, -378558);
|
||||
d = hh(d, a, b, c, k[8], 11, -2022574463);
|
||||
c = hh(c, d, a, b, k[11], 16, 1839030562);
|
||||
b = hh(b, c, d, a, k[14], 23, -35309556);
|
||||
a = hh(a, b, c, d, k[1], 4, -1530992060);
|
||||
d = hh(d, a, b, c, k[4], 11, 1272893353);
|
||||
c = hh(c, d, a, b, k[7], 16, -155497632);
|
||||
b = hh(b, c, d, a, k[10], 23, -1094730640);
|
||||
a = hh(a, b, c, d, k[13], 4, 681279174);
|
||||
d = hh(d, a, b, c, k[0], 11, -358537222);
|
||||
c = hh(c, d, a, b, k[3], 16, -722521979);
|
||||
b = hh(b, c, d, a, k[6], 23, 76029189);
|
||||
a = hh(a, b, c, d, k[9], 4, -640364487);
|
||||
d = hh(d, a, b, c, k[12], 11, -421815835);
|
||||
c = hh(c, d, a, b, k[15], 16, 530742520);
|
||||
b = hh(b, c, d, a, k[2], 23, -995338651);
|
||||
|
||||
a = ii(a, b, c, d, k[0], 6, -198630844);
|
||||
d = ii(d, a, b, c, k[7], 10, 1126891415);
|
||||
c = ii(c, d, a, b, k[14], 15, -1416354905);
|
||||
b = ii(b, c, d, a, k[5], 21, -57434055);
|
||||
a = ii(a, b, c, d, k[12], 6, 1700485571);
|
||||
d = ii(d, a, b, c, k[3], 10, -1894986606);
|
||||
c = ii(c, d, a, b, k[10], 15, -1051523);
|
||||
b = ii(b, c, d, a, k[1], 21, -2054922799);
|
||||
a = ii(a, b, c, d, k[8], 6, 1873313359);
|
||||
d = ii(d, a, b, c, k[15], 10, -30611744);
|
||||
c = ii(c, d, a, b, k[6], 15, -1560198380);
|
||||
b = ii(b, c, d, a, k[13], 21, 1309151649);
|
||||
a = ii(a, b, c, d, k[4], 6, -145523070);
|
||||
d = ii(d, a, b, c, k[11], 10, -1120210379);
|
||||
c = ii(c, d, a, b, k[2], 15, 718787259);
|
||||
b = ii(b, c, d, a, k[9], 21, -343485551);
|
||||
|
||||
x[0] = add32(a, x[0]);
|
||||
x[1] = add32(b, x[1]);
|
||||
x[2] = add32(c, x[2]);
|
||||
x[3] = add32(d, x[3]);
|
||||
}
|
||||
|
||||
function cmn(q, a, b, x, s, t) {
|
||||
a = add32(add32(a, q), add32(x, t));
|
||||
return add32((a << s) | (a >>> (32 - s)), b);
|
||||
}
|
||||
|
||||
function ff(a, b, c, d, x, s, t) {
|
||||
return cmn((b & c) | ((~b) & d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function gg(a, b, c, d, x, s, t) {
|
||||
return cmn((b & d) | (c & (~d)), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function hh(a, b, c, d, x, s, t) {
|
||||
return cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
}
|
||||
|
||||
function ii(a, b, c, d, x, s, t) {
|
||||
return cmn(c ^ (b | (~d)), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function md51(s) {
|
||||
const n = s.length;
|
||||
const state = [1732584193, -271733879, -1732584194, 271733878];
|
||||
let i;
|
||||
for (i = 64; i <= s.length; i += 64) {
|
||||
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
||||
}
|
||||
s = s.substring(i - 64);
|
||||
const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for (i = 0; i < s.length; i++) {
|
||||
tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
|
||||
}
|
||||
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
|
||||
if (i > 55) {
|
||||
md5cycle(state, tail);
|
||||
for (i = 0; i < 16; i++) {
|
||||
tail[i] = 0;
|
||||
}
|
||||
}
|
||||
tail[14] = n * 8;
|
||||
md5cycle(state, tail);
|
||||
return state;
|
||||
}
|
||||
|
||||
/* there needs to be support for Unicode here,
|
||||
* unless we pretend that we can redefine the MD-5
|
||||
* algorithm for multi-byte characters (perhaps
|
||||
* by adding every four 16-bit characters and
|
||||
* shortening the sum to 32 bits). Otherwise
|
||||
* I suggest performing MD-5 as if every character
|
||||
* was two bytes--e.g., 0040 0025 = @%--but then
|
||||
* how will an ordinary MD-5 sum be matched?
|
||||
* There is no way to standardize text to something
|
||||
* like UTF-8 before transformation; speed cost is
|
||||
* utterly prohibitive. The JavaScript standard
|
||||
* itself needs to look at this: it should start
|
||||
* providing access to strings as preformed UTF-8
|
||||
* 8-bit unsigned value arrays.
|
||||
*/
|
||||
function md5blk(s) { /* I figured global was faster. */
|
||||
const md5blks = [];
|
||||
let i; /* Andy King said do it this way. */
|
||||
for (i = 0; i < 64; i += 4) {
|
||||
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) <<
|
||||
24);
|
||||
}
|
||||
return md5blks;
|
||||
}
|
||||
|
||||
const hex_chr = '0123456789abcdef'.split('');
|
||||
|
||||
function rhex(n) {
|
||||
let s = '';
|
||||
let j = 0;
|
||||
for (; j < 4; j++) {
|
||||
s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function hex(x) {
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
x[i] = rhex(x[i]);
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
/* this function is much faster,
|
||||
so if possible we use it. Some IEs
|
||||
are the only ones I know of that
|
||||
need the idiotic second function,
|
||||
generated by an if clause. */
|
||||
|
||||
function add32(a, b) {
|
||||
return (a + b) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
export default md5;
|
80
src/crypto/hash/md5.ts
Normal file
80
src/crypto/hash/md5.ts
Normal file
@ -0,0 +1,80 @@
|
||||
// Copied from https://github.com/paulmillr/noble-hashes/blob/main/test/misc/md5.ts
|
||||
|
||||
import { HashMD } from '@noble/hashes/_md';
|
||||
import { rotl, wrapConstructor } from '@noble/hashes/utils';
|
||||
|
||||
// Per-round constants
|
||||
const K = Array.from({ length: 64 }, (_, i) => Math.floor(2 ** 32 * Math.abs(Math.sin(i + 1))));
|
||||
// Choice: a ? b : c
|
||||
const Chi = (a: number, b: number, c: number) => (a & b) ^ (~a & c);
|
||||
// Initial state (same as sha1, but 4 u32 instead of 5)
|
||||
const IV = /* @__PURE__ */ new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
|
||||
// Temporary buffer, not used to store anything between runs
|
||||
// Named this way for SHA1 compat
|
||||
const MD5_W = /* @__PURE__ */ new Uint32Array(16);
|
||||
class MD5 extends HashMD<MD5> {
|
||||
private A = IV[0] | 0;
|
||||
private B = IV[1] | 0;
|
||||
private C = IV[2] | 0;
|
||||
private D = IV[3] | 0;
|
||||
constructor() {
|
||||
super(64, 16, 8, true);
|
||||
}
|
||||
protected get(): [number, number, number, number] {
|
||||
const { A, B, C, D } = this;
|
||||
return [A, B, C, D];
|
||||
}
|
||||
protected set(A: number, B: number, C: number, D: number) {
|
||||
this.A = A | 0;
|
||||
this.B = B | 0;
|
||||
this.C = C | 0;
|
||||
this.D = D | 0;
|
||||
}
|
||||
protected process(view: DataView, offset: number): void {
|
||||
for (let i = 0; i < 16; i++, offset += 4) MD5_W[i] = view.getUint32(offset, true);
|
||||
// Compression function main loop, 64 rounds
|
||||
let { A, B, C, D } = this;
|
||||
for (let i = 0; i < 64; i++) {
|
||||
// eslint-disable-next-line one-var, one-var-declaration-per-line
|
||||
let F, g, s;
|
||||
if (i < 16) {
|
||||
// eslint-disable-next-line new-cap
|
||||
F = Chi(B, C, D);
|
||||
g = i;
|
||||
s = [7, 12, 17, 22];
|
||||
} else if (i < 32) {
|
||||
// eslint-disable-next-line new-cap
|
||||
F = Chi(D, B, C);
|
||||
g = (5 * i + 1) % 16;
|
||||
s = [5, 9, 14, 20];
|
||||
} else if (i < 48) {
|
||||
F = B ^ C ^ D;
|
||||
g = (3 * i + 5) % 16;
|
||||
s = [4, 11, 16, 23];
|
||||
} else {
|
||||
F = C ^ (B | ~D);
|
||||
g = (7 * i) % 16;
|
||||
s = [6, 10, 15, 21];
|
||||
}
|
||||
F = F + A + K[i] + MD5_W[g];
|
||||
A = D;
|
||||
D = C;
|
||||
C = B;
|
||||
B = B + rotl(F, s[i % 4]);
|
||||
}
|
||||
// Add the compressed chunk to the current hash value
|
||||
A = (A + this.A) | 0;
|
||||
B = (B + this.B) | 0;
|
||||
C = (C + this.C) | 0;
|
||||
D = (D + this.D) | 0;
|
||||
this.set(A, B, C, D);
|
||||
}
|
||||
protected roundClean() {
|
||||
MD5_W.fill(0);
|
||||
}
|
||||
destroy() {
|
||||
this.set(0, 0, 0, 0);
|
||||
this.buffer.fill(0);
|
||||
}
|
||||
}
|
||||
export const md5 = /* @__PURE__ */ wrapConstructor(() => new MD5());
|
@ -9,8 +9,10 @@ import { sha224, sha256 } from '@noble/hashes/sha256';
|
||||
import { sha384, sha512 } from '@noble/hashes/sha512';
|
||||
import { sha3_256, sha3_512 } from '@noble/hashes/sha3';
|
||||
import { ripemd160 } from '@noble/hashes/ripemd160';
|
||||
import { md5 } from './md5';
|
||||
|
||||
export const nobleHashes = new Map(Object.entries({
|
||||
md5,
|
||||
sha1,
|
||||
sha224,
|
||||
sha256,
|
||||
|
@ -9,39 +9,10 @@
|
||||
* @module crypto
|
||||
*/
|
||||
|
||||
import * as cipher from './cipher';
|
||||
import hash from './hash';
|
||||
import mode from './mode';
|
||||
import publicKey from './public_key';
|
||||
import * as signature from './signature';
|
||||
import * as random from './random';
|
||||
import * as pkcs1 from './pkcs1';
|
||||
import * as pkcs5 from './pkcs5';
|
||||
import * as crypto from './crypto';
|
||||
import * as aesKW from './aes_kw';
|
||||
|
||||
// TODO move cfb and gcm to cipher
|
||||
const mod = {
|
||||
/** @see module:crypto/cipher */
|
||||
cipher: cipher,
|
||||
/** @see module:crypto/hash */
|
||||
hash: hash,
|
||||
/** @see module:crypto/mode */
|
||||
mode: mode,
|
||||
/** @see module:crypto/public_key */
|
||||
publicKey: publicKey,
|
||||
/** @see module:crypto/signature */
|
||||
signature: signature,
|
||||
/** @see module:crypto/random */
|
||||
random: random,
|
||||
/** @see module:crypto/pkcs1 */
|
||||
pkcs1: pkcs1,
|
||||
/** @see module:crypto/pkcs5 */
|
||||
pkcs5: pkcs5,
|
||||
/** @see module:crypto/aes_kw */
|
||||
aesKW: aesKW
|
||||
};
|
||||
|
||||
Object.assign(mod, crypto);
|
||||
|
||||
export default mod;
|
||||
export * from './crypto';
|
||||
export { getCipherParams } from './cipher';
|
||||
export * from './hash';
|
||||
export * as cipherMode from './cipherMode';
|
||||
export * as publicKey from './public_key';
|
||||
export * as signature from './signature';
|
||||
export { getRandomBytes } from './random';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user