Compare commits

...

72 Commits

Author SHA1 Message Date
dependabot[bot]
88310fe69d
Bump playwright from 1.51.0 to 1.51.1 (#1834)
Bumps [playwright](https://github.com/microsoft/playwright) from 1.51.0 to 1.51.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.51.0...v1.51.1)

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 13:56:54 +01:00
dependabot[bot]
c2526c8a88
Tests: bump playwright from 1.50.1 to 1.51.0 (#1831)
Bumps [playwright](https://github.com/microsoft/playwright) from 1.50.1 to 1.51.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.50.1...v1.51.0)

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-07 15:53:39 +01:00
larabr
1848f51a4c
Merge pull request #1829
Re-enable using WebCrypto for X25519 when available.
2025-03-05 11:51:40 +01:00
larabr
4762d2c762 CI: do not test Webkit on Linux
The tests work correctly in Epiphany, but not in the WebKit build,
where the native X25519 implementation throws non-standard errors on
importKey (DataError) and generateKey (OperationError).
Patching this would be simply a matter of catching such errors and falling back
to the JS implementation, but since only the CI WebKit build seems to be
affected, we prefer not to relax fallback checks in the context of crypto
operations without issues reported in the wild.
2025-02-26 13:00:14 +01:00
larabr
d5689894f6 Re-enable using WebCrypto for X25519 when available
Reverting commit ccb040ae96acd127a29161ffaf3b82b5b18c062f .
Firefox has fixed support in v132 (https://bugzilla.mozilla.org/show_bug.cgi?id=1918354)
usage of v130 and 131, which have a broken implementation, is now below 1%.

Also, Chrome has released support in v133.
2025-02-26 12:15:17 +01:00
Carlos Alexandro Becker
6d4a86295e
Make Issuer Key ID signature subpacket non-critical (#1828)
RPM <=4.16 does not support it.

See also:
- https://github.com/ProtonMail/go-crypto/pull/175
- https://github.com/ProtonMail/go-crypto/issues/263
2025-02-26 10:19:25 +01:00
dependabot[bot]
8a2062d342
Bump the noble group with 3 updates (#1825)
Bumps the noble group with 3 updates: [@noble/ciphers](https://github.com/paulmillr/noble-ciphers), [@noble/curves](https://github.com/paulmillr/noble-curves) and [@noble/hashes](https://github.com/paulmillr/noble-hashes).

Also:
* Internal: OCB: do not reuse AES-CBC instance (Noble is now preventing instance reuse).
* Tests: update error message following noble-curve change


Updates `@noble/ciphers` from 1.0.0 to 1.2.1
- [Release notes](https://github.com/paulmillr/noble-ciphers/releases)
- [Commits](https://github.com/paulmillr/noble-ciphers/compare/1.0.0...1.2.1)

Updates `@noble/curves` from 1.6.0 to 1.8.1
- [Release notes](https://github.com/paulmillr/noble-curves/releases)
- [Commits](https://github.com/paulmillr/noble-curves/compare/1.6.0...1.8.1)

Updates `@noble/hashes` from 1.5.0 to 1.7.1
- [Release notes](https://github.com/paulmillr/noble-hashes/releases)
- [Commits](https://github.com/paulmillr/noble-hashes/compare/1.5.0...1.7.1)

---

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: larabr <7375870+larabr@users.noreply.github.com>
2025-02-12 13:20:20 +01:00
dependabot[bot]
e9fe979649
Bump fflate from 0.7.4 to 0.8.2 (#1826)
Bumps [fflate](https://github.com/101arrowz/fflate) from 0.7.4 to 0.8.2.
- [Release notes](https://github.com/101arrowz/fflate/releases)
- [Changelog](https://github.com/101arrowz/fflate/blob/master/CHANGELOG.md)
- [Commits](https://github.com/101arrowz/fflate/compare/v0.7.4...v0.8.2)

---
updated-dependencies:
- dependency-name: fflate
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 13:59:09 +01:00
dependabot[bot]
1ab6f27fc9
Bump playwright from 1.48.2 to 1.50.1 (#1824)
Bumps [playwright](https://github.com/microsoft/playwright) from 1.48.2 to 1.50.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.48.2...v1.50.1)

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 13:27:41 +01:00
larabr
a7660cc43b CI: fix (again) Dependabot setup: add workaround to set different schedules for npm updates 2025-02-11 11:38:06 +01:00
larabr
b583bcad23
CI: fix Dependabot setup (#1823)
Typo in filename resulting in bot not actually working.
2025-02-10 19:20:03 +01:00
larabr
a3a9e06802
CI: add reconnection mechanism for Browserstack on testsStartTimeout (#1822)
iOS tests sometimes fail to start due to some "server disconnect" issue on BS side.
This seems more prominent on certain devices (e.g. iPhone 16 with iOS 18).
So, we also change the 'iOS latest' target to a more stable one.
2025-02-10 19:15:43 +01:00
Daniel Huigens
965e63b672
Only push new tags when running npm version (#1821) 2025-02-03 14:27:39 +01:00
Daniel Huigens
96b13a468b
6.1.0 2025-01-30 14:15:35 +01:00
Daniel Huigens
432856ff0e
Fix signing using keys without preferred hash algorithms (#1820) 2025-01-29 16:45:32 +01:00
larabr
b2bd8a0fdd
Merge pull request #1812
Improve internal tree-shaking and lazy load md5
2024-11-25 11:13:15 +01:00
larabr
6db98f1e47 Internal: improve tree-shaking in armor module 2024-11-22 14:34:18 +01:00
larabr
8e5da78971 Internal: improve tree-shaking of web-stream-tools
Import single functions instead of entire lib.
2024-11-22 14:34:18 +01:00
larabr
a5d894f514 Internal: avoid importing enums in legacy_cipher chunk
To avoid issues with the lightweight build:
for now it works fine, but it could mess up chunking in the future,
and it already results in a circular import.
2024-11-22 14:34:18 +01:00
larabr
a16160fc66 Use noble-hashes for md5
The existing md5 module brought in the util module,
which messed up the chunking structure in the lightweight build;
inlining those functions is an option, but the noble-hashes code
is also more modern and readable.
2024-11-22 14:32:42 +01:00
larabr
abe750cf7c Lightweight build: lazy load md5 hashing module
Used by old, legacy messages only
2024-11-22 14:32:42 +01:00
larabr
2a8969b437 Internal: improve tree-shaking for crypto modules
Every submodule under the 'crypto' directory was exported-imported
even if a handful of functions where actually needed.
We now only export entire modules behind default exports if it makes
sense for readability and if the different submodules would be
imported together anyway (e.g. `cipherMode` exports are all needed
by the SEIPD class).

We've also dropped exports that are not used outside of the crypto modules,
e.g. pkcs5 helpers.
2024-11-22 14:32:39 +01:00
larabr
bf85deedb8
Merge pull request #1811 2024-11-22 14:30:41 +01:00
larabr
6c3b02872d Throw on encryption using non-standard experimentalGCM AEAD algo
The `enums.aead.gcm` ID standardized by RFC9580 should be used instead.
2024-11-22 14:29:14 +01:00
larabr
4d2d8740dc Fix decryption support for non-standard, legacy AEAD messages and keys that used experimentalGCM
This adds back support for decrypting password-protected messages which
were encrypted in OpenPGP.js v5 with custom config settings
`config.aeadProtect = true` together with
`config.preferredAEADAlgorithm = openpgp.enums.aead.experimentalGCM`.

Public-key-encrypted messages are affected if they were encrypted using the same config, while also providing `encryptionKeys` that declared `experimentalGCM` in their AEAD prefs.
Such keys could be generated in OpenPGP.js v5 by setting the aforementioned config values.
2024-11-22 10:15:20 +01:00
larabr
bbdaad0cba TS: add gcm to enums.aead, mark non-standard experimentalGCM as deprecated
`experimentalGCM` should not be used anymore,
as a different a different algorithm ID was standardized
for GCM, and using the experimental value could give
interoperability issues with e.g. SEIPDv2 and AEAD-encrypted keys.
2024-11-22 10:15:20 +01:00
larabr
daeaf6b1da CI: disable Browserstack concurrency to improve reliability 2024-11-21 18:11:10 +01:00
larabr
67faffafff 6.0.1 2024-11-21 17:16:29 +01:00
larabr
f75447afaa Fix ES imports for webpack: declare exports.browser entrypoint as higher priority than import
We could also drop the browser's directive `"./dist/node/openpgp.min.cjs": "./dist/openpgp.min.js"`,
since that build cannot be used with `require()`, and it's instead meant
to be the target of <script> tags.
But we keep it around for now to avoid potentially breaking changes, in case it's
used in some setups.
2024-11-21 16:43:15 +01:00
larabr
121b478312 Tests: drop unused, unnecessary error assertion
The `expect().to.not.throw` check as written is a no-op.
In fact, `throw` should have been called as a function.

We drop the relevant check altogether since if the wrapped
operation throws, the test will naturally fail due to the
unexpected error.
2024-11-13 19:44:06 +01:00
larabr
088d5f3638
Merge pull request #1807 2024-11-11 20:46:51 +01:00
Daniel Huigens
ac1bfc0d60
Fix openpgp.verify/decrypt with expectSigned: true and format: 'binary' (#1805) 2024-11-11 15:42:33 +01:00
larabr
287104aafb TS: fix PrivateKey.getDecryptionKeys() return type 2024-11-11 14:20:18 +01:00
larabr
2d65d1d553 TS: generateKey: fix options.type definitions to accept 'curve25519' and 'curve448' 2024-11-11 13:28:05 +01:00
Daniel Huigens
3f060660c2
Update hash algorithm preferences order (#1804)
Prefer SHA3_512 over SHA3_256 for consistency.
2024-11-07 15:19:20 +01:00
larabr
01b62399af Revert "CI: temporarily enable for PRs to v6 branch" [skip ci] 2024-11-05 12:56:39 +01:00
larabr
dd01ee00cb 6.0.0 2024-11-04 17:35:18 +01:00
Daniel Huigens
a5645e1d6c Spaces after "RFC" in README 2024-11-04 17:03:36 +01:00
Daniel Huigens
09800741f0 Document required Web Crypto support in README 2024-11-04 17:02:04 +01:00
Daniel Huigens
31a7e2616b
Merge pull request #1629 from openpgpjs/v6
V6
2024-11-04 12:11:19 +01:00
larabr
42d504a69a
Switch to SHA512 as default preferred hash algo (config.preferredHashAlgorithm) (#1801)
This affects the preferences of newly generated keys, which by default will
have SHA512 as first hash algo preference.
SHA512 will also be used when signing, as long as the recipient keys declare
support for the algorithm.
2024-10-31 00:24:19 +01:00
larabr
fb72ea449a
Merge pull request #1802
Determine signature hash prefs based on recipient keys instead of signing key
2024-10-31 00:16:40 +01:00
larabr
f9a3e54364 openpgp.sign: add recipientKeys option to get the signing prefs from
If given, the signature will be generated using the preferred hash algo from the recipient keys.
Otherwise, the signing key preferences are used (this was also the existing behavior).

Note: when signing through `openpgp.encrypt`, the `encryptionKeys` are automatically used as recipient keys.
2024-10-30 19:06:44 +01:00
larabr
d3e75de23d openpgp.encrypt: use encryptionKeys to determine preferred hash algo when signing
In `openpgp.sign`, the signing key preferences are considered instead,
since no "recipient keys" are available.

The hash algo selection logic has been reworked as follows:
if `config.preferredHashAlgo` appears in the prefs of all recipients, we pick it;
otherwise, we use the strongest supported algo (note: SHA256 is always implicitly supported by all keys),
as long as it is compatible with the signing key (e.g. ECC keys require minimum digest sizes).

Previously, only the preferences of the signing key were used to determine the hash algo to use,
but this is in contrast to the RFC: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.16-2 .
Also, an algo stronger than `config.preferredHashAlgo` would be used, if the signing key
declared it as first preference.

With this change, `config.preferredHashAlgo` is picked even if it's weaker than the
preferences of the recipient keys.
2024-10-30 19:06:44 +01:00
larabr
12274a1543 Update README [skip ci] 2024-10-28 18:01:07 +01:00
larabr
0138b69356 CI: update Browserstack project id to include target branch 2024-10-28 13:38:59 +01:00
larabr
821f260ba9 Lightweight build: lazy load bzip decompression lib 2024-10-28 13:38:59 +01:00
larabr
09095ced4f Run npm update
as well as npm audit
2024-10-28 13:38:59 +01:00
larabr
d7f5736d67
Merge pull request #1794 2024-10-25 12:16:02 +02:00
larabr
693adb417e CI: run browser tests also on Linux
To test platform potential specific code of e.g. the WebCrypto API

Testing on Windows would be nice too, but all browsers fail to fetch resources
from the web-test-runner server.
2024-10-25 11:38:52 +02:00
larabr
013dffce70 CI: test latest Webkit on macOS, as a replacement for testing Safari on Browserstack
We were previously testing the webkit engine on Linux, which however relies on a
different WebCrypto API implementation compared to the macOS version (behind Safari).

Also, increase mocha timeouts, as the argon2 memory-heavy test takes longer in Firefox.
2024-10-24 20:12:11 +02:00
larabr
59c809c943 CI: Browserstack: test only iOS latest and min supported version (iOS 14)
Dropping Safari since Web Secure Sockets do not seem to work with
the 'networkLogs' capability, which is in turn required for the HTTPS
connection to work without insecure certs warnings.
2024-10-24 15:39:20 +02:00
larabr
4ddadd4f53 CI: setup HTTPS in web-test-runner for BrowserStack tests
To have tests work Browserstack Safari (also below iOS 15), as the tests are run in an iframe,
rewriting localhost as hostname, making WebCrypto not available.

We keep HTTP for the non-browserstack tests so that in local testing,
generating self-signed certs is not required.
2024-10-24 15:39:14 +02:00
larabr
ae5698c621 CI: fix playwright version parsing
Only look at direct dependency
2024-10-23 18:03:54 +02:00
larabr
4b017f6c67 Tests: drop karma (deprecated) in favor of web-test-runner 2024-10-23 18:03:51 +02:00
larabr
e924a50c31
Merge pull request #1799 2024-10-22 14:32:31 +02:00
larabr
88f20974dd Tests: add support for RNG mocking in browser tests
The affected tests were previously only run in Node.
2024-10-22 12:40:15 +02:00
larabr
05fbc63732 Use WebCrypto.getRandomValues in Node
To move towards uniform code with across platforms.
2024-10-22 12:40:15 +02:00
larabr
3cdaab7894 Check session key size on v3 SKESK and PKESK packet decryption
For v3 SKESK and PKESK packets, the session key algorithm is part of the payload,
so we can check the session key size on packet decryption.
This is helpful to catch errors early, when using e.g. `decryptSessionKeys`.

In v6 packets, the session key size check can only be done on SEIPDv2 decryption.
2024-10-22 12:40:15 +02:00
larabr
e58c02d5ee Check session key size on SEIPD decryption
This is especially important for SEIPDv2 session keys,
as a key derivation step is run where the resulting key
will always match the expected cipher size,
but we want to ensure that the input key isn't e.g. too short.
2024-10-22 12:40:15 +02:00
larabr
a57bffc84a
Fix key and signature parsing of EdDSALegacy entities with unsupported curves (e.g. Curve448Legacy) (#1798)
Signature parsing would fail in case of unexpected payload sizes, causing key parsing to always throw
when processing e.g. an (unsupported) Curve448Legacy subkey instead of ignoring it.

To address this, we now throw on signature verification instead of parsing (as done for ECDSA).

NB: the bug and this fix are not relevant for the new Ed25519/Ed448 entities as standardized by the crypto-refresh.
2024-10-14 12:15:33 +02:00
larabr
5ee854140a CI: update SOP test suite docker image to v1.1.12
Includes rsop with crypto-refresh support
2024-10-03 16:44:55 +02:00
larabr
ada794cab6 Throw on (unexpected) low order points in ECDH over Curve25519/448
These points do not pose a security threat in the context of OpenPGP ECDH,
and would simply result in an all-zero shared secret being generated.
However, they represent unexpected inputs, so we prefer to warn the user.
2024-09-12 13:32:14 +02:00
larabr
e80d71bdfc CI: setup Dependabot to update non-dev dependencies
We unfortunately need to manually list them as they are still
declared as dev dependencies in the package.json, due to the fact
that we bundle them.
2024-09-11 19:41:57 +02:00
larabr
e454faab0c CI: setup Dependabot to update playwright and test latest browser versions 2024-09-11 19:35:47 +02:00
larabr
6ac17dc71c 6.0.0-beta.3.patch.1 2024-09-11 10:57:20 +02:00
larabr
148fff91e8 Docs: fix type tag warnings 2024-09-11 10:56:08 +02:00
larabr
ccb040ae96 Revert to not using the WebCrypto for X25519 (ECDH only)
Due to missing support in WebKit and Chrome (without experimental flags),
and broken support in Firefox, for now we go back to using a JS implementation.

This change only affects encryption and decryption using X25519.
For signing and verification using Ed25519 we keep relying on
WebCrypto when available (namely in WebKit, Firefox, and Node).
2024-09-11 10:56:08 +02:00
larabr
2b9a07e840 Run npm audit 2024-09-11 10:42:37 +02:00
larabr
0255fcba86 CI: update playwright to test latest browser versions 2024-09-11 10:39:51 +02:00
larabr
f2818429db 6.0.0-beta.3.patch.0 2024-09-09 11:47:41 +02:00
larabr
8d8033383b Fix regression in x25519 (legacy) key generation: store clamped secret scalar
Fixes regression from changes in #1782, as the spec mandates that
legacy x25519 store the secret scalar already clamped.
Keys generated using v6.0.0-beta.3 are still expected to be functional,
since the scalar is to be clamped before computing the ECDH shared secret.
2024-09-09 11:20:59 +02:00
154 changed files with 9412 additions and 5132 deletions

33
.github/dependabot.yml vendored Normal file
View 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"

View File

@ -2,7 +2,7 @@ name: Performance Regression Test
on: on:
pull_request: pull_request:
branches: [main, v6] branches: [main]
jobs: jobs:
benchmark: benchmark:

View File

@ -4,7 +4,7 @@ on:
push: push:
branches: [main] branches: [main]
pull_request: pull_request:
branches: [main, v6] branches: [main]
jobs: jobs:
lint: lint:

View File

@ -2,7 +2,7 @@ name: SOP interoperability test suite
on: on:
pull_request: pull_request:
branches: [ main, v6 ] branches: [ main ]
jobs: jobs:
@ -10,7 +10,7 @@ jobs:
name: Run interoperability test suite name: Run interoperability test suite
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.10 image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.12
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.github_token }} password: ${{ secrets.github_token }}

View File

@ -4,7 +4,7 @@ on:
push: push:
branches: [main] branches: [main]
pull_request: pull_request:
branches: [main, v6] branches: [main]
jobs: jobs:
build: # cache both dist and tests (non-lightweight only), based on commit hash build: # cache both dist and tests (non-lightweight only), based on commit hash
@ -57,8 +57,15 @@ jobs:
test-browsers-latest: test-browsers-latest:
name: Browsers (latest) name: Browsers (latest)
runs-on: ubuntu-latest
needs: build 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -79,17 +86,19 @@ jobs:
npm pkg delete scripts.prepare npm pkg delete scripts.prepare
npm ci npm ci
- name: Get Playwright version - name: Get Playwright version and cache location
id: playwright-version id: playwright-version
run: | 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 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 - name: Check for cached browsers
id: cache-playwright-browsers id: cache-playwright-browsers
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/.cache/ms-playwright path: ${{ steps.playwright-version.outputs.playwright_cache }}
key: playwright-browsers-${{ steps.playwright-version.outputs.version }} key: playwright-browsers-${{ matrix.runner }}-${{ steps.playwright-version.outputs.version }}
- name: Install browsers - name: Install browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: | run: |
@ -97,15 +106,16 @@ jobs:
npx playwright install --with-deps firefox npx playwright install --with-deps firefox
- name: Install WebKit # caching not possible, external shared libraries required - 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 run: npx playwright install --with-deps webkit
- name: Run browser tests - 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 - name: Run browser tests (lightweight) # overwrite test/lib
run: | run: |
npm run build-test --lightweight npm run build-test --lightweight
npm run test-browser npm run test-browser:ci -- --static-logging
test-browsers-compatibility: test-browsers-compatibility:
name: Browsers (older, on Browserstack) name: Browsers (older, on Browserstack)
@ -119,6 +129,15 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@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 - name: Install dependencies
run: npm ci --ignore-scripts run: npm ci --ignore-scripts
@ -139,12 +158,12 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run browserstack tests - name: Run browserstack tests
run: npm run test-browserstack run: npm run test-browserstack -- --static-logging
- name: Run browserstack tests (lightweight) # overwrite test/lib - name: Run browserstack tests (lightweight) # overwrite test/lib
run: | run: |
npm run build-test --lightweight npm run build-test --lightweight
npm run test-browserstack npm run test-browserstack -- --static-logging
env: env:
LIGHTWEIGHT: true LIGHTWEIGHT: true

View File

@ -1,7 +1,7 @@
OpenPGP.js [![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=N1l2eHFOanVBMU9wYWxJM3ZnWERnc1lidkt5UkRqa3BralV3SWVhOGpGTT0tLVljSjE4Z3dzVmdiQjl6RWgxb2c3T2c9PQ==--5864052cd523f751b6b907d547ac9c4c5f88c8a3)](https://automate.browserstack.com/public-build/N1l2eHFOanVBMU9wYWxJM3ZnWERnc1lidkt5UkRqa3BralV3SWVhOGpGTT0tLVljSjE4Z3dzVmdiQjl6RWgxb2c3T2c9PQ==--5864052cd523f751b6b907d547ac9c4c5f88c8a3) [![Join the chat on Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/openpgpjs/openpgpjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) OpenPGP.js [![Join the chat on Gitter](https://badges.gitter.im/Join%20Chat.svg)](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** **Table of Contents**
@ -35,29 +35,35 @@ OpenPGP.js [![BrowserStack Status](https://automate.browserstack.com/badge.svg?b
* The `dist/openpgp.min.js` (or `.mjs`) bundle works with recent versions of Chrome, Firefox, Edge and Safari 14+. * The `dist/openpgp.min.js` (or `.mjs`) 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 ### 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 | | Curve | Encryption | Signature | NodeCrypto | WebCrypto | Constant-Time |
|:---------------:|:----------:|:---------:|:----------:|:---------:|:-----------------:| |:---------------:|:----------:|:---------:|:----------:|:---------:|:-----------------:|
| curve25519 | ECDH | N/A | No | Yes* | If native** | | curve25519 | ECDH | N/A | No | No | Algorithmically |
| ed25519 | N/A | EdDSA | No | Yes* | If native** | | ed25519 | N/A | EdDSA | No | Yes* | If native** |
| nistP256 | ECDH | ECDSA | Yes* | Yes* | If native** | | nistP256 | ECDH | ECDSA | Yes* | Yes* | If native** |
| nistP384 | ECDH | ECDSA | Yes* | Yes* | If native** | | nistP384 | ECDH | ECDSA | Yes* | Yes* | If native** |
@ -67,25 +73,22 @@ library to convert back and forth between them.
| brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native** | | brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native** |
| secp256k1 | ECDH | ECDSA | Yes* | No | If native** | | secp256k1 | ECDH | ECDSA | Yes* | No | If native** |
\* when available \* when available
\** 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`. * 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 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. 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: 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; // Default, native in WebCrypto and Node.js
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.gcm; // 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 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 ### Getting started
#### Node.js #### Node.js
@ -386,14 +389,8 @@ Where the value can be any of:
})(); })();
``` ```
For more information on using ReadableStreams, see [the MDN Documentation on the 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). 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.
#### Streaming encrypt and decrypt *String* data with PGP keys #### Streaming encrypt and decrypt *String* data with PGP keys
@ -667,7 +664,7 @@ To create your own build of the library, just run the following command after cl
npm install && npm test 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 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

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

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

14
openpgp.d.ts vendored
View File

@ -94,7 +94,7 @@ export class PrivateKey extends PublicKey {
public revoke(reason?: ReasonForRevocation, date?: Date, config?: Config): Promise<PrivateKey>; public revoke(reason?: ReasonForRevocation, date?: Date, config?: Config): Promise<PrivateKey>;
public isDecrypted(): boolean; public isDecrypted(): boolean;
public addSubkey(options: SubkeyOptions): Promise<PrivateKey>; 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>; public update(sourceKey: PublicKey, date?: Date, config?: Config): Promise<PrivateKey>;
} }
@ -702,7 +702,7 @@ export type EllipticCurveName = 'ed25519Legacy' | 'curve25519Legacy' | 'nistP256
interface GenerateKeyOptions { interface GenerateKeyOptions {
userIDs: MaybeArray<UserID>; userIDs: MaybeArray<UserID>;
passphrase?: string; passphrase?: string;
type?: 'ecc' | 'rsa'; type?: 'ecc' | 'rsa' | 'curve25519' | 'curve448';
curve?: EllipticCurveName; curve?: EllipticCurveName;
rsaBits?: number; rsaBits?: number;
keyExpirationTime?: number; keyExpirationTime?: number;
@ -713,14 +713,8 @@ interface GenerateKeyOptions {
} }
export type KeyOptions = GenerateKeyOptions; export type KeyOptions = GenerateKeyOptions;
export interface SubkeyOptions { export interface SubkeyOptions extends Pick<GenerateKeyOptions, 'type' | 'curve' | 'rsaBits' | 'keyExpirationTime' | 'date' | 'config'> {
type?: 'ecc' | 'rsa';
curve?: EllipticCurveName;
rsaBits?: number;
keyExpirationTime?: number;
date?: Date;
sign?: boolean; sign?: boolean;
config?: PartialConfig;
} }
export declare class KeyID { export declare class KeyID {
@ -922,6 +916,8 @@ export namespace enums {
export enum aead { export enum aead {
eax = 1, eax = 1,
ocb = 2, ocb = 2,
gcm = 3,
/** @deprecated use `gcm` instead */
experimentalGCM = 100 // Private algorithm experimentalGCM = 100 // Private algorithm
} }

6571
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "openpgp", "name": "openpgp",
"description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.", "description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.",
"version": "6.0.0-beta.3", "version": "6.1.0",
"license": "LGPL-3.0+", "license": "LGPL-3.0+",
"homepage": "https://openpgpjs.org/", "homepage": "https://openpgpjs.org/",
"engines": { "engines": {
@ -22,9 +22,9 @@
"exports": { "exports": {
".": { ".": {
"types": "./openpgp.d.ts", "types": "./openpgp.d.ts",
"browser": "./dist/openpgp.min.mjs",
"import": "./dist/node/openpgp.mjs", "import": "./dist/node/openpgp.mjs",
"require": "./dist/node/openpgp.min.cjs", "require": "./dist/node/openpgp.min.cjs"
"browser": "./dist/openpgp.min.mjs"
}, },
"./lightweight": { "./lightweight": {
"types": "./openpgp.d.ts", "types": "./openpgp.d.ts",
@ -49,35 +49,40 @@
"test-type-definitions": "tsx test/typescript/definitions.ts", "test-type-definitions": "tsx test/typescript/definitions.ts",
"benchmark-time": "node test/benchmarks/time.js", "benchmark-time": "node test/benchmarks/time.js",
"benchmark-memory-usage": "node test/benchmarks/memory_usage.js", "benchmark-memory-usage": "node test/benchmarks/memory_usage.js",
"start": "http-server",
"prebrowsertest": "npm run build-test", "prebrowsertest": "npm run build-test",
"browsertest": "npm start -- -o test/unittests.html", "browsertest": "web-test-runner --config test/web-test-runner.config.js --group local --manual --open",
"test-browser": "karma start test/karma.conf.cjs", "test-browser": "web-test-runner --config test/web-test-runner.config.js --group local --playwright --browsers chromium firefox webkit",
"test-browserstack": "karma start test/karma.conf.cjs --browsers bs_safari_latest,bs_ios_14,bs_safari_14", "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", "coverage": "c8 npm test",
"lint": "eslint .", "lint": "eslint .",
"docs": "jsdoc --configure .jsdocrc.cjs --destination docs --recurse README.md src && printf '%s' 'docs.openpgpjs.org' > docs/CNAME", "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", "preversion": "rm -rf dist docs node_modules && npm ci && npm test",
"version": "npm run docs && git add -A docs", "version": "npm run docs && git add -A docs",
"postversion": "git push && git push --tags && npm publish" "postversion": "git push --follow-tags && npm publish"
}, },
"devDependencies": { "devDependencies": {
"@noble/ciphers": "^0.6.0", "@noble/ciphers": "^1.2.1",
"@noble/curves": "^1.6.0", "@noble/curves": "^1.8.1",
"@noble/hashes": "^1.5.0", "@noble/hashes": "^1.5.0",
"@openpgp/jsdoc": "^3.6.11", "@openpgp/jsdoc": "^3.6.11",
"@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/seek-bzip": "^1.0.5-git",
"@openpgp/tweetnacl": "^1.0.4-1", "@openpgp/tweetnacl": "^1.0.4-1",
"@openpgp/web-stream-tools": "~0.1.3", "@openpgp/web-stream-tools": "~0.1.3",
"@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^25.0.8", "@rollup/plugin-commonjs": "^25.0.8",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-wasm": "^6.2.2", "@rollup/plugin-wasm": "^6.2.2",
"@types/chai": "^4.3.19", "@types/chai": "^4.3.19",
"@types/sinon": "^17.0.3",
"@typescript-eslint/parser": "^7.18.0", "@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", "argon2id": "^1.0.1",
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"bn.js": "^5.2.1", "bn.js": "^5.2.1",
@ -85,33 +90,29 @@
"chai": "^4.4.1", "chai": "^4.4.1",
"chai-as-promised": "^7.1.2", "chai-as-promised": "^7.1.2",
"eckey-utils": "^0.7.14", "eckey-utils": "^0.7.14",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-airbnb-typescript": "^18.0.0",
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-chai-friendly": "^0.7.4", "eslint-plugin-chai-friendly": "^0.7.4",
"eslint-plugin-import": "^2.30.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-unicorn": "^48.0.1", "eslint-plugin-unicorn": "^48.0.1",
"fflate": "^0.7.4", "fflate": "^0.8.2",
"http-server": "^14.1.1",
"karma": "^6.4.4",
"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.6.0",
"mocha": "^10.7.3", "mocha": "^10.7.3",
"playwright": "^1.46.1", "playwright": "^1.51.1",
"rollup": "^4.21.2", "rollup": "^4.24.2",
"sinon": "^17.0.1", "sinon": "^18.0.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.7.0", "tslib": "^2.8.0",
"tsx": "^4.19.0", "tsx": "^4.19.2",
"typescript": "^5.5.4", "typescript": "^5.6.3",
"web-streams-polyfill": "^4.0.0" "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": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/openpgpjs/openpgpjs" "url": "https://github.com/openpgpjs/openpgpjs"

View File

@ -23,13 +23,20 @@ const wasmOptions = {
browser: { targetEnv: 'browser', maxFileSize: undefined } // always inlline (our wasm files are small) browser: { targetEnv: 'browser', maxFileSize: undefined } // always inlline (our wasm files are small)
}; };
const getChunkFileName = (chunkInfo, extension) => { const getChunkFileName = (chunkInfo, extension) => `[name].${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 * Dynamically imported modules which expose an index file as entrypoint end up with a chunk named `index`
return `${packageName}.${extension}`; * 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 = 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([ export default Object.assign([
{ nodeBuild,
input: 'src/index.js', fullBrowserBuild,
external: nodeBuiltinModules.concat(nodeDependencies), lightweightBrowserBuild,
output: [ testBuild
{ 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)
]
}
].filter(config => { ].filter(config => {
config.output = config.output.filter(output => { config.output = config.output.filter(output => {
return (output.file || output.dir + '/' + output.entryFileNames).includes( return (output.file || output.dir + '/' + output.entryFileNames).includes(

View File

@ -59,20 +59,22 @@ export class CleartextMessage {
/** /**
* Sign the cleartext message * 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 {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 {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 {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 {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 * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<CleartextMessage>} New cleartext message with signed content. * @returns {Promise<CleartextMessage>} New cleartext message with signed content.
* @async * @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(); const literalDataPacket = new LiteralDataPacket();
literalDataPacket.setText(this.text); 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); return new CleartextMessage(this.text, newSignature);
} }

View File

@ -26,7 +26,7 @@ export default {
* @memberof module:config * @memberof module:config
* @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash} * @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash}
*/ */
preferredHashAlgorithm: enums.hash.sha256, preferredHashAlgorithm: enums.hash.sha512,
/** /**
* @memberof module:config * @memberof module:config
* @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric} * @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric}

View File

@ -11,7 +11,8 @@ export async function getLegacyCipher(algo) {
case enums.symmetric.twofish: case enums.symmetric.twofish:
case enums.symmetric.tripledes: { case enums.symmetric.tripledes: {
const { legacyCiphers } = await import('./legacy_ciphers'); 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) { if (!cipher) {
throw new Error('Unsupported cipher algorithm'); throw new Error('Unsupported cipher algorithm');
} }

View File

@ -3,15 +3,16 @@
* Separate dynamic imports are not convenient as they result in multiple chunks. * Separate dynamic imports are not convenient as they result in multiple chunks.
*/ */
import { TripleDES } from './des'; import { TripleDES as tripledes } from './des';
import CAST5 from './cast5'; import cast5 from './cast5';
import TwoFish from './twofish'; import twofish from './twofish';
import BlowFish from './blowfish'; import blowfish from './blowfish';
import enums from '../../enums';
export const legacyCiphers = new Map([ // We avoid importing 'enums' as this module is lazy loaded, and doing so could mess up
[enums.symmetric.tripledes, TripleDES], // chunking for the lightweight build
[enums.symmetric.cast5, CAST5], export const legacyCiphers = new Map(Object.entries({
[enums.symmetric.blowfish, BlowFish], tripledes,
[enums.symmetric.twofish, TwoFish] cast5,
]); twofish,
blowfish
}));

View File

@ -23,10 +23,11 @@
import { cfb as nobleAesCfb, unsafe as nobleAesHelpers } from '@noble/ciphers/aes'; import { cfb as nobleAesCfb, unsafe as nobleAesHelpers } from '@noble/ciphers/aes';
import * as stream from '@openpgp/web-stream-tools'; import { transform as streamTransform } from '@openpgp/web-stream-tools';
import util from '../../util'; import util from '../../util';
import enums from '../../enums'; import enums from '../../enums';
import { getLegacyCipher, getCipherParams } from '../cipher'; import { getLegacyCipher, getCipherParams } from '../cipher';
import { getRandomBytes } from '../random';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
@ -43,6 +44,20 @@ const nodeAlgos = {
/* twofish is not implemented in OpenSSL */ /* 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 * CFB encryption
* @param {enums.symmetric} algo - block cipher algorithm * @param {enums.symmetric} algo - block cipher algorithm
@ -84,7 +99,7 @@ export async function encrypt(algo, key, plaintext, iv, config) {
} }
return ciphertext.subarray(0, j); return ciphertext.subarray(0, j);
}; };
return stream.transform(plaintext, process, process); return streamTransform(plaintext, process, process);
} }
/** /**
@ -127,7 +142,7 @@ export async function decrypt(algo, key, ciphertext, iv) {
} }
return plaintext.subarray(0, j); return plaintext.subarray(0, j);
}; };
return stream.transform(ciphertext, process, process); return streamTransform(ciphertext, process, process);
} }
class WebCryptoEncryptor { class WebCryptoEncryptor {
@ -331,10 +346,10 @@ class NobleStreamProcessor {
async function aesEncrypt(algo, key, pt, iv) { async function aesEncrypt(algo, key, pt, iv) {
if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys
const cfb = new WebCryptoEncryptor(algo, key, iv); const cfb = new WebCryptoEncryptor(algo, key, iv);
return util.isStream(pt) ? stream.transform(pt, value => cfb.encryptChunk(value), () => cfb.finish()) : cfb.encrypt(pt); 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 stream.transform unless the input is a stream } 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); const cfb = new NobleStreamProcessor(true, algo, key, iv);
return stream.transform(pt, value => cfb.processChunk(value), () => cfb.finish()); return streamTransform(pt, value => cfb.processChunk(value), () => cfb.finish());
} }
return nobleAesCfb(key, iv).encrypt(pt); return nobleAesCfb(key, iv).encrypt(pt);
} }
@ -342,7 +357,7 @@ async function aesEncrypt(algo, key, pt, iv) {
async function aesDecrypt(algo, key, ct, iv) { async function aesDecrypt(algo, key, ct, iv) {
if (util.isStream(ct)) { if (util.isStream(ct)) {
const cfb = new NobleStreamProcessor(false, algo, key, iv); const cfb = new NobleStreamProcessor(false, algo, key, iv);
return stream.transform(ct, value => cfb.processChunk(value), () => cfb.finish()); return streamTransform(ct, value => cfb.processChunk(value), () => cfb.finish());
} }
return nobleAesCfb(key, iv).decrypt(ct); return nobleAesCfb(key, iv).decrypt(ct);
} }
@ -359,11 +374,11 @@ const getUint32Array = arr => new Uint32Array(arr.buffer, arr.byteOffset, Math.f
function nodeEncrypt(algo, key, pt, iv) { function nodeEncrypt(algo, key, pt, iv) {
const algoName = enums.read(enums.symmetric, algo); const algoName = enums.read(enums.symmetric, algo);
const cipherObj = new nodeCrypto.createCipheriv(nodeAlgos[algoName], key, iv); 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) { function nodeDecrypt(algo, key, ct, iv) {
const algoName = enums.read(enums.symmetric, algo); const algoName = enums.read(enums.symmetric, algo);
const decipherObj = new nodeCrypto.createDecipheriv(nodeAlgos[algoName], key, iv); 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)));
} }

View 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');
}
}

View File

@ -73,9 +73,8 @@ async function OCB(cipher, key) {
// `encipher` and `decipher` cannot be async, since `crypt` shares state across calls, // `encipher` and `decipher` cannot be async, since `crypt` shares state across calls,
// hence its execution cannot be broken up. // hence its execution cannot be broken up.
// As a result, WebCrypto cannot currently be used for `encipher`. // As a result, WebCrypto cannot currently be used for `encipher`.
const aes = nobleAesCbc(key, zeroBlock, { disablePadding: true }); const encipher = block => nobleAesCbc(key, zeroBlock, { disablePadding: true }).encrypt(block);
const encipher = block => aes.encrypt(block); const decipher = block => nobleAesCbc(key, zeroBlock, { disablePadding: true }).decrypt(block);
const decipher = block => aes.decrypt(block);
let mask; let mask;
constructKeyVariables(cipher, key); constructKeyVariables(cipher, key);

View File

@ -23,8 +23,7 @@
* @module crypto/crypto * @module crypto/crypto
*/ */
import publicKey from './public_key'; import { rsa, elliptic, elgamal, dsa } from './public_key';
import mode from './mode';
import { getRandomBytes } from './random'; import { getRandomBytes } from './random';
import { getCipherParams } from './cipher'; import { getCipherParams } from './cipher';
import ECDHSymkey from '../type/ecdh_symkey'; 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.rsaEncrypt:
case enums.publicKey.rsaEncryptSign: { case enums.publicKey.rsaEncryptSign: {
const { n, e } = publicParams; const { n, e } = publicParams;
const c = await publicKey.rsa.encrypt(data, n, e); const c = await rsa.encrypt(data, n, e);
return { c }; return { c };
} }
case enums.publicKey.elgamal: { case enums.publicKey.elgamal: {
const { p, g, y } = publicParams; const { p, g, y } = publicParams;
return publicKey.elgamal.encrypt(data, p, g, y); return elgamal.encrypt(data, p, g, y);
} }
case enums.publicKey.ecdh: { case enums.publicKey.ecdh: {
const { oid, Q, kdfParams } = publicParams; 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); oid, kdfParams, data, Q, fingerprint);
return { V, C: new ECDHSymkey(C) }; 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'); throw new Error('X25519 and X448 keys can only encrypt AES session keys');
} }
const { A } = publicParams; const { A } = publicParams;
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt( const { ephemeralPublicKey, wrappedKey } = await elliptic.ecdhX.encrypt(
keyAlgo, data, A); keyAlgo, data, A);
const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey }); const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey });
return { ephemeralPublicKey, C }; return { ephemeralPublicKey, C };
@ -102,19 +101,19 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
const { c } = sessionKeyParams; const { c } = sessionKeyParams;
const { n, e } = publicKeyParams; const { n, e } = publicKeyParams;
const { d, p, q, u } = privateKeyParams; 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: { case enums.publicKey.elgamal: {
const { c1, c2 } = sessionKeyParams; const { c1, c2 } = sessionKeyParams;
const p = publicKeyParams.p; const p = publicKeyParams.p;
const x = privateKeyParams.x; 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: { case enums.publicKey.ecdh: {
const { oid, Q, kdfParams } = publicKeyParams; const { oid, Q, kdfParams } = publicKeyParams;
const { d } = privateKeyParams; const { d } = privateKeyParams;
const { V, C } = sessionKeyParams; const { V, C } = sessionKeyParams;
return publicKey.elliptic.ecdh.decrypt( return elliptic.ecdh.decrypt(
oid, kdfParams, V, C.data, Q, d, fingerprint); oid, kdfParams, V, C.data, Q, d, fingerprint);
} }
case enums.publicKey.x25519: case enums.publicKey.x25519:
@ -125,7 +124,7 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
if (C.algorithm !== null && !util.isAES(C.algorithm)) { if (C.algorithm !== null && !util.isAES(C.algorithm)) {
throw new Error('AES session key expected'); throw new Error('AES session key expected');
} }
return publicKey.elliptic.ecdhX.decrypt( return elliptic.ecdhX.decrypt(
algo, ephemeralPublicKey, C.wrappedKey, A, k); algo, ephemeralPublicKey, C.wrappedKey, A, k);
} }
default: default:
@ -338,22 +337,22 @@ export function generateParams(algo, bits, oid) {
case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncrypt:
case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaSign: 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 }, privateParams: { d, p, q, u },
publicParams: { n, e } publicParams: { n, e }
})); }));
case enums.publicKey.ecdsa: case enums.publicKey.ecdsa:
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ return elliptic.generate(oid).then(({ oid, Q, secret }) => ({
privateParams: { d: secret }, privateParams: { d: secret },
publicParams: { oid: new OID(oid), Q } publicParams: { oid: new OID(oid), Q }
})); }));
case enums.publicKey.eddsaLegacy: case enums.publicKey.eddsaLegacy:
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ return elliptic.generate(oid).then(({ oid, Q, secret }) => ({
privateParams: { seed: secret }, privateParams: { seed: secret },
publicParams: { oid: new OID(oid), Q } publicParams: { oid: new OID(oid), Q }
})); }));
case enums.publicKey.ecdh: 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 }, privateParams: { d: secret },
publicParams: { publicParams: {
oid: new OID(oid), oid: new OID(oid),
@ -363,13 +362,13 @@ export function generateParams(algo, bits, oid) {
})); }));
case enums.publicKey.ed25519: case enums.publicKey.ed25519:
case enums.publicKey.ed448: case enums.publicKey.ed448:
return publicKey.elliptic.eddsa.generate(algo).then(({ A, seed }) => ({ return elliptic.eddsa.generate(algo).then(({ A, seed }) => ({
privateParams: { seed }, privateParams: { seed },
publicParams: { A } publicParams: { A }
})); }));
case enums.publicKey.x25519: case enums.publicKey.x25519:
case enums.publicKey.x448: case enums.publicKey.x448:
return publicKey.elliptic.ecdhX.generate(algo).then(({ A, k }) => ({ return elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
privateParams: { k }, privateParams: { k },
publicParams: { A } publicParams: { A }
})); }));
@ -399,21 +398,21 @@ export async function validateParams(algo, publicParams, privateParams) {
case enums.publicKey.rsaSign: { case enums.publicKey.rsaSign: {
const { n, e } = publicParams; const { n, e } = publicParams;
const { d, p, q, u } = privateParams; 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: { case enums.publicKey.dsa: {
const { p, q, g, y } = publicParams; const { p, q, g, y } = publicParams;
const { x } = privateParams; const { x } = privateParams;
return publicKey.dsa.validateParams(p, q, g, y, x); return dsa.validateParams(p, q, g, y, x);
} }
case enums.publicKey.elgamal: { case enums.publicKey.elgamal: {
const { p, g, y } = publicParams; const { p, g, y } = publicParams;
const { x } = privateParams; 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.ecdsa:
case enums.publicKey.ecdh: { 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 { oid, Q } = publicParams;
const { d } = privateParams; const { d } = privateParams;
return algoModule.validateParams(oid, Q, d); return algoModule.validateParams(oid, Q, d);
@ -421,39 +420,25 @@ export async function validateParams(algo, publicParams, privateParams) {
case enums.publicKey.eddsaLegacy: { case enums.publicKey.eddsaLegacy: {
const { Q, oid } = publicParams; const { Q, oid } = publicParams;
const { seed } = privateParams; 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.ed25519:
case enums.publicKey.ed448: { case enums.publicKey.ed448: {
const { A } = publicParams; const { A } = publicParams;
const { seed } = privateParams; 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.x25519:
case enums.publicKey.x448: { case enums.publicKey.x448: {
const { A } = publicParams; const { A } = publicParams;
const { k } = privateParams; const { k } = privateParams;
return publicKey.elliptic.ecdhX.validateParams(algo, A, k); return elliptic.ecdhX.validateParams(algo, A, k);
} }
default: default:
throw new Error('Unknown public key algorithm.'); 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 * 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. * 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); 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 * Check whether the given curve OID is supported
* @param {module:type/oid} oid - EC object identifier * @param {module:type/oid} oid - EC object identifier
@ -499,13 +473,13 @@ export function getCurvePayloadSize(algo, oid) {
case enums.publicKey.ecdsa: case enums.publicKey.ecdsa:
case enums.publicKey.ecdh: case enums.publicKey.ecdh:
case enums.publicKey.eddsaLegacy: case enums.publicKey.eddsaLegacy:
return new publicKey.elliptic.CurveWithOID(oid).payloadSize; return new elliptic.CurveWithOID(oid).payloadSize;
case enums.publicKey.ed25519: case enums.publicKey.ed25519:
case enums.publicKey.ed448: case enums.publicKey.ed448:
return publicKey.elliptic.eddsa.getPayloadSize(algo); return elliptic.eddsa.getPayloadSize(algo);
case enums.publicKey.x25519: case enums.publicKey.x25519:
case enums.publicKey.x448: case enums.publicKey.x448:
return publicKey.elliptic.ecdhX.getPayloadSize(algo); return elliptic.ecdhX.getPayloadSize(algo);
default: default:
throw new Error('Unknown elliptic algo'); throw new Error('Unknown elliptic algo');
} }
@ -520,14 +494,12 @@ export function getPreferredCurveHashAlgo(algo, oid) {
switch (algo) { switch (algo) {
case enums.publicKey.ecdsa: case enums.publicKey.ecdsa:
case enums.publicKey.eddsaLegacy: case enums.publicKey.eddsaLegacy:
return publicKey.elliptic.getPreferredHashAlgo(oid); return elliptic.getPreferredHashAlgo(oid);
case enums.publicKey.ed25519: case enums.publicKey.ed25519:
case enums.publicKey.ed448: case enums.publicKey.ed448:
return publicKey.elliptic.eddsa.getPreferredHashAlgo(algo); return elliptic.eddsa.getPreferredHashAlgo(algo);
default: default:
throw new Error('Unknown elliptic signing algo'); throw new Error('Unknown elliptic signing algo');
} }
} }
export { getCipherParams };

View File

@ -5,8 +5,7 @@
* @module crypto/hash * @module crypto/hash
*/ */
import * as stream from '@openpgp/web-stream-tools'; import { transform as streamTransform, isArrayStream, readToEnd as streamReadToEnd } from '@openpgp/web-stream-tools';
import md5 from './md5';
import util from '../../util'; import util from '../../util';
import enums from '../../enums'; import enums from '../../enums';
@ -20,7 +19,7 @@ function nodeHash(type) {
} }
return async function (data) { return async function (data) {
const shasum = nodeCrypto.createHash(type); const shasum = nodeCrypto.createHash(type);
return stream.transform(data, value => { return streamTransform(data, value => {
shasum.update(value); shasum.update(value);
}, () => new Uint8Array(shasum.digest())); }, () => new Uint8Array(shasum.digest()));
}; };
@ -35,14 +34,14 @@ function nobleHash(nobleHashName, webCryptoHashName) {
}; };
return async function(data) { return async function(data) {
if (stream.isArrayStream(data)) { if (isArrayStream(data)) {
data = await stream.readToEnd(data); data = await streamReadToEnd(data);
} }
if (util.isStream(data)) { if (util.isStream(data)) {
const hash = await getNobleHash(); const hash = await getNobleHash();
const hashInstance = hash.create(); const hashInstance = hash.create();
return stream.transform(data, value => { return streamTransform(data, value => {
hashInstance.update(value); hashInstance.update(value);
}, () => hashInstance.digest()); }, () => hashInstance.digest());
} else if (webCrypto && webCryptoHashName) { } 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, * Create a hash on the specified data using the specified algorithm
sha1: nodeHash('sha1') || nobleHash('sha1', 'SHA-1'), * @param {module:enums.hash} algo - Hash algorithm type (see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
sha224: nodeHash('sha224') || nobleHash('sha224'), * @param {Uint8Array} data - Data to be hashed
sha256: nodeHash('sha256') || nobleHash('sha256', 'SHA-256'), * @returns {Promise<Uint8Array>} Hash value.
sha384: nodeHash('sha384') || nobleHash('sha384', 'SHA-384'), */
sha512: nodeHash('sha512') || nobleHash('sha512', 'SHA-512'), export function computeDigest(algo, data) {
ripemd: nodeHash('ripemd160') || nobleHash('ripemd160'), switch (algo) {
sha3_256: nodeHash('sha3-256') || nobleHash('sha3_256'), case enums.hash.md5:
sha3_512: nodeHash('sha3-512') || nobleHash('sha3_512'), return md5(data);
case enums.hash.sha1:
/** return sha1(data);
* Create a hash on the specified data using the specified algorithm case enums.hash.ripemd:
* @param {module:enums.hash} algo - Hash algorithm type (see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}) return ripemd(data);
* @param {Uint8Array} data - Data to be hashed case enums.hash.sha256:
* @returns {Promise<Uint8Array>} Hash value. return sha256(data);
*/ case enums.hash.sha384:
digest: function(algo, data) { return sha384(data);
switch (algo) { case enums.hash.sha512:
case enums.hash.md5: return sha512(data);
return this.md5(data); case enums.hash.sha224:
case enums.hash.sha1: return sha224(data);
return this.sha1(data); case enums.hash.sha3_256:
case enums.hash.ripemd: return sha3_256(data);
return this.ripemd(data); case enums.hash.sha3_512:
case enums.hash.sha256: return sha3_512(data);
return this.sha256(data); default:
case enums.hash.sha384: throw new Error('Unsupported hash function');
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.');
}
} }
}; }
/**
* 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.');
}
}

View File

@ -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
View 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());

View File

@ -9,8 +9,10 @@ import { sha224, sha256 } from '@noble/hashes/sha256';
import { sha384, sha512 } from '@noble/hashes/sha512'; import { sha384, sha512 } from '@noble/hashes/sha512';
import { sha3_256, sha3_512 } from '@noble/hashes/sha3'; import { sha3_256, sha3_512 } from '@noble/hashes/sha3';
import { ripemd160 } from '@noble/hashes/ripemd160'; import { ripemd160 } from '@noble/hashes/ripemd160';
import { md5 } from './md5';
export const nobleHashes = new Map(Object.entries({ export const nobleHashes = new Map(Object.entries({
md5,
sha1, sha1,
sha224, sha224,
sha256, sha256,

View File

@ -9,39 +9,10 @@
* @module crypto * @module crypto
*/ */
import * as cipher from './cipher'; export * from './crypto';
import hash from './hash'; export { getCipherParams } from './cipher';
import mode from './mode'; export * from './hash';
import publicKey from './public_key'; export * as cipherMode from './cipherMode';
import * as signature from './signature'; export * as publicKey from './public_key';
import * as random from './random'; export * as signature from './signature';
import * as pkcs1 from './pkcs1'; export { getRandomBytes } from './random';
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;

View File

@ -1,21 +0,0 @@
/**
* @fileoverview Cipher modes
* @module crypto/mode
*/
import * as cfb from './cfb';
import eax from './eax';
import ocb from './ocb';
import gcm from './gcm';
export default {
/** @see module:crypto/mode/cfb */
cfb: cfb,
/** @see module:crypto/mode/gcm */
gcm: gcm,
experimentalGCM: gcm,
/** @see module:crypto/mode/eax */
eax: eax,
/** @see module:crypto/mode/ocb */
ocb: ocb
};

View File

@ -24,7 +24,7 @@
*/ */
import { getRandomBytes } from './random'; import { getRandomBytes } from './random';
import hash from './hash'; import { getHashByteLength } from './hash';
import util from '../util'; import util from '../util';
/** /**
@ -134,7 +134,7 @@ export function emeDecode(encoded, randomPayload) {
*/ */
export function emsaEncode(algo, hashed, emLen) { export function emsaEncode(algo, hashed, emLen) {
let i; let i;
if (hashed.length !== hash.getHashByteLength(algo)) { if (hashed.length !== getHashByteLength(algo)) {
throw new Error('Invalid hash length'); throw new Error('Invalid hash length');
} }
// produce an ASN.1 DER value for the hash function used. // produce an ASN.1 DER value for the hash function used.

View File

@ -22,7 +22,7 @@
import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams, checkPublicPointEnconding } from './oid_curves'; import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams, checkPublicPointEnconding } from './oid_curves';
import * as aesKW from '../../aes_kw'; import * as aesKW from '../../aes_kw';
import hash from '../../hash'; import { computeDigest } from '../../hash';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import { b64ToUint8Array } from '../../../encoding/base64'; import { b64ToUint8Array } from '../../../encoding/base64';
@ -72,7 +72,7 @@ async function kdf(hashAlgo, X, length, param, stripLeading = false, stripTraili
for (i = X.length - 1; i >= 0 && X[i] === 0; i--); for (i = X.length - 1; i >= 0 && X[i] === 0; i--);
X = X.subarray(0, i + 1); X = X.subarray(0, i + 1);
} }
const digest = await hash.digest(hashAlgo, util.concatUint8Array([ const digest = await computeDigest(hashAlgo, util.concatUint8Array([
new Uint8Array([0, 0, 0, 1]), new Uint8Array([0, 0, 0, 1]),
X, X,
param param

View File

@ -35,7 +35,7 @@ export async function generate(algo) {
return { return {
A: new Uint8Array(b64ToUint8Array(publicKey.x)), A: new Uint8Array(b64ToUint8Array(publicKey.x)),
k: b64ToUint8Array(privateKey.d, true) k: b64ToUint8Array(privateKey.d)
}; };
} catch (err) { } catch (err) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
@ -209,14 +209,15 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
} }
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo)); const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA); const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
assertNonZeroArray(sharedSecret);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey); const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
return { ephemeralPublicKey, sharedSecret }; return { ephemeralPublicKey, sharedSecret };
} }
case enums.publicKey.x448: { case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448); const x448 = await util.getNobleCurve(enums.publicKey.x448);
const ephemeralSecretKey = x448.utils.randomPrivateKey(); const ephemeralSecretKey = x448.utils.randomPrivateKey();
const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA); const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA);
assertNonZeroArray(sharedSecret);
const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey); const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey);
return { ephemeralPublicKey, sharedSecret }; return { ephemeralPublicKey, sharedSecret };
} }
@ -244,11 +245,14 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
return x25519.scalarMult(k, ephemeralPublicKey); const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
assertNonZeroArray(sharedSecret);
return sharedSecret;
} }
case enums.publicKey.x448: { case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448); const x448 = await util.getNobleCurve(enums.publicKey.x448);
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey); const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
assertNonZeroArray(sharedSecret);
return sharedSecret; return sharedSecret;
} }
default: default:
@ -256,6 +260,22 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
} }
} }
/**
* x25519 and x448 produce an all-zero value when given as input a point with small order.
* This does not lead to a security issue in the context of ECDH, but it is still unexpected,
* hence we throw.
* @param {Uint8Array} sharedSecret
*/
function assertNonZeroArray(sharedSecret) {
let acc = 0;
for (let i = 0; i < sharedSecret.length; i++) {
acc |= sharedSecret[i];
}
if (acc === 0) {
throw new Error('Unexpected low order point');
}
}
function publicKeyToJWK(algo, publicKey) { function publicKeyToJWK(algo, publicKey) {
switch (algo) { switch (algo) {

Some files were not shown because too many files have changed in this diff Show More