diff --git a/.eslintrc.js b/.eslintrc.js index 5477843af..7fa4468b3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,18 +1,24 @@ module.exports = { + root: true, env: { browser: true, commonjs: true, node: true }, - extends: ['eslint-config-digitalbazaar'], + extends: [ + 'digitalbazaar' + ], parserOptions: { - ecmaVersion: 5 + ecmaVersion: 5, + sourceType: 'script' }, rules: { // overrides to support ES5, remove when updated to ES20xx 'no-unused-vars': 'warn', 'no-var': 'off', 'object-shorthand': 'off', - 'prefer-const': 'off' + 'prefer-const': 'off', + // fix when code is globally reformatted + 'max-len': 'off' } }; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..77be5fef2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,80 @@ +name: Main Checks + +on: [push] + +jobs: + test-node: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] + steps: + - uses: actions/checkout@v4 + # FIXME: Install/build with 16.x until webpack is updated + - name: Use Node.js 16.x + uses: actions/setup-node@v4 + with: + node-version: 16.x + - run: npm install + # FIXME: Run tests with target version until webpack is updated + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Run test with Node.js ${{ matrix.node-version }} + run: npm run test-node + test-karma: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + node-version: [16.x] + bundler: [webpack, browserify] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Run karma tests + run: npm run test-karma + env: + BUNDLER: ${{ matrix.bundler }} +# lint: +# runs-on: ubuntu-latest +# timeout-minutes: 10 +# strategy: +# matrix: +# node-version: [16.x] +# steps: +# - uses: actions/checkout@v4 +# - name: Use Node.js ${{ matrix.node-version }} +# uses: actions/setup-node@v4 +# with: +# node-version: ${{ matrix.node-version }} +# - run: npm install +# - name: Run eslint +# run: npm run lint + coverage: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Generate coverage report + run: npm run coverage-ci + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage/lcov.info + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 3c9cc9129..01519a399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ *.py[co] *.sw[nop] *~ -.bower.json .cdtproject .classpath .cproject .nyc_output .project .settings +.vscode TAGS coverage dist diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 24b8a8454..000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: node_js -node_js: - - "4" - - "6" - - "8" - - "10" - - "node" -sudo: false -install: npm install -script: - - if [ "x$BUNDLER" = "x" ]; then npm test; fi - - if [ "x$BUNDLER" != "x" ]; then npm run test-karma; fi -# only run karma tests for one node version -matrix: - include: - - node_js: "10" - env: BUNDLER=webpack - - node_js: "10" - env: BUNDLER=browserify -notifications: - email: - on_success: change - on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index 370fe3480..4f91e618e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,245 @@ Forge ChangeLog =============== +## 1.3.2 - 2025-11-25 + +### Security +- **HIGH**: ASN.1 Validator Desynchronization + - An Interpretation Conflict (CWE-436) vulnerability in node-forge versions + 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 + structures to desynchronize schema validations, yielding a semantic + divergence that may bypass downstream cryptographic verifications and + security decisions. + - Reported by Hunter Wodzenski. + - CVE ID: [CVE-2025-12816](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-12816) + - GHSA ID: [GHSA-5gfm-wpxj-wjgq](https://github.com/digitalbazaar/forge/security/advisories/GHSA-5gfm-wpxj-wjgq) +- **HIGH**: ASN.1 Unbounded Recursion + - An Uncontrolled Recursion (CWE-674) vulnerability in node-forge versions + 1.3.1 and below enables remote, unauthenticated attackers to craft deep + ASN.1 structures that trigger unbounded recursive parsing. This leads to a + Denial-of-Service (DoS) via stack exhaustion when parsing untrusted DER + inputs. + - Reported by Hunter Wodzenski. + - GHSA ID: [GHSA-554w-wpv2-vw27](https://github.com/digitalbazaar/forge/security/advisories/GHSA-554w-wpv2-vw27) +- **MODERATE**: ASN.1 OID Integer Truncation + - An Integer Overflow (CWE-190) vulnerability in node-forge versions 1.3.1 + and below enables remote, unauthenticated attackers to craft ASN.1 + structures containing OIDs with oversized arcs. These arcs may be decoded + as smaller, trusted OIDs due to 32-bit bitwise truncation, enabling the + bypass of downstream OID-based security decisions. + - Reported by Hunter Wodzenski. + - GHSA ID: [GHSA-65ch-62r8-g69g](https://github.com/digitalbazaar/forge/security/advisories/GHSA-65ch-62r8-g69g) + +### Fixed +- [asn1] Fix for vulnerability identified by CVE-2025-12816 PKCS#12 MAC + verification bypass due to missing macData enforcement and improper + asn1.validate routine. +- [asn1] Add `fromDer()` max recursion depth check. + - Add a `asn1.maxDepth` global configurable maximum depth of 256. + - Add a `asn1.fromDer()` per-call `maxDepth` option. + - **NOTE**: The default maximum is assumed to be higher than needed for valid + data. If this assumption is false then this could be a breaking change. + Please file an issue if there are use cases that need a higher maximum. + - **NOTE**: The per-call `maxDepth` parameter has not been exposed up through + all of the API stack due to the complexities involved. Please file an issue + if there are use cases that require this instead of changing the default + maximum. +- [asn1] Improve OID handling. + - Error on parsed OID values larger than `2**32 - 1`. + - Error on DER OID values larger than `2**53 - 1 `. + +## 1.3.1 - 2022-03-29 + +### Fixed +- RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters + for `sha*` algorithms and require `NULL` parameters for `md2` and `md5` + algorithms. + +## 1.3.0 - 2022-03-17 + +### Security +- Three RSA PKCS#1 v1.5 signature verification issues were reported by Moosa + Yahyazadeh (moosa-yahyazadeh@uiowa.edu). +- **HIGH**: Leniency in checking `digestAlgorithm` structure can lead to + signature forgery. + - The code is lenient in checking the digest algorithm structure. This can + allow a crafted structure that steals padding bytes and uses unchecked + portion of the PKCS#1 encoded message to forge a signature when a low + public exponent is being used. For more information, please see + ["Bleichenbacher's RSA signature forgery based on implementation + error"](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/) + by Hal Finney. + - CVE ID: [CVE-2022-24771](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24771) + - GHSA ID: [GHSA-cfm4-qjh2-4765](https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765) +- **HIGH**: Failing to check tailing garbage bytes can lead to signature + forgery. + - The code does not check for tailing garbage bytes after decoding a + `DigestInfo` ASN.1 structure. This can allow padding bytes to be removed + and garbage data added to forge a signature when a low public exponent is + being used. For more information, please see ["Bleichenbacher's RSA + signature forgery based on implementation + error"](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/) + by Hal Finney. + - CVE ID: [CVE-2022-24772](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24772) + - GHSA ID: [GHSA-x4jg-mjrx-434g](https://github.com/digitalbazaar/forge/security/advisories/GHSA-x4jg-mjrx-434g) +- **MEDIUM**: Leniency in checking type octet. + - `DigestInfo` is not properly checked for proper ASN.1 structure. This can + lead to successful verification with signatures that contain invalid + structures but a valid digest. + - CVE ID: [CVE-2022-24773](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24773) + - GHSA ID: [GHSA-2r2c-g63r-vccr](https://github.com/digitalbazaar/forge/security/advisories/GHSA-2r2c-g63r-vccr) + +### Fixed +- [asn1] Add fallback to pretty print invalid UTF8 data. +- [asn1] `fromDer` is now more strict and will default to ensuring all input + bytes are parsed or throw an error. A new option `parseAllBytes` can disable + this behavior. + - **NOTE**: The previous behavior is being changed since it can lead to + security issues with crafted inputs. It is possible that code doing custom + DER parsing may need to adapt to this new behavior and optional flag. +- [rsa] Add and use a validator to check for proper structure of parsed ASN.1 + `RSASSA-PKCS-v1_5` `DigestInfo` data. Additionally check that the hash + algorithm identifier is a known value from RFC 8017 + `PKCS1-v1-5DigestAlgorithms`. An invalid `DigestInfo` or algorithm identifier + will now throw an error. + - **NOTE**: The previous lenient behavior is being changed to be more strict + since it could lead to security issues with crafted inputs. It is possible + that code may have to handle the errors from these stricter checks. + +### Added +- [oid] Added missing RFC 8017 PKCS1-v1-5DigestAlgorithms algorithm + identifiers: + - `1.2.840.113549.2.2` / `md2` + - `2.16.840.1.101.3.4.2.4` / `sha224` + - `2.16.840.1.101.3.4.2.5` / `sha512-224` + - `2.16.840.1.101.3.4.2.6` / `sha512-256` + +## 1.2.1 - 2022-01-11 + +### Fixed +- [tests]: Load entire module to improve top-level testing and coverage + reporting. +- [log]: Refactor logging setup to avoid use of `URLSearchParams`. + +## 1.2.0 - 2022-01-07 + +### Fixed +- [x509] 'Expected' and 'Actual' issuers were backwards in verification failure + message. + +### Added +- [oid,x509]: Added OID `1.3.14.3.2.29 / sha1WithRSASignature` for sha1 with + RSA. Considered a deprecated equivalent to `1.2.840.113549.1.1.5 / + sha1WithRSAEncryption`. See [discussion and + links](https://github.com/digitalbazaar/forge/issues/825). + +### Changed +- [x509]: Reduce duplicate code. Add helper function to create a signature + digest given an signature algorithm OID. Add helper function to verify + signatures. + +## 1.1.0 - 2022-01-06 + +### Fixed +- [x509]: Correctly compute certificate issuer and subject hashes to match + behavior of openssl. +- [pem]: Accept certificate requests with "NEW" in the label. "BEGIN NEW + CERTIFICATE REQUEST" handled as "BEGIN CERTIFICATE REQUEST". + +## 1.0.0 - 2022-01-04 + +### Notes +- **1.0.0**! +- This project is over a decade old! Time for a 1.0.0 release. +- The URL related changes may expose bugs in some of the networking related + code (unrelated to the much wider used cryptography code). The automated and + manual test coverage for this code is weak at best. Issues or patches to + update the code or tests would be appreciated. + +### Removed +- **SECURITY**, **BREAKING**: Remove `forge.debug` API. The API has the + potential for prototype pollution. This API was only briefly used by the + maintainers for internal project debug purposes and was never intended to be + used with untrusted user inputs. This API was not documented or advertised + and is being removed rather than fixed. +- **SECURITY**, **BREAKING**: Remove `forge.util.parseUrl()` (and + `forge.http.parseUrl` alias) and use the [WHATWG URL + Standard](https://url.spec.whatwg.org/). `URL` is supported by modern + browsers and modern Node.js. This change is needed to address URL parsing + security issues. If `forge.util.parseUrl()` is used directly or through + `forge.xhr` or `forge.http` APIs, and support is needed for environments + without `URL` support, then a polyfill must be used. +- **BREAKING**: Remove `forge.task` API. This API was never used, documented, + or advertised by the maintainers. If anyone was using this API and wishes to + continue development it in other project, please let the maintainers know. + Due to use in the test suite, a modified version is located in + `tests/support/`. +- **BREAKING**: Remove `forge.util.makeLink`, `forge.util.makeRequest`, + `forge.util.parseFragment`, `forge.util.getQueryVariables`. Replace with + `URL`, `URLSearchParams`, and custom code as needed. + +### Changed +- **BREAKING**: Increase supported Node.js version to 6.13.0 for URL support. +- **BREAKING**: Renamed `master` branch to `main`. +- **BREAKING**: Release process updated to use tooling that prefixes versions + with `v`. Other tools, scripts, or scanners may need to adapt. +- **BREAKING**: Remove docs related to Bower and + [forge-dist](https://github.com/digitalbazaar/forge-dist). Install using + [another method](./README.md#installation). + +### Added +- OIDs for `surname`, `title`, and `givenName`. + +### Fixed +- **BREAKING**: OID 2.5.4.5 name fixed from `serialName` to `serialNumber`. + Depending on how applications used this id to name association it could cause + compatibility issues. + +## 0.10.0 - 2020-09-01 + +### Changed +- **BREAKING**: Node.js 4 no longer supported. The code *may* still work, and + non-invasive patches to keep it working will be considered. However, more + modern tools no longer support old Node.js versions making testing difficult. + +### Removed +- **BREAKING**: Remove `util.getPath`, `util.setPath`, and `util.deletePath`. + `util.setPath` had a potential prototype pollution security issue when used + with unsafe inputs. These functions are not used by `forge` itself. They date + from an early time when `forge` was targeted at providing general helper + functions. The library direction changed to be more focused on cryptography. + Many other excellent libraries are more suitable for general utilities. If + you need a replacement for these functions, consider `get`, `set`, and `unset` + from [lodash](https://lodash.com/). But also consider the potential similar + security issues with those APIs. + +## 0.9.2 - 2020-09-01 + +### Changed +- Added `util.setPath` security note to function docs and to README. + +### Notes +- **SECURITY**: The `util.setPath` function has the potential to cause + prototype pollution if used with unsafe input. + - This function is **not** used internally by `forge`. + - The rest of the library is unaffected by this issue. + - **Do not** use unsafe input with this function. + - Usage with known input should function as expected. (Including input + intentionally using potentially problematic keys.) + - No code changes will be made to address this issue in 0.9.x. The current + behavior *could* be considered a feature rather than a security issue. + 0.10.0 will be released that removes `util.getPath` and `util.setPath`. + Consider `get` and `set` from [lodash](https://lodash.com/) if you need + replacements. But also consider the potential similar security issues with + those APIs. + - https://snyk.io/vuln/SNYK-JS-NODEFORGE-598677 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7720 + +## 0.9.1 - 2019-09-26 + +### Fixed +- Ensure DES-CBC given IV is long enough for block size. + ## 0.9.0 - 2019-09-04 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5f08e8b1..299d2711c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Want to contribute to forge? Great! Here are a few notes: Code ---- -* In general, follow the current code style or the [Node.js Style Guide][]. +* In general, follow the current code style. * Read the [contributing](./README.md#contributing) notes. * Ensure [tests pass](./README.md#testing). @@ -15,5 +15,4 @@ Release Process Maintainers should refer to the [release instructions](./RELEASE.md). -[Node.js Style Guide]: http://nodeguide.com/style.html [Semantic Versioning]: http://semver.org/ diff --git a/README.md b/README.md index 40bf29561..06cff04c9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![npm package](https://nodei.co/npm/node-forge.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/node-forge/) -[![Build status](https://img.shields.io/travis/digitalbazaar/forge.svg?branch=master)](https://travis-ci.org/digitalbazaar/forge) +[![Build Status](https://github.com/digitalbazaar/forge/workflows/Main%20Checks/badge.svg)](https://github.com/digitalbazaar/forge/actions?query=workflow%3A%22Main+Checks%22) A native implementation of [TLS][] (and various other cryptographic tools) in [JavaScript][]. @@ -80,7 +80,6 @@ Documentation * [Tasks](#task) * [Utilities](#util) * [Logging](#log) -* [Debugging](#debug) * [Flash Networking Support](#flash) ### Other @@ -106,7 +105,7 @@ not be regularly updated. If you want to use forge with [Node.js][], it is available through `npm`: -https://npmjs.org/package/node-forge +https://www.npmjs.com/package/node-forge Installation: @@ -121,24 +120,12 @@ var forge = require('node-forge'); The npm package includes pre-built `forge.min.js`, `forge.all.min.js`, and `prime.worker.min.js` using the [UMD][] format. -### Bundle / Bower - -Each release is published in a separate repository as pre-built and minimized -basic forge bundles using the [UMD][] format. - -https://github.com/digitalbazaar/forge-dist - -This bundle can be used in many environments. In particular it can be installed -with [Bower][]: - - bower install forge - ### jsDelivr CDN To use it via [jsDelivr](https://www.jsdelivr.com/package/npm/node-forge) include this in your html: ```html - + ``` ### unpkg CDN @@ -146,7 +133,7 @@ To use it via [jsDelivr](https://www.jsdelivr.com/package/npm/node-forge) includ To use it via [unpkg](https://unpkg.com/#/) include this in your html: ```html - + ``` ### Development Requirements @@ -1452,7 +1439,7 @@ __Examples__ ```js // generate a key pair -var keys = forge.pki.rsa.generateKeyPair(1024); +var keys = forge.pki.rsa.generateKeyPair(2048); // create a certification request (CSR) var csr = forge.pki.createCertificationRequest(); @@ -1969,10 +1956,6 @@ var nodeBuffer = Buffer.from(forgeBuffer.getBytes(), 'binary'); // make sure you specify the encoding as 'binary' var nodeBuffer = Buffer.from('CAFE', 'hex'); var forgeBuffer = forge.util.createBuffer(nodeBuffer.toString('binary')); - -// parse a URL -var parsed = forge.util.parseUrl('http://example.com/foo?bar=baz'); -// parsed.scheme, parsed.host, parsed.port, parsed.path, parsed.fullHost ``` @@ -1988,19 +1971,6 @@ __Examples__ // TODO ``` - - -### Debugging - -Provides storage of debugging information normally inaccessible in -closures for viewing/investigation. - -__Examples__ - -```js -// TODO -``` - ### Flash Networking Support @@ -2021,8 +1991,8 @@ When using this code please keep the following in mind: runtime characteristics, runtime optimization, code optimization, code minimization, code obfuscation, bundling tools, possible bugs, the Forge code itself, and so on. -- If using pre-built bundles from [Bower][] or similar be aware someone else - ran the tools to create those files. +- If using pre-built bundles from [NPM][], another CDN, or similar, be aware + someone else ran the tools to create those files. - Use a secure transport channel such as [TLS][] to load scripts and consider using additional security mechanisms such as [Subresource Integrity][] script attributes. @@ -2048,12 +2018,13 @@ Contact * Code: https://github.com/digitalbazaar/forge * Bugs: https://github.com/digitalbazaar/forge/issues * Email: support@digitalbazaar.com -* IRC: [#forgejs][] on [freenode][] +* IRC: [#forgejs][] on [Libera.Chat][] (people may also be on [freenode][] for + historical reasons). Donations --------- -Financial support is welcome and helps contribute to futher development: +Financial support is welcome and helps contribute to further development: * For [PayPal][] please send to paypal@digitalbazaar.com. * Something else? Please contact support@digitalbazaar.com. @@ -2063,7 +2034,6 @@ Financial support is welcome and helps contribute to futher development: [3DES]: https://en.wikipedia.org/wiki/Triple_DES [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard [ASN.1]: https://en.wikipedia.org/wiki/ASN.1 -[Bower]: https://bower.io/ [Browserify]: http://browserify.org/ [CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation [CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation @@ -2076,7 +2046,9 @@ Financial support is welcome and helps contribute to futher development: [HMAC]: https://en.wikipedia.org/wiki/HMAC [JavaScript]: https://en.wikipedia.org/wiki/JavaScript [Karma]: https://karma-runner.github.io/ +[Libera.Chat]: https://libera.chat/ [MD5]: https://en.wikipedia.org/wiki/MD5 +[NPM]: https://www.npmjs.com/ [Node.js]: https://nodejs.org/ [OFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation [PKCS#10]: https://en.wikipedia.org/wiki/Certificate_signing_request diff --git a/RELEASE.md b/RELEASE.md index c90a249f4..92c01d248 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,77 +1,19 @@ Forge Release Process ===================== -Versioning ----------- +Prepare a Release +----------------- * Follow the [Semantic Versioning][] guidelines. -* Use version X.Y.Z-dev in dev mode. -* Use version X.Y.Z for releases. - -Master Branch Release Process ------------------------------ - * Ensure [tests pass](./README.md#testing). +* Ensure [CHANGELOG.md](./CHANGELOG.md) is up-to-date using [Keep a + CHANGELOG][] style. -## Update the main repository: - -* Commit changes. -* Update the [CHANGELOG](./CHANGELOG.md) as needed using rougly - [Keep a CHANGELOG][] style. -* `$EDITOR package.json`: update to release version and remove `-dev` suffix. -* `git commit package.json -m "Release {version}."` -* `git tag {version}` -* `$EDITOR package.json`: update to next version and add `-dev` suffix. -* `git commit package.json -m "Start {next-version}."` -* `git push` -* `git push --tags` - -## Publish to NPM: - -To ensure a clean upload, use a clean updated checkout, and run the following: - -* `git checkout {version}` -* `npm install` -* `npm publish` - -## Update bundled distribution - -This is kept in a different repository to avoid the accumulated size when -adding per-release bundles. - -* Checkout [forge-dist][]. -* Build a clean Forge version you want to distribute: - * `git checkout {version}` - * `npm install` - * `npm run build` -* Copy files to `forge-dist`: - * `cp dist/forge.min.js{,.map} dist/prime.worker.min.js{,.map} FORGEDIST/dist/` -* Release `forge-dist`: - * `git commit -a -m "Release {version}."` - * `git tag {version}` - * `git push` - * `git push origin {version}` - -Older Branch Release Process ----------------------------- - -In order to provide support for Bower (and similar) for current built bundle -releases and historical releases the [forge-dist][] repository needs to be -updated with code changes and tags from the main repository. Once a historical -branch, like 0.6.x, on the main repository is updated and tagged, do the -following: +Publish to NPM +-------------- -* Checkout [forge-dist][]. -* Setup an upstream branch: - * `git remote add upstream git@github.com:digitalbazaar/forge.git` - * `git fetch upstream` -* Merge changes: - * `git checkout 0.6.x` - * `git merge upstream/0.6.x` -* Push code and tag(s): - * `git push` - * `git push origin {version}` +As of Forge 1.0.0 publishing is performed using the `pubnpm` script from +https://github.com/digitalbazaar/publish-script. -[Keep a CHANGELOG]: http://keepachangelog.com/ -[Semantic Versioning]: http://semver.org/ -[forge-dist]: https://github.com/digitalbazaar/forge-dist +[Keep a CHANGELOG]: https://keepachangelog.com/ +[Semantic Versioning]: https://semver.org/ diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..090cbbc12 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to security@digitalbazaar.com. diff --git a/examples/create-cert.js b/examples/create-cert.js index 365f5a782..03df72c95 100644 --- a/examples/create-cert.js +++ b/examples/create-cert.js @@ -1,7 +1,7 @@ var forge = require('..'); -console.log('Generating 1024-bit key-pair...'); -var keys = forge.pki.rsa.generateKeyPair(1024); +console.log('Generating 2048-bit key-pair...'); +var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); console.log('Creating self-signed certificate...'); diff --git a/examples/create-csr.js b/examples/create-csr.js index e5be773a2..8961a31fd 100644 --- a/examples/create-csr.js +++ b/examples/create-csr.js @@ -1,7 +1,7 @@ var forge = require('..'); -console.log('Generating 1024-bit key-pair...'); -var keys = forge.pki.rsa.generateKeyPair(1024); +console.log('Generating 2048-bit key-pair...'); +var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); console.log('Creating certification request (CSR) ...'); diff --git a/examples/create-pkcs12.js b/examples/create-pkcs12.js index 1125fc0f1..d41965fb1 100644 --- a/examples/create-pkcs12.js +++ b/examples/create-pkcs12.js @@ -2,8 +2,8 @@ var forge = require('..'); try { // generate a keypair - console.log('Generating 1024-bit key-pair...'); - var keys = forge.pki.rsa.generateKeyPair(1024); + console.log('Generating 2048-bit key-pair...'); + var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created.'); // create a certificate diff --git a/examples/sign-p7.js b/examples/sign-p7.js index 406ce787e..73d07e566 100644 --- a/examples/sign-p7.js +++ b/examples/sign-p7.js @@ -42,8 +42,8 @@ function createSigner(name) { console.log('Creating signer "' + name + '"...'); // generate a keypair - console.log('Generating 1024-bit key-pair...'); - var keys = forge.pki.rsa.generateKeyPair(1024); + console.log('Generating 2048-bit key-pair...'); + var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created:'); console.log(forge.pki.privateKeyToPem(keys.privateKey)); console.log(forge.pki.publicKeyToPem(keys.publicKey)); diff --git a/flash/SocketPool.as b/flash/SocketPool.as index f61dfa855..384400fd5 100644 --- a/flash/SocketPool.as +++ b/flash/SocketPool.as @@ -42,7 +42,7 @@ package private var mEventDispatcher:EventDispatcher; /** - * Creates a new, unitialized SocketPool. + * Creates a new, uninitialized SocketPool. * * @throws Error - if no external interface is available to provide * javascript access. diff --git a/karma.conf.js b/karma.conf.js index 996a5adf4..a61407dc1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -55,6 +55,7 @@ module.exports = function(config) { }, webpack: { + mode: 'development', devtool: 'inline-source-map', node: { Buffer: false, diff --git a/lib/aes.js b/lib/aes.js index 3c1ddb29b..5bdd2acc3 100644 --- a/lib/aes.js +++ b/lib/aes.js @@ -325,7 +325,7 @@ var imix; // inverse mix-columns table * The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0. * * Addition is performed by XOR'ing like powers of x. Multiplication - * is performed in two steps, the first is an algebriac expansion as + * is performed in two steps, the first is an algebraic expansion as * you would do normally (where addition is XOR). But the result is * a polynomial larger than 3 degrees and thus it cannot fit in a word. So * next the result is modularly reduced by an AES-specific polynomial of diff --git a/lib/asn1.js b/lib/asn1.js index e0fea0e08..e3cb5007b 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -178,6 +178,11 @@ asn1.Type = { BMPSTRING: 30 }; +/** + * Sets the default maximum recursion depth when parsing ASN.1 structures. + */ +asn1.maxDepth = 256; + /** * Creates a new asn1 object. * @@ -411,12 +416,18 @@ var _getValueLength = function(bytes, remaining) { * @param [options] object with options or boolean strict flag * [strict] true to be strict when checking value lengths, false to * allow truncated values (default: true). + * [parseAllBytes] true to ensure all bytes are parsed + * (default: true) * [decodeBitStrings] true to attempt to decode the content of * BIT STRINGs (not OCTET STRINGs) using strict mode. Note that * without schema support to understand the data context this can * erroneously decode values that happen to be valid ASN.1. This * flag will be deprecated or removed as soon as schema support is * available. (default: true) + * [maxDepth] override asn1.maxDepth recursion limit + * (default: asn1.maxDepth) + * + * @throws Will throw an error for various malformed input conditions. * * @return the parsed asn1 object. */ @@ -424,28 +435,44 @@ asn1.fromDer = function(bytes, options) { if(options === undefined) { options = { strict: true, + parseAllBytes: true, decodeBitStrings: true }; } if(typeof options === 'boolean') { options = { strict: options, + parseAllBytes: true, decodeBitStrings: true }; } if(!('strict' in options)) { options.strict = true; } + if(!('parseAllBytes' in options)) { + options.parseAllBytes = true; + } if(!('decodeBitStrings' in options)) { options.decodeBitStrings = true; } + if(!('maxDepth' in options)) { + options.maxDepth = asn1.maxDepth; + } // wrap in buffer if needed if(typeof bytes === 'string') { bytes = forge.util.createBuffer(bytes); } - return _fromDer(bytes, bytes.length(), 0, options); + var byteCount = bytes.length(); + var value = _fromDer(bytes, bytes.length(), 0, options); + if(options.parseAllBytes && bytes.length() !== 0) { + var error = new Error('Unparsed DER bytes remain after ASN.1 parsing.'); + error.byteCount = byteCount; + error.remaining = bytes.length(); + throw error; + } + return value; }; /** @@ -459,6 +486,12 @@ asn1.fromDer = function(bytes, options) { * @return the parsed asn1 object. */ function _fromDer(bytes, remaining, depth, options) { + + // check depth limit + if(depth >= options.maxDepth) { + throw new Error('ASN.1 parsing error: Max depth exceeded.'); + } + // temporary storage for consumption calculations var start; @@ -566,7 +599,6 @@ function _fromDer(bytes, remaining, depth, options) { start = bytes.length(); var subOptions = { // enforce strict mode to avoid parsing ASN.1 from plain data - verbose: options.verbose, strict: true, decodeBitStrings: true }; @@ -615,6 +647,7 @@ function _fromDer(bytes, remaining, depth, options) { } } else { value = bytes.getBytes(length); + remaining -= length; } } @@ -756,6 +789,10 @@ asn1.oidToDer = function(oid) { last = true; valueBytes = []; value = parseInt(values[i], 10); + // TODO: Change bitwise logic to allow larger values. + if(value > 0xffffffff) { + throw new Error('OID value too large; max is 32-bits.'); + } do { b = value & 0x7F; value = value >>> 7; @@ -801,8 +838,13 @@ asn1.derToOid = function(bytes) { // the last byte for each value var value = 0; while(bytes.length() > 0) { + // error if 7b shift would exceed Number.MAX_SAFE_INTEGER + // (Number.MAX_SAFE_INTEGER / 128) + if(value > 0x3fffffffffff) { + throw new Error('OID value too large; max is 53-bits.'); + } b = bytes.getByte(); - value = value << 7; + value = value * 128; // not the last byte for the value if(b & 0x80) { value += b & 0x7F; @@ -1148,22 +1190,65 @@ asn1.validate = function(obj, v, capture, errors) { if(v.value && forge.util.isArray(v.value)) { var j = 0; for(var i = 0; rval && i < v.value.length; ++i) { - rval = v.value[i].optional || false; - if(obj.value[j]) { - rval = asn1.validate(obj.value[j], v.value[i], capture, errors); - if(rval) { - ++j; - } else if(v.value[i].optional) { + var schemaItem = v.value[i]; + rval = !!schemaItem.optional; + + // current child in the object + var objChild = obj.value[j]; + + // if there is no child left to match + if(!objChild) { + // if optional, ok (rval already true), else fail below + if(!schemaItem.optional) { + rval = false; + if(errors) { + errors.push('[' + v.name + '] ' + + 'Missing required element. Expected tag class "' + + schemaItem.tagClass + '", type "' + schemaItem.type + '"'); + } + } + continue; + } + + // If schema explicitly specifies tagClass/type, do a quick structural check + // to avoid unnecessary recursion/side-effects when tags clearly don't match. + var schemaHasTag = (typeof schemaItem.tagClass !== 'undefined' && + typeof schemaItem.type !== 'undefined'); + + if(schemaHasTag && + (objChild.tagClass !== schemaItem.tagClass || objChild.type !== schemaItem.type)) { + // Tags do not match. + if(schemaItem.optional) { + // Skip this schema element (don't consume objChild; don't call recursive validate). rval = true; + continue; + } else { + // Required schema item mismatched - fail. + rval = false; + if(errors) { + errors.push('[' + v.name + '] ' + + 'Tag mismatch. Expected (' + + schemaItem.tagClass + ',' + schemaItem.type + '), got (' + + objChild.tagClass + ',' + objChild.type + ')'); + } + break; } } - if(!rval && errors) { - errors.push( - '[' + v.name + '] ' + - 'Tag class "' + v.tagClass + '", type "' + - v.type + '" expected value length "' + - v.value.length + '", got "' + - obj.value.length + '"'); + + // Tags are compatible (or schema did not declare tags) - dive into recursive validate. + var childRval = asn1.validate(objChild, schemaItem, capture, errors); + if(childRval) { + // consume this child + ++j; + rval = true; + } else if(schemaItem.optional) { + // validation failed but element is optional => skip schema item (don't consume child) + rval = true; + } else { + // required item failed + rval = false; + // errors should already be populated by recursive call; keep failing + break; } } } @@ -1209,7 +1294,8 @@ asn1.validate = function(obj, v, capture, errors) { if(obj.type !== v.type) { errors.push( '[' + v.name + '] ' + - 'Expected type "' + v.type + '", got "' + obj.type + '"'); + 'Expected type "' + v.type + '", got "' + + obj.type + '"'); } } return rval; @@ -1391,7 +1477,16 @@ asn1.prettyPrint = function(obj, level, indentation) { } rval += '0x' + forge.util.bytesToHex(obj.value); } else if(obj.type === asn1.Type.UTF8) { - rval += forge.util.decodeUtf8(obj.value); + try { + rval += forge.util.decodeUtf8(obj.value); + } catch(e) { + if(e.message === 'URI malformed') { + rval += + '0x' + forge.util.bytesToHex(obj.value) + ' (malformed UTF8)'; + } else { + throw e; + } + } } else if(obj.type === asn1.Type.PRINTABLESTRING || obj.type === asn1.Type.IA5String) { rval += obj.value; diff --git a/lib/cipherModes.js b/lib/cipherModes.js index 1f2d41ddc..339915cc1 100644 --- a/lib/cipherModes.js +++ b/lib/cipherModes.js @@ -119,7 +119,7 @@ modes.cbc.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } else { // save IV as "previous" block - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._prev = this._iv.slice(0); } }; @@ -215,7 +215,7 @@ modes.cfb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -359,7 +359,7 @@ modes.ofb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -444,7 +444,7 @@ modes.ctr.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -954,7 +954,7 @@ modes.gcm.prototype.generateSubHashTable = function(mid, bits) { /** Utility functions */ -function transformIV(iv) { +function transformIV(iv, blockSize) { if(typeof iv === 'string') { // convert iv string into byte buffer iv = forge.util.createBuffer(iv); @@ -968,9 +968,21 @@ function transformIV(iv) { iv.putByte(tmp[i]); } } + + if(iv.length() < blockSize) { + throw new Error( + 'Invalid IV length; got ' + iv.length() + + ' bytes and expected ' + blockSize + ' bytes.'); + } + if(!forge.util.isArray(iv)) { // convert iv byte buffer into 32-bit integer array - iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]; + var ints = []; + var blocks = blockSize / 4; + for(var i = 0; i < blocks; ++i) { + ints.push(iv.getInt32()); + } + iv = ints; } return iv; diff --git a/lib/debug.js b/lib/debug.js deleted file mode 100644 index 26756350e..000000000 --- a/lib/debug.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Debugging support for web applications. - * - * @author David I. Lehn - * - * Copyright 2008-2013 Digital Bazaar, Inc. - */ -var forge = require('./forge'); - -/* DEBUG API */ -module.exports = forge.debug = forge.debug || {}; - -// Private storage for debugging. -// Useful to expose data that is otherwise unviewable behind closures. -// NOTE: remember that this can hold references to data and cause leaks! -// format is "forge._debug.. = data" -// Example: -// (function() { -// var cat = 'forge.test.Test'; // debugging category -// var sState = {...}; // local state -// forge.debug.set(cat, 'sState', sState); -// })(); -forge.debug.storage = {}; - -/** - * Gets debug data. Omit name for all cat data Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to get (optional). - * @return object with requested debug data or undefined. - */ -forge.debug.get = function(cat, name) { - var rval; - if(typeof(cat) === 'undefined') { - rval = forge.debug.storage; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - rval = forge.debug.storage[cat]; - } else { - rval = forge.debug.storage[cat][name]; - } - } - return rval; -}; - -/** - * Sets debug data. - * - * @param cat name of debugging category. - * @param name name of data to set. - * @param data data to set. - */ -forge.debug.set = function(cat, name, data) { - if(!(cat in forge.debug.storage)) { - forge.debug.storage[cat] = {}; - } - forge.debug.storage[cat][name] = data; -}; - -/** - * Clears debug data. Omit name for all cat data. Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to clear or omit to clear entire category. - */ -forge.debug.clear = function(cat, name) { - if(typeof(cat) === 'undefined') { - forge.debug.storage = {}; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - delete forge.debug.storage[cat]; - } else { - delete forge.debug.storage[cat][name]; - } - } -}; diff --git a/lib/http.js b/lib/http.js index 1dcb0a65e..fe52986b1 100644 --- a/lib/http.js +++ b/lib/http.js @@ -6,7 +6,6 @@ * Copyright (c) 2010-2014 Digital Bazaar, Inc. All rights reserved. */ var forge = require('./forge'); -require('./debug'); require('./tls'); require('./util'); @@ -16,11 +15,6 @@ var http = module.exports = forge.http = forge.http || {}; // logging category var cat = 'forge.http'; -// add array of clients to debug storage -if(forge.debug) { - forge.debug.set('forge.http', 'clients', []); -} - // normalizes an http header field name var _normalize = function(name) { return name.toLowerCase().replace(/(^.)|(-.)/g, @@ -39,8 +33,8 @@ var _getStorageId = function(client) { // browsers (if this is undesirable) // navigator.userAgent return 'forge.http.' + - client.url.scheme + '.' + - client.url.host + '.' + + client.url.protocol.slice(0, -1) + '.' + + client.url.hostname + '.' + client.url.port; }; @@ -127,7 +121,7 @@ var _doRequest = function(client, socket) { // connect socket.options.request.connectTime = +new Date(); socket.connect({ - host: client.url.host, + host: client.url.hostname, port: client.url.port, policyPort: client.policyPort, policyUrl: client.policyUrl @@ -316,7 +310,7 @@ var _initSocket = function(client, socket, tlsOptions) { // prime socket by connecting and caching TLS session, will do // next request from there socket.connect({ - host: client.url.host, + host: client.url.hostname, port: client.url.port, policyPort: client.policyPort, policyUrl: client.policyUrl @@ -411,7 +405,7 @@ var _readCookies = function(client, response) { * * @param options: * url: the url to connect to (scheme://host:port). - * socketPool: the flash socket pool to use. + * socketPool: the flash socket pool to use. * policyPort: the flash policy port to use (if other than the * socket pool default), use 0 for flash default. * policyUrl: the flash policy file URL to use (if provided will @@ -447,8 +441,10 @@ http.createClient = function(options) { // get scheme, host, and port from url options.url = (options.url || window.location.protocol + '//' + window.location.host); - var url = http.parseUrl(options.url); - if(!url) { + var url; + try { + url = new URL(options.url); + } catch(e) { var error = new Error('Invalid url.'); error.details = {url: options.url}; throw error; @@ -475,7 +471,7 @@ http.createClient = function(options) { // idle sockets idle: [], // whether or not the connections are secure - secure: (url.scheme === 'https'), + secure: (url.protocol === 'https:'), // cookie jar (key'd off of name and then path, there is only 1 domain // and one setting for secure per client so name+path is unique) cookies: {}, @@ -484,11 +480,6 @@ http.createClient = function(options) { true : options.persistCookies }; - // add client to debug storage - if(forge.debug) { - forge.debug.get('forge.http', 'clients').push(client); - } - // load cookies from disk _loadCookies(client); @@ -508,7 +499,7 @@ http.createClient = function(options) { if(depth === 0 && verified === true) { // compare common name to url host var cn = certs[depth].subject.getField('CN'); - if(cn === null || client.url.host !== cn.value) { + if(cn === null || client.url.hostname !== cn.value) { verified = { message: 'Certificate common name does not match url host.' }; @@ -523,7 +514,7 @@ http.createClient = function(options) { tlsOptions = { caStore: caStore, cipherSuites: options.cipherSuites || null, - virtualHost: options.virtualHost || url.host, + virtualHost: options.virtualHost || url.hostname, verify: options.verify || _defaultCertificateVerify, getCertificate: options.getCertificate || null, getPrivateKey: options.getPrivateKey || null, @@ -563,7 +554,7 @@ http.createClient = function(options) { client.send = function(options) { // add host header if not set if(options.request.getField('Host') === null) { - options.request.setField('Host', client.url.fullHost); + options.request.setField('Host', client.url.origin); } // set default dummy handlers @@ -1318,15 +1309,6 @@ http.createResponse = function() { return response; }; -/** - * Parses the scheme, host, and port from an http(s) url. - * - * @param str the url string. - * - * @return the parsed url object or null if the url is invalid. - */ -http.parseUrl = forge.util.parseUrl; - /** * Returns true if the given url is within the given cookie's domain. * @@ -1347,11 +1329,11 @@ http.withinCookieDomain = function(url, cookie) { // ensure domain starts with a '.' // parse URL as necessary if(typeof url === 'string') { - url = http.parseUrl(url); + url = new URL(url); } - // add '.' to front of URL host to match against domain - var host = '.' + url.host; + // add '.' to front of URL hostname to match against domain + var host = '.' + url.hostname; // if the host ends with domain then it falls within it var idx = host.lastIndexOf(domain); diff --git a/lib/index.js b/lib/index.js index ea8c14cf9..6cdd5a9cc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,7 +10,6 @@ require('./aes'); require('./aesCipherSuites'); require('./asn1'); require('./cipher'); -require('./debug'); require('./des'); require('./ed25519'); require('./hmac'); @@ -30,6 +29,5 @@ require('./pss'); require('./random'); require('./rc2'); require('./ssh'); -require('./task'); require('./tls'); require('./util'); diff --git a/lib/log.js b/lib/log.js index 8d36f4a89..4ef700591 100644 --- a/lib/log.js +++ b/lib/log.js @@ -286,7 +286,7 @@ if(typeof(console) !== 'undefined' && 'log' in console) { } /* - * Check for logging control query vars. + * Check for logging control query vars in current URL. * * console.level= * Set's the console log level by name. Useful to override defaults and @@ -297,16 +297,18 @@ if(typeof(console) !== 'undefined' && 'log' in console) { * after console.level is processed. Useful to force a level of verbosity * that could otherwise be limited by a user config. */ -if(sConsoleLogger !== null) { - var query = forge.util.getQueryVariables(); - if('console.level' in query) { +if(sConsoleLogger !== null && + typeof window !== 'undefined' && window.location +) { + var query = new URL(window.location.href).searchParams; + if(query.has('console.level')) { // set with last value forge.log.setLevel( - sConsoleLogger, query['console.level'].slice(-1)[0]); + sConsoleLogger, query.get('console.level').slice(-1)[0]); } - if('console.lock' in query) { + if(query.has('console.lock')) { // set with last value - var lock = query['console.lock'].slice(-1)[0]; + var lock = query.get('console.lock').slice(-1)[0]; if(lock == 'true') { forge.log.lock(sConsoleLogger); } diff --git a/lib/oids.js b/lib/oids.js index 6a937f571..d1504eb16 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -42,9 +42,15 @@ _IN('1.2.840.10040.4.3', 'dsa-with-sha1'); _IN('1.3.14.3.2.7', 'desCBC'); _IN('1.3.14.3.2.26', 'sha1'); +// Deprecated equivalent of sha1WithRSAEncryption +_IN('1.3.14.3.2.29', 'sha1WithRSASignature'); _IN('2.16.840.1.101.3.4.2.1', 'sha256'); _IN('2.16.840.1.101.3.4.2.2', 'sha384'); _IN('2.16.840.1.101.3.4.2.3', 'sha512'); +_IN('2.16.840.1.101.3.4.2.4', 'sha224'); +_IN('2.16.840.1.101.3.4.2.5', 'sha512-224'); +_IN('2.16.840.1.101.3.4.2.6', 'sha512-256'); +_IN('1.2.840.113549.2.2', 'md2'); _IN('1.2.840.113549.2.5', 'md5'); // pkcs#7 content types @@ -104,16 +110,19 @@ _IN('2.16.840.1.101.3.4.1.42', 'aes256-CBC'); // certificate issuer/subject OIDs _IN('2.5.4.3', 'commonName'); -_IN('2.5.4.5', 'serialName'); +_IN('2.5.4.4', 'surname'); +_IN('2.5.4.5', 'serialNumber'); _IN('2.5.4.6', 'countryName'); _IN('2.5.4.7', 'localityName'); _IN('2.5.4.8', 'stateOrProvinceName'); _IN('2.5.4.9', 'streetAddress'); _IN('2.5.4.10', 'organizationName'); _IN('2.5.4.11', 'organizationalUnitName'); +_IN('2.5.4.12', 'title'); _IN('2.5.4.13', 'description'); _IN('2.5.4.15', 'businessCategory'); _IN('2.5.4.17', 'postalCode'); +_IN('2.5.4.42', 'givenName'); _IN('1.3.6.1.4.1.311.60.2.1.2', 'jurisdictionOfIncorporationStateOrProvinceName'); _IN('1.3.6.1.4.1.311.60.2.1.3', 'jurisdictionOfIncorporationCountryName'); diff --git a/lib/pbe.js b/lib/pbe.js index cf8456ba6..f3cfa70dd 100644 --- a/lib/pbe.js +++ b/lib/pbe.js @@ -672,7 +672,7 @@ pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) { D.fillWithByte(id, v); /* 2. Concatenate copies of the salt together to create a string S of length - v * ceil(s / v) bytes (the final copy of the salt may be trunacted + v * ceil(s / v) bytes (the final copy of the salt may be truncated to create S). Note that if the salt is the empty string, then so is S. */ var Slen = v * Math.ceil(s / v); diff --git a/lib/pem.js b/lib/pem.js index aed8bdf92..1992bc77b 100644 --- a/lib/pem.js +++ b/lib/pem.js @@ -106,8 +106,15 @@ pem.decode = function(str) { break; } + // accept "NEW CERTIFICATE REQUEST" as "CERTIFICATE REQUEST" + // https://datatracker.ietf.org/doc/html/rfc7468#section-7 + var type = match[1]; + if(type === 'NEW CERTIFICATE REQUEST') { + type = 'CERTIFICATE REQUEST'; + } + var msg = { - type: match[1], + type: type, procType: null, contentDomain: null, dekInfo: null, diff --git a/lib/pkcs12.js b/lib/pkcs12.js index cd06c494a..dee8b36ad 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -474,6 +474,9 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { if(macValue.getBytes() !== capture.macDigest) { throw new Error('PKCS#12 MAC could not be verified. Invalid password?'); } + } else if(Array.isArray(obj.value) && obj.value.length > 2) { + /* This is pfx data that should have mac and verify macDigest */ + throw new Error('Invalid PKCS#12. macData field present but MAC was not validated.'); } _decodeAuthenticatedSafe(pfx, data.value, strict, password); diff --git a/lib/pkcs7.js b/lib/pkcs7.js index bb87de363..3a5d845c5 100644 --- a/lib/pkcs7.js +++ b/lib/pkcs7.js @@ -837,7 +837,7 @@ function _recipientFromAsn1(obj) { serialNumber: forge.util.createBuffer(capture.serial).toHex(), encryptedContent: { algorithm: asn1.derToOid(capture.encAlgorithm), - parameter: capture.encParameter.value, + parameter: capture.encParameter ? capture.encParameter.value : undefined, content: capture.encKey } }; @@ -1124,8 +1124,11 @@ function _encryptedContentToAsn1(ec) { asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, asn1.oidToDer(ec.algorithm).getBytes()), // Parameters (IV) - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, - ec.parameter.getBytes()) + !ec.parameter ? + undefined : + asn1.create( + asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, + ec.parameter.getBytes()) ]), // [0] EncryptedContent asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ diff --git a/lib/pkcs7asn1.js b/lib/pkcs7asn1.js index a2ac01f85..0e13c8915 100644 --- a/lib/pkcs7asn1.js +++ b/lib/pkcs7asn1.js @@ -397,7 +397,8 @@ p7v.recipientInfoValidator = { name: 'RecipientInfo.keyEncryptionAlgorithm.parameter', tagClass: asn1.Class.UNIVERSAL, constructed: false, - captureAsn1: 'encParameter' + captureAsn1: 'encParameter', + optional: true }] }, { name: 'RecipientInfo.encryptedKey', diff --git a/lib/prime.worker.js b/lib/prime.worker.js index ce1355d9a..b145f8aeb 100644 --- a/lib/prime.worker.js +++ b/lib/prime.worker.js @@ -56,7 +56,7 @@ function findPrime(data) { } function isProbablePrime(n) { - // divide by low primes, ignore even checks, etc (n alread aligned properly) + // divide by low primes, ignore even checks, etc (n already aligned properly) var i = 1; while(i < LOW_PRIMES.length) { var m = LOW_PRIMES[i]; diff --git a/lib/prng.js b/lib/prng.js index c2f5f0518..d3bd22e05 100644 --- a/lib/prng.js +++ b/lib/prng.js @@ -317,7 +317,7 @@ prng.create = function(plugin) { // throw in more pseudo random next = seed >>> (i << 3); next ^= Math.floor(Math.random() * 0x0100); - b.putByte(String.fromCharCode(next & 0xFF)); + b.putByte(next & 0xFF); } } } diff --git a/lib/rsa.js b/lib/rsa.js index 7c67917ce..b207a6385 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -264,6 +264,43 @@ var publicKeyValidator = forge.pki.rsa.publicKeyValidator = { }] }; +// validator for a DigestInfo structure +var digestInfoValidator = { + name: 'DigestInfo', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'DigestInfo.DigestAlgorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'DigestInfo.DigestAlgorithm.algorithmIdentifier', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OID, + constructed: false, + capture: 'algorithmIdentifier' + }, { + // NULL parameters + name: 'DigestInfo.DigestAlgorithm.parameters', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.NULL, + // captured only to check existence for md2 and md5 + capture: 'parameters', + optional: true, + constructed: false + }] + }, { + // digest + name: 'DigestInfo.digest', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OCTETSTRING, + constructed: false, + capture: 'digest' + }] +}; + /** * Wrap digest in DigestInfo object. * @@ -279,7 +316,7 @@ var publicKeyValidator = forge.pki.rsa.publicKeyValidator = { * * @param md the message digest object with the hash to sign. * - * @return the encoded message (ready for RSA encrytion) + * @return the encoded message (ready for RSA encryption) */ var emsaPkcs1v15encode = function(md) { // get the oid for the algorithm @@ -461,7 +498,7 @@ var _modPow = function(x, key, pub) { * * The parameter bt controls whether to put padding bytes before the * message passed in. Set bt to either true or false to disable padding - * completely (in order to handle e.g. EMSA-PSS encoding seperately before), + * completely (in order to handle e.g. EMSA-PSS encoding separately before), * signaling whether the encryption operation is a public key operation * (i.e. encrypting data) or not, i.e. private key operation (data signing). * @@ -1092,15 +1129,27 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { * a Forge PSS object for RSASSA-PSS, * 'NONE' or null for none, DigestInfo will not be expected, but * PKCS#1 v1.5 padding will still be used. + * @param options optional verify options + * _parseAllDigestBytes testing flag to control parsing of all + * digest bytes. Unsupported and not for general usage. + * (default: true) * * @return true if the signature was verified, false if not. */ - key.verify = function(digest, signature, scheme) { + key.verify = function(digest, signature, scheme, options) { if(typeof scheme === 'string') { scheme = scheme.toUpperCase(); } else if(scheme === undefined) { scheme = 'RSASSA-PKCS1-V1_5'; } + if(options === undefined) { + options = { + _parseAllDigestBytes: true + }; + } + if(!('_parseAllDigestBytes' in options)) { + options._parseAllDigestBytes = true; + } if(scheme === 'RSASSA-PKCS1-V1_5') { scheme = { @@ -1108,9 +1157,51 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { // remove padding d = _decodePkcs1_v1_5(d, key, true); // d is ASN.1 BER-encoded DigestInfo - var obj = asn1.fromDer(d); + var obj = asn1.fromDer(d, { + parseAllBytes: options._parseAllDigestBytes + }); + + // validate DigestInfo + var capture = {}; + var errors = []; + if(!asn1.validate(obj, digestInfoValidator, capture, errors)) { + var error = new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value.'); + error.errors = errors; + throw error; + } + // check hash algorithm identifier + // see PKCS1-v1-5DigestAlgorithms in RFC 8017 + // FIXME: add support to validator for strict value choices + var oid = asn1.derToOid(capture.algorithmIdentifier); + if(!(oid === forge.oids.md2 || + oid === forge.oids.md5 || + oid === forge.oids.sha1 || + oid === forge.oids.sha224 || + oid === forge.oids.sha256 || + oid === forge.oids.sha384 || + oid === forge.oids.sha512 || + oid === forge.oids['sha512-224'] || + oid === forge.oids['sha512-256'])) { + var error = new Error( + 'Unknown RSASSA-PKCS1-v1_5 DigestAlgorithm identifier.'); + error.oid = oid; + throw error; + } + + // special check for md2 and md5 that NULL parameters exist + if(oid === forge.oids.md2 || oid === forge.oids.md5) { + if(!('parameters' in capture)) { + throw new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value. ' + + 'Missing algorithm identifier NULL parameters.'); + } + } + // compare the given digest to the decrypted one - return digest === obj.value[1].value; + return digest === capture.digest; } }; } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { @@ -1546,7 +1637,7 @@ function _decodePkcs1_v1_5(em, key, pub, ml) { 1. The encryption block EB cannot be parsed unambiguously. 2. The padding string PS consists of fewer than eight octets - or is inconsisent with the block type BT. + or is inconsistent with the block type BT. 3. The decryption process is a public-key operation and the block type BT is not 00 or 01, or the decryption process is a private-key operation and the block type is not 02. diff --git a/lib/tls.js b/lib/tls.js index fadfd646f..00d45089b 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -758,7 +758,7 @@ tls.handleUnexpected = function(c, record) { */ tls.handleHelloRequest = function(c, record, length) { // ignore renegotiation requests from the server during a handshake, but - // if handshaking, send a warning alert that renegotation is denied + // if handshaking, send a warning alert that renegotiation is denied if(!c.handshaking && c.handshakes > 0) { // send alert warning tls.queue(c, tls.createAlert(c, { @@ -2258,7 +2258,7 @@ hsTable[tls.ConnectionEnd.client] = [ ]; // map server current expect state and handshake type to function -// Note: CAD[CH] does not map to FB because renegotation is prohibited +// Note: CAD[CH] does not map to FB because renegotiation is prohibited var H7 = tls.handleClientHello; var H8 = tls.handleClientKeyExchange; var H9 = tls.handleCertificateVerify; diff --git a/lib/util.js b/lib/util.js index a86609284..db2341ac7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -677,7 +677,7 @@ util.ByteStringBuffer.prototype.clear = function() { }; /** - * Shortens this buffer by triming bytes off of the end of this buffer. + * Shortens this buffer by trimming bytes off of the end of this buffer. * * @param count the number of bytes to trim off. * @@ -1343,7 +1343,7 @@ util.DataBuffer.prototype.clear = function() { }; /** - * Shortens this buffer by triming bytes off of the end of this buffer. + * Shortens this buffer by trimming bytes off of the end of this buffer. * * @param count the number of bytes to trim off. * @@ -2258,354 +2258,6 @@ util.clearItems = function(api, id, location) { _callStorageFunction(_clearItems, arguments, location); }; -/** - * Parses the scheme, host, and port from an http(s) url. - * - * @param str the url string. - * - * @return the parsed url object or null if the url is invalid. - */ -util.parseUrl = function(str) { - // FIXME: this regex looks a bit broken - var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g; - regex.lastIndex = 0; - var m = regex.exec(str); - var url = (m === null) ? null : { - full: str, - scheme: m[1], - host: m[2], - port: m[3], - path: m[4] - }; - if(url) { - url.fullHost = url.host; - if(url.port) { - if(url.port !== 80 && url.scheme === 'http') { - url.fullHost += ':' + url.port; - } else if(url.port !== 443 && url.scheme === 'https') { - url.fullHost += ':' + url.port; - } - } else if(url.scheme === 'http') { - url.port = 80; - } else if(url.scheme === 'https') { - url.port = 443; - } - url.full = url.scheme + '://' + url.fullHost; - } - return url; -}; - -/* Storage for query variables */ -var _queryVariables = null; - -/** - * Returns the window location query variables. Query is parsed on the first - * call and the same object is returned on subsequent calls. The mapping - * is from keys to an array of values. Parameters without values will have - * an object key set but no value added to the value array. Values are - * unescaped. - * - * ...?k1=v1&k2=v2: - * { - * "k1": ["v1"], - * "k2": ["v2"] - * } - * - * ...?k1=v1&k1=v2: - * { - * "k1": ["v1", "v2"] - * } - * - * ...?k1=v1&k2: - * { - * "k1": ["v1"], - * "k2": [] - * } - * - * ...?k1=v1&k1: - * { - * "k1": ["v1"] - * } - * - * ...?k1&k1: - * { - * "k1": [] - * } - * - * @param query the query string to parse (optional, default to cached - * results from parsing window location search query). - * - * @return object mapping keys to variables. - */ -util.getQueryVariables = function(query) { - var parse = function(q) { - var rval = {}; - var kvpairs = q.split('&'); - for(var i = 0; i < kvpairs.length; i++) { - var pos = kvpairs[i].indexOf('='); - var key; - var val; - if(pos > 0) { - key = kvpairs[i].substring(0, pos); - val = kvpairs[i].substring(pos + 1); - } else { - key = kvpairs[i]; - val = null; - } - if(!(key in rval)) { - rval[key] = []; - } - // disallow overriding object prototype keys - if(!(key in Object.prototype) && val !== null) { - rval[key].push(unescape(val)); - } - } - return rval; - }; - - var rval; - if(typeof(query) === 'undefined') { - // set cached variables if needed - if(_queryVariables === null) { - if(typeof(window) !== 'undefined' && window.location && window.location.search) { - // parse window search query - _queryVariables = parse(window.location.search.substring(1)); - } else { - // no query variables available - _queryVariables = {}; - } - } - rval = _queryVariables; - } else { - // parse given query - rval = parse(query); - } - return rval; -}; - -/** - * Parses a fragment into a path and query. This method will take a URI - * fragment and break it up as if it were the main URI. For example: - * /bar/baz?a=1&b=2 - * results in: - * { - * path: ["bar", "baz"], - * query: {"k1": ["v1"], "k2": ["v2"]} - * } - * - * @return object with a path array and query object. - */ -util.parseFragment = function(fragment) { - // default to whole fragment - var fp = fragment; - var fq = ''; - // split into path and query if possible at the first '?' - var pos = fragment.indexOf('?'); - if(pos > 0) { - fp = fragment.substring(0, pos); - fq = fragment.substring(pos + 1); - } - // split path based on '/' and ignore first element if empty - var path = fp.split('/'); - if(path.length > 0 && path[0] === '') { - path.shift(); - } - // convert query into object - var query = (fq === '') ? {} : util.getQueryVariables(fq); - - return { - pathString: fp, - queryString: fq, - path: path, - query: query - }; -}; - -/** - * Makes a request out of a URI-like request string. This is intended to - * be used where a fragment id (after a URI '#') is parsed as a URI with - * path and query parts. The string should have a path beginning and - * delimited by '/' and optional query parameters following a '?'. The - * query should be a standard URL set of key value pairs delimited by - * '&'. For backwards compatibility the initial '/' on the path is not - * required. The request object has the following API, (fully described - * in the method code): - * { - * path: . - * query: , - * getPath(i): get part or all of the split path array, - * getQuery(k, i): get part or all of a query key array, - * getQueryLast(k, _default): get last element of a query key array. - * } - * - * @return object with request parameters. - */ -util.makeRequest = function(reqString) { - var frag = util.parseFragment(reqString); - var req = { - // full path string - path: frag.pathString, - // full query string - query: frag.queryString, - /** - * Get path or element in path. - * - * @param i optional path index. - * - * @return path or part of path if i provided. - */ - getPath: function(i) { - return (typeof(i) === 'undefined') ? frag.path : frag.path[i]; - }, - /** - * Get query, values for a key, or value for a key index. - * - * @param k optional query key. - * @param i optional query key index. - * - * @return query, values for a key, or value for a key index. - */ - getQuery: function(k, i) { - var rval; - if(typeof(k) === 'undefined') { - rval = frag.query; - } else { - rval = frag.query[k]; - if(rval && typeof(i) !== 'undefined') { - rval = rval[i]; - } - } - return rval; - }, - getQueryLast: function(k, _default) { - var rval; - var vals = req.getQuery(k); - if(vals) { - rval = vals[vals.length - 1]; - } else { - rval = _default; - } - return rval; - } - }; - return req; -}; - -/** - * Makes a URI out of a path, an object with query parameters, and a - * fragment. Uses jQuery.param() internally for query string creation. - * If the path is an array, it will be joined with '/'. - * - * @param path string path or array of strings. - * @param query object with query parameters. (optional) - * @param fragment fragment string. (optional) - * - * @return string object with request parameters. - */ -util.makeLink = function(path, query, fragment) { - // join path parts if needed - path = jQuery.isArray(path) ? path.join('/') : path; - - var qstr = jQuery.param(query || {}); - fragment = fragment || ''; - return path + - ((qstr.length > 0) ? ('?' + qstr) : '') + - ((fragment.length > 0) ? ('#' + fragment) : ''); -}; - -/** - * Follows a path of keys deep into an object hierarchy and set a value. - * If a key does not exist or it's value is not an object, create an - * object in it's place. This can be destructive to a object tree if - * leaf nodes are given as non-final path keys. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - * @param value the value to set. - */ -util.setPath = function(object, keys, value) { - // need to start at an object - if(typeof(object) === 'object' && object !== null) { - var i = 0; - var len = keys.length; - while(i < len) { - var next = keys[i++]; - if(i == len) { - // last - object[next] = value; - } else { - // more - var hasNext = (next in object); - if(!hasNext || - (hasNext && typeof(object[next]) !== 'object') || - (hasNext && object[next] === null)) { - object[next] = {}; - } - object = object[next]; - } - } - } -}; - -/** - * Follows a path of keys deep into an object hierarchy and return a value. - * If a key does not exist, create an object in it's place. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - * @param _default value to return if path not found. - * - * @return the value at the path if found, else default if given, else - * undefined. - */ -util.getPath = function(object, keys, _default) { - var i = 0; - var len = keys.length; - var hasNext = true; - while(hasNext && i < len && - typeof(object) === 'object' && object !== null) { - var next = keys[i++]; - hasNext = next in object; - if(hasNext) { - object = object[next]; - } - } - return (hasNext ? object : _default); -}; - -/** - * Follow a path of keys deep into an object hierarchy and delete the - * last one. If a key does not exist, do nothing. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - */ -util.deletePath = function(object, keys) { - // need to start at an object - if(typeof(object) === 'object' && object !== null) { - var i = 0; - var len = keys.length; - while(i < len) { - var next = keys[i++]; - if(i == len) { - // last - delete object[next]; - } else { - // more - if(!(next in object) || - (typeof(object[next]) !== 'object') || - (object[next] === null)) { - break; - } - object = object[next]; - } - } - } -}; - /** * Check if an object is empty. * @@ -2664,7 +2316,7 @@ util.format = function(format) { parts.push(''); } break; - // FIXME: do proper formating for numbers, etc + // FIXME: do proper formatting for numbers, etc //case 'f': //case 'd': case '%': diff --git a/lib/x509.js b/lib/x509.js index 95dbc2946..99209ffbf 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -689,6 +689,101 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { return params; }; +/** + * Create signature digest for OID. + * + * @param options + * signatureOid: the OID specifying the signature algorithm. + * type: a human readable type for error messages + * @return a created md instance. throws if unknown oid. + */ +var _createSignatureDigest = function(options) { + switch(oids[options.signatureOid]) { + case 'sha1WithRSAEncryption': + // deprecated alias + case 'sha1WithRSASignature': + return forge.md.sha1.create(); + case 'md5WithRSAEncryption': + return forge.md.md5.create(); + case 'sha256WithRSAEncryption': + return forge.md.sha256.create(); + case 'sha384WithRSAEncryption': + return forge.md.sha384.create(); + case 'sha512WithRSAEncryption': + return forge.md.sha512.create(); + case 'RSASSA-PSS': + return forge.md.sha256.create(); + default: + var error = new Error( + 'Could not compute ' + options.type + ' digest. ' + + 'Unknown signature OID.'); + error.signatureOid = options.signatureOid; + throw error; + } +}; + +/** + * Verify signature on certificate or CSR. + * + * @param options: + * certificate the certificate or CSR to verify. + * md the signature digest. + * signature the signature + * @return a created md instance. throws if unknown oid. + */ +var _verifySignature = function(options) { + var cert = options.certificate; + var scheme; + + switch(cert.signatureOid) { + case oids.sha1WithRSAEncryption: + // deprecated alias + case oids.sha1WithRSASignature: + /* use PKCS#1 v1.5 padding scheme */ + break; + case oids['RSASSA-PSS']: + var hash, mgf; + + /* initialize mgf */ + hash = oids[cert.signatureParameters.mgf.hash.algorithmOid]; + if(hash === undefined || forge.md[hash] === undefined) { + var error = new Error('Unsupported MGF hash function.'); + error.oid = cert.signatureParameters.mgf.hash.algorithmOid; + error.name = hash; + throw error; + } + + mgf = oids[cert.signatureParameters.mgf.algorithmOid]; + if(mgf === undefined || forge.mgf[mgf] === undefined) { + var error = new Error('Unsupported MGF function.'); + error.oid = cert.signatureParameters.mgf.algorithmOid; + error.name = mgf; + throw error; + } + + mgf = forge.mgf[mgf].create(forge.md[hash].create()); + + /* initialize hash function */ + hash = oids[cert.signatureParameters.hash.algorithmOid]; + if(hash === undefined || forge.md[hash] === undefined) { + var error = new Error('Unsupported RSASSA-PSS hash function.'); + error.oid = cert.signatureParameters.hash.algorithmOid; + error.name = hash; + throw error; + } + + scheme = forge.pss.create( + forge.md[hash].create(), mgf, cert.signatureParameters.saltLength + ); + break; + } + + // verify signature on cert using public key + return cert.publicKey.verify( + options.md.digest().getBytes(), options.signature, scheme + ); +}; + /** * Converts an X.509 certificate from PEM format. * @@ -1069,43 +1164,18 @@ pki.createCertificate = function() { 'The parent certificate did not issue the given child ' + 'certificate; the child certificate\'s issuer does not match the ' + 'parent\'s subject.'); - error.expectedIssuer = issuer.attributes; - error.actualIssuer = subject.attributes; + error.expectedIssuer = subject.attributes; + error.actualIssuer = issuer.attributes; throw error; } var md = child.md; if(md === null) { - // check signature OID for supported signature types - if(child.signatureOid in oids) { - var oid = oids[child.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - md = forge.md.sha256.create(); - break; - } - } - if(md === null) { - var error = new Error('Could not compute certificate digest. ' + - 'Unknown signature OID.'); - error.signatureOid = child.signatureOid; - throw error; - } + // create digest for OID signature types + md = _createSignatureDigest({ + signatureOid: child.signatureOid, + type: 'certificate' + }); // produce DER formatted TBSCertificate and digest it var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child); @@ -1114,52 +1184,9 @@ pki.createCertificate = function() { } if(md !== null) { - var scheme; - - switch(child.signatureOid) { - case oids.sha1WithRSAEncryption: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; - case oids['RSASSA-PSS']: - var hash, mgf; - - /* initialize mgf */ - hash = oids[child.signatureParameters.mgf.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported MGF hash function.'); - error.oid = child.signatureParameters.mgf.hash.algorithmOid; - error.name = hash; - throw error; - } - - mgf = oids[child.signatureParameters.mgf.algorithmOid]; - if(mgf === undefined || forge.mgf[mgf] === undefined) { - var error = new Error('Unsupported MGF function.'); - error.oid = child.signatureParameters.mgf.algorithmOid; - error.name = mgf; - throw error; - } - - mgf = forge.mgf[mgf].create(forge.md[hash].create()); - - /* initialize hash function */ - hash = oids[child.signatureParameters.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - throw { - message: 'Unsupported RSASSA-PSS hash function.', - oid: child.signatureParameters.hash.algorithmOid, - name: hash - }; - } - - scheme = forge.pss.create(forge.md[hash].create(), mgf, - child.signatureParameters.saltLength); - break; - } - - // verify signature on cert using public key - rval = cert.publicKey.verify( - md.digest().getBytes(), child.signature, scheme); + rval = _verifySignature({ + certificate: cert, md: md, signature: child.signature + }); } return rval; @@ -1333,37 +1360,11 @@ pki.certificateFromAsn1 = function(obj, computeHash) { cert.tbsCertificate = capture.tbsCertificate; if(computeHash) { - // check signature OID for supported signature types - cert.md = null; - if(cert.signatureOid in oids) { - var oid = oids[cert.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - cert.md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - cert.md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - cert.md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - cert.md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - cert.md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - cert.md = forge.md.sha256.create(); - break; - } - } - if(cert.md === null) { - var error = new Error('Could not compute certificate digest. ' + - 'Unknown signature OID.'); - error.signatureOid = cert.signatureOid; - throw error; - } + // create digest for OID signature type + cert.md = _createSignatureDigest({ + signatureOid: cert.signatureOid, + type: 'certificate' + }); // produce DER formatted TBSCertificate and digest it var bytes = asn1.toDer(cert.tbsCertificate); @@ -1372,6 +1373,8 @@ pki.certificateFromAsn1 = function(obj, computeHash) { // handle issuer, build issuer message digest var imd = forge.md.sha1.create(); + var ibytes = asn1.toDer(capture.certIssuer); + imd.update(ibytes.getBytes()); cert.issuer.getField = function(sn) { return _getAttribute(cert.issuer, sn); }; @@ -1379,7 +1382,7 @@ pki.certificateFromAsn1 = function(obj, computeHash) { _fillMissingFields([attr]); cert.issuer.attributes.push(attr); }; - cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd); + cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer); if(capture.certIssuerUniqueId) { cert.issuer.uniqueId = capture.certIssuerUniqueId; } @@ -1387,6 +1390,8 @@ pki.certificateFromAsn1 = function(obj, computeHash) { // handle subject, build subject message digest var smd = forge.md.sha1.create(); + var sbytes = asn1.toDer(capture.certSubject); + smd.update(sbytes.getBytes()); cert.subject.getField = function(sn) { return _getAttribute(cert.subject, sn); }; @@ -1394,7 +1399,7 @@ pki.certificateFromAsn1 = function(obj, computeHash) { _fillMissingFields([attr]); cert.subject.attributes.push(attr); }; - cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd); + cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject); if(capture.certSubjectUniqueId) { cert.subject.uniqueId = capture.certSubjectUniqueId; } @@ -1677,37 +1682,11 @@ pki.certificationRequestFromAsn1 = function(obj, computeHash) { csr.certificationRequestInfo = capture.certificationRequestInfo; if(computeHash) { - // check signature OID for supported signature types - csr.md = null; - if(csr.signatureOid in oids) { - var oid = oids[csr.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - csr.md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - csr.md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - csr.md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - csr.md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - csr.md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - csr.md = forge.md.sha256.create(); - break; - } - } - if(csr.md === null) { - var error = new Error('Could not compute certification request digest. ' + - 'Unknown signature OID.'); - error.signatureOid = csr.signatureOid; - throw error; - } + // create digest for OID signature type + csr.md = _createSignatureDigest({ + signatureOid: csr.signatureOid, + type: 'certification request' + }); // produce DER formatted CertificationRequestInfo and digest it var bytes = asn1.toDer(csr.certificationRequestInfo); @@ -1847,38 +1826,10 @@ pki.createCertificationRequest = function() { var md = csr.md; if(md === null) { - // check signature OID for supported signature types - if(csr.signatureOid in oids) { - // TODO: create DRY `OID to md` function - var oid = oids[csr.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - md = forge.md.sha256.create(); - break; - } - } - if(md === null) { - var error = new Error( - 'Could not compute certification request digest. ' + - 'Unknown signature OID.'); - error.signatureOid = csr.signatureOid; - throw error; - } + md = _createSignatureDigest({ + signatureOid: csr.signatureOid, + type: 'certification request' + }); // produce DER formatted CertificationRequestInfo and digest it var cri = csr.certificationRequestInfo || @@ -1888,51 +1839,9 @@ pki.createCertificationRequest = function() { } if(md !== null) { - var scheme; - - switch(csr.signatureOid) { - case oids.sha1WithRSAEncryption: - /* use PKCS#1 v1.5 padding scheme */ - break; - case oids['RSASSA-PSS']: - var hash, mgf; - - /* initialize mgf */ - hash = oids[csr.signatureParameters.mgf.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported MGF hash function.'); - error.oid = csr.signatureParameters.mgf.hash.algorithmOid; - error.name = hash; - throw error; - } - - mgf = oids[csr.signatureParameters.mgf.algorithmOid]; - if(mgf === undefined || forge.mgf[mgf] === undefined) { - var error = new Error('Unsupported MGF function.'); - error.oid = csr.signatureParameters.mgf.algorithmOid; - error.name = mgf; - throw error; - } - - mgf = forge.mgf[mgf].create(forge.md[hash].create()); - - /* initialize hash function */ - hash = oids[csr.signatureParameters.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported RSASSA-PSS hash function.'); - error.oid = csr.signatureParameters.hash.algorithmOid; - error.name = hash; - throw error; - } - - scheme = forge.pss.create(forge.md[hash].create(), mgf, - csr.signatureParameters.saltLength); - break; - } - - // verify signature on csr using its public key - rval = csr.publicKey.verify( - md.digest().getBytes(), csr.signature, scheme); + rval = _verifySignature({ + certificate: csr, md: md, signature: csr.signature + }); } return rval; @@ -2380,7 +2289,7 @@ function _fillMissingExtensionFields(e, options) { * Convert signature parameters object to ASN.1 * * @param {String} oid Signature algorithm OID - * @param params The signature parametrs object + * @param params The signature parameters object * @return ASN.1 object representing signature parameters */ function _signatureParametersToAsn1(oid, params) { diff --git a/lib/xhr.js b/lib/xhr.js index e493c3b60..1527042b6 100644 --- a/lib/xhr.js +++ b/lib/xhr.js @@ -151,7 +151,7 @@ xhrApi.init = function(options) { getPrivateKey: options.getPrivateKey, getSignature: options.getSignature }); - _clients[_client.url.full] = _client; + _clients[_client.url.origin] = _client; forge.log.debug(cat, 'ready'); }; @@ -380,8 +380,10 @@ xhrApi.create = function(options) { // use default _state.client = _client; } else { - var url = http.parseUrl(options.url); - if(!url) { + var url; + try { + url = new URL(options.url); + } catch(e) { var error = new Error('Invalid url.'); error.details = { url: options.url @@ -389,9 +391,9 @@ xhrApi.create = function(options) { } // find client - if(url.full in _clients) { + if(url.origin in _clients) { // client found - _state.client = _clients[url.full]; + _state.client = _clients[url.origin]; } else { // create client _state.client = http.createClient({ @@ -409,7 +411,7 @@ xhrApi.create = function(options) { getPrivateKey: options.getPrivateKey, getSignature: options.getSignature }); - _clients[url.full] = _state.client; + _clients[url.origin] = _state.client; } } @@ -457,7 +459,7 @@ xhrApi.create = function(options) { // set request method to given method // set request URL // set username, password - // set asychronous flag + // set asynchronous flag _state.sendFlag = false; xhr.responseText = ''; xhr.responseXML = null; diff --git a/package.json b/package.json index ab3f2cef3..2a9d83b2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.9.0", + "version": "1.3.2", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { @@ -15,31 +15,32 @@ "Christoph Dorn " ], "devDependencies": { - "browserify": "^16.1.0", + "browserify": "^16.5.2", "commander": "^2.20.0", - "cross-env": "^5.1.3", - "eslint": "^5.16.0", - "eslint-config-digitalbazaar": "^2.0.0", + "cross-env": "^5.2.1", + "eslint": "^7.27.0", + "eslint-config-digitalbazaar": "^2.8.0", "express": "^4.16.2", - "karma": "^3.1.4", - "karma-browserify": "^6.0.0", - "karma-chrome-launcher": "^2.2.0", + "karma": "^4.4.1", + "karma-browserify": "^7.0.0", + "karma-chrome-launcher": "^3.1.0", "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", + "karma-firefox-launcher": "^1.3.0", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", "karma-safari-launcher": "^1.0.0", - "karma-sauce-launcher": "^1.2.0", - "karma-sourcemap-loader": "^0.3.7", + "karma-sauce-launcher": "^2.0.2", + "karma-sourcemap-loader": "^0.3.8", "karma-tap-reporter": "0.0.6", - "karma-webpack": "^3.0.5", + "karma-webpack": "^4.0.2", "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "nodejs-websocket": "^1.7.1", - "nyc": "^14.1.1", - "opts": "^1.2.2", - "webpack": "^3.11.0", + "nyc": "^15.1.0", + "opts": "^1.2.7", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12", "worker-loader": "^2.0.0" }, "repository": { @@ -59,7 +60,7 @@ "dist/*.min.js.map" ], "engines": { - "node": ">= 4.5.0" + "node": ">= 6.13.0" }, "keywords": [ "aes", @@ -94,13 +95,15 @@ "prepublish": "npm run build", "build": "webpack", "test-build": "webpack --config webpack-tests.config.js", - "test": "cross-env NODE_ENV=test mocha -t 30000 -R ${REPORTER:-spec} tests/unit/index.js", + "test": "npm run test-node", + "test-node": "cross-env NODE_ENV=test mocha -t 30000 -R ${REPORTER:-spec} tests/unit/index.js", "test-karma": "karma start", "test-karma-sauce": "karma start karma-sauce.conf", "test-server": "node tests/server.js", "test-server-ws": "node tests/websockets/server-ws.js", "test-server-webid": "node tests/websockets/server-webid.js", "coverage": "rm -rf coverage && nyc --reporter=lcov --reporter=text-summary npm test", + "coverage-ci": "rm -rf coverage && nyc --reporter=lcovonly npm test", "coverage-report": "nyc report", "lint": "eslint *.js lib/*.js tests/*.js tests/**/*.js examples/*.js flash/*.js" }, diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js new file mode 100644 index 000000000..bf3479fb6 --- /dev/null +++ b/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + mocha: true + } +}; diff --git a/tests/issues/issue-428.html b/tests/issues/issue-428.html index fc9af30c6..1685c3e1f 100644 --- a/tests/issues/issue-428.html +++ b/tests/issues/issue-428.html @@ -4,6 +4,7 @@ Forge Issue 428 Test + diff --git a/tests/issues/issue-428.js b/tests/issues/issue-428.js index 7b9427e4a..179477d42 100644 --- a/tests/issues/issue-428.js +++ b/tests/issues/issue-428.js @@ -52,7 +52,7 @@ jQuery(function($) { $('#start').attr('disabled', 'true'); $('#stop').attr('disabled', ''); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { diff --git a/tests/legacy/common.html b/tests/legacy/common.html index 9ee073117..d835af37f 100644 --- a/tests/legacy/common.html +++ b/tests/legacy/common.html @@ -4,6 +4,7 @@ Forge Common Tests + diff --git a/tests/legacy/common.js b/tests/legacy/common.js index 57dfbc4ba..4c08e1319 100644 --- a/tests/legacy/common.js +++ b/tests/legacy/common.js @@ -39,7 +39,7 @@ jQuery(function($) { $('#start').attr('disabled', 'true'); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { diff --git a/tests/legacy/loginDemo.js b/tests/legacy/loginDemo.js index 35dab6b18..dc0d301db 100644 --- a/tests/legacy/loginDemo.js +++ b/tests/legacy/loginDemo.js @@ -29,11 +29,11 @@ var init = function($) try { // get query variables - var query = forge.util.getQueryVariables(); - var domain = query.domain || ''; - var auth = query.auth || ''; - var redirect = query.redirect || ''; - var pport = query.pport || 843; + var query = new URL(window.location.href).searchParams; + var domain = query.get('domain') || ''; + var auth = query.get('auth') || ''; + var redirect = query.get('redirect') || ''; + var pport = parseInt(query.get('pport')) || 843; redirect = 'https://' + domain + '/' + redirect; if(domain) { diff --git a/tests/legacy/socketPool.html b/tests/legacy/socketPool.html index 9edcf41aa..fa44a835a 100644 --- a/tests/legacy/socketPool.html +++ b/tests/legacy/socketPool.html @@ -209,7 +209,7 @@

SocketPool Tests

- +

Could not load the flash SocketPool.

diff --git a/tests/legacy/tasks.html b/tests/legacy/tasks.html index dc7e48d60..5456faded 100644 --- a/tests/legacy/tasks.html +++ b/tests/legacy/tasks.html @@ -4,6 +4,7 @@ Forge Tasks Tests + diff --git a/tests/legacy/tasks.js b/tests/legacy/tasks.js index 2c99a9d36..eef594a19 100644 --- a/tests/legacy/tasks.js +++ b/tests/legacy/tasks.js @@ -33,7 +33,7 @@ jQuery(function($) { $('#start').attr('disabled', 'disabled'); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { @@ -365,7 +365,7 @@ jQuery(function($) for(var i = 0; i < count; ++i) { - forge.task.start(tasks[i]); + forge_task.start(tasks[i]); } }); diff --git a/tests/legacy/tls.html b/tests/legacy/tls.html index 8f59ee1fb..a20c3c336 100644 --- a/tests/legacy/tls.html +++ b/tests/legacy/tls.html @@ -339,7 +339,7 @@

TLS Tests

- +

Could not load the flash SocketPool.

diff --git a/tests/legacy/xhr.html b/tests/legacy/xhr.html index 5aa868f54..28ba5dcb1 100644 --- a/tests/legacy/xhr.html +++ b/tests/legacy/xhr.html @@ -5,6 +5,7 @@ + @@ -28,7 +29,7 @@

XmlHttpRequest Tests

- +

Could not load the flash SocketPool.

diff --git a/tests/legacy/xhr.js b/tests/legacy/xhr.js index 1beb48799..fbe202cda 100644 --- a/tests/legacy/xhr.js +++ b/tests/legacy/xhr.js @@ -60,7 +60,7 @@ jQuery(function($) { $('#start').attr('disabled', 'disabled'); // meta! use tasks to run the task tests - forge.task.start({ + forge_task.start({ type: 'test', run: function(task) { task.next('starting', function(task) { diff --git a/tests/security/cve-2025-12816.js b/tests/security/cve-2025-12816.js new file mode 100644 index 000000000..bf26f26bb --- /dev/null +++ b/tests/security/cve-2025-12816.js @@ -0,0 +1,75 @@ +'use strict'; + +var assert = require('assert'); +var md = require('../../lib/md'); +var pkcs12 = require('../../lib/pkcs12'); +var asn1 = require('../../lib/asn1'); +var pki = require('../../lib/pki'); + +/** + * Build a minimal certificate-only PKCS#12 (PFX) with a MAC. + */ +function buildCertOnlyPfxWithMac(password) { + var keys = pki.rsa.generateKeyPair({bits: 1024, e: 0x10001}); + var cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = '01'; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); + cert.setSubject([{name: 'commonName', value: 'p12-demo'}]); + cert.setIssuer([{name: 'commonName', value: 'p12-demo'}]); + cert.sign(keys.privateKey, md.sha256.create()); + + return pkcs12.toPkcs12Asn1( + null, + cert, + password, + {useMac: true, count: 2048, saltSize: 8} + ); +} + +/** + * Replace macData node with arbitrary junk. + */ +function corruptMacData(pfxAsn1) { + var clone = asn1.fromDer(asn1.toDer(pfxAsn1).getBytes()); + // Replace 3rd element (macData) with garbage node + clone.value[2] = asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OCTETSTRING, + false, + 'JUNK' + ); + return clone; +} + +describe('PKCS#12 MAC corruption', function() { + var correctPw = 'correct-horse-battery-staple'; + var wrongPw = 'wrong-password'; + var legit; + + before(function() { + legit = buildCertOnlyPfxWithMac(correctPw); + }); + + it('should accept valid PFX with correct password', function() { + assert.doesNotThrow(function() { + var obj = pkcs12.pkcs12FromAsn1(legit, true, correctPw); + assert(obj, 'pkcs12FromAsn1 should return an object'); + }); + }); + + it('should reject valid PFX with wrong password (MAC mismatch)', function() { + assert.throws(function() { + pkcs12.pkcs12FromAsn1(legit, true, wrongPw); + }, /PKCS#12 MAC could not be verified. Invalid password?/); + }); + + it('should reject tampered PFX with corrupted macData', function() { + var tampered = corruptMacData(legit); + assert.throws(function() { + pkcs12.pkcs12FromAsn1(tampered, true, wrongPw); + }, /Invalid PKCS#12. macData field present but MAC was not validated./); + }); +}); diff --git a/tests/security/ghsa-554w-wpv2-vw27.js b/tests/security/ghsa-554w-wpv2-vw27.js new file mode 100644 index 000000000..7fd743f2c --- /dev/null +++ b/tests/security/ghsa-554w-wpv2-vw27.js @@ -0,0 +1,81 @@ +/* + * Regression Test for GHSA-554w-wpv2-vw27 + * Verifies that the parser enforces a maximum recursion depth + * instead of crashing with a call stack overflow. + */ +var assert = require('assert'); +var asn1 = require('../../lib/asn1'); +var util = require('../../lib/util'); + +describe('GHSA-554w-wpv2-vw27 Security Patch', function() { + + function createNestedDer(depth) { + var obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, '\x00'); + for(var i = 0; i < depth; i++) { + obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [obj]); + } + return asn1.toDer(obj).getBytes(); + } + + beforeEach(function() { + // check max depth is the default + assert.equal(asn1.maxDepth, 256); + }); + + it('should throw a manageable error when default recursion depth is exceeded', function() { + // create a payload just above the default limit (256) + var DANGEROUS_DEPTH = 257; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); + + // assert that it throws the correct error + assert.throws(function() { + asn1.fromDer(buf, {strict: true}); + }, /ASN.1 parsing error: Max depth exceeded./); + }); + + it('should throw a manageable error when optional recursion depth is exceeded', function() { + // create a payload just above the optional defined limit (128) + var DANGEROUS_DEPTH = 129; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); + + // assert that it throws the correct error + assert.throws(function() { + asn1.fromDer(buf, {strict: true, maxDepth: 128}); + }, /ASN.1 parsing error: Max depth exceeded./); + }); + + it('should still parse valid nested structures with new default limits', function() { + var oldMaxDepth = asn1.maxDepth; + asn1.maxDepth = 258; + + // create a payload just above the default limit (256) + var DANGEROUS_DEPTH = 257; + var der = createNestedDer(DANGEROUS_DEPTH); + var buf = util.createBuffer(der); + + // verify with new default depth + asn1.fromDer(buf, {strict: true}); + + asn1.maxDepth = oldMaxDepth; + }); + + it('should still parse valid nested structures within default limits', function() { + // verify we didn't break default depth functionality + var SAFE_DEPTH = 20; + var der = createNestedDer(SAFE_DEPTH); + var buf = util.createBuffer(der); + + asn1.fromDer(buf, {strict: true}); + }); + + it('should still parse valid nested structures within optional limits', function() { + // verify we didn't break optional depth functionality + var SAFE_DEPTH = 20; + var der = createNestedDer(SAFE_DEPTH); + var buf = util.createBuffer(der); + + asn1.fromDer(buf, {strict: true, maxDepth: 128}); + }); +}); diff --git a/tests/security/index.js b/tests/security/index.js new file mode 100644 index 000000000..5ce5600a1 --- /dev/null +++ b/tests/security/index.js @@ -0,0 +1,3 @@ +// tests related to security, vulnerability reports, etc +require('./cve-2025-12816.js'); +require('./ghsa-554w-wpv2-vw27.js'); diff --git a/tests/server.js b/tests/server.js index 85af534f2..5537b7b99 100644 --- a/tests/server.js +++ b/tests/server.js @@ -30,6 +30,8 @@ function contentServer(callback) { // forge app.use('/forge', express.static(path.join(__dirname, '..', 'dist'))); + app.use('/support', express.static(path.join(__dirname, 'support'))); + app.use('/issues', express.static(path.join(__dirname, 'issues'))); // unit tests support app.use('/mocha', diff --git a/lib/task.js b/tests/support/task.js similarity index 97% rename from lib/task.js rename to tests/support/task.js index df4866001..0d614a207 100644 --- a/lib/task.js +++ b/tests/support/task.js @@ -7,13 +7,10 @@ * * Copyright (c) 2009-2013 Digital Bazaar, Inc. */ -var forge = require('./forge'); -require('./debug'); -require('./log'); -require('./util'); +// 'forge' should be a global // logging category -var cat = 'forge.task'; +var cat = 'forge.tests.task'; // verbose level // 0: off, 1: a little, 2: a whole lot @@ -27,13 +24,9 @@ var sVL = 0; // track tasks for debugging var sTasks = {}; var sNextTaskId = 0; -// debug access -forge.debug.set(cat, 'tasks', sTasks); // a map of task type to task queue var sTaskQueues = {}; -// debug access -forge.debug.set(cat, 'queues', sTaskQueues); // name for unnamed tasks var sNoTaskName = '?'; @@ -73,7 +66,7 @@ var ERROR = 'error'; * SLEEP: sleep for a period of time * WAKEUP: wakeup early from SLEEPING state * CANCEL: cancel further tasks - * FAIL: a failure occured + * FAIL: a failure occurred */ var STOP = 'stop'; var START = 'start'; @@ -277,7 +270,7 @@ Task.prototype.parallel = function(name, subrun) { // closure and changes as the loop changes -- causing i // to always be set to its highest value var startParallelTask = function(pname, pi) { - forge.task.start({ + forge_task.start({ type: pname, run: function(task) { subrun[pi](task); @@ -345,7 +338,7 @@ Task.prototype.block = function(n) { * running once enough permits have been released via unblock() calls. * * If multiple processes need to synchronize with a single task then - * use a condition variable (see forge.task.createCondition). It is + * use a condition variable (see task.createCondition). It is * an error to unblock a task more times than it has been blocked. * * @param n number of permits to release (default: 1). @@ -381,7 +374,7 @@ Task.prototype.sleep = function(n) { /** * Waits on a condition variable until notified. The next task will * not be scheduled until notification. A condition variable can be - * created with forge.task.createCondition(). + * created with task.createCondition(). * * Once cond.notify() is called, the task will continue. * @@ -618,7 +611,7 @@ var finish = function(task, suppressCallbacks) { }; /* Tasks API */ -module.exports = forge.task = forge.task || {}; +window.forge_task = {}; /** * Starts a new task that will run the passed function asynchronously. @@ -642,7 +635,7 @@ module.exports = forge.task = forge.task || {}; * * @param options the object as described above. */ -forge.task.start = function(options) { +forge_task.start = function(options) { // create a new task var task = new Task({ run: options.run, @@ -673,7 +666,7 @@ forge.task.start = function(options) { * * @param type the type of task to cancel. */ -forge.task.cancel = function(type) { +forge_task.cancel = function(type) { // find the task queue if(type in sTaskQueues) { // empty all but the current task from the queue @@ -688,7 +681,7 @@ forge.task.cancel = function(type) { * * @return the condition variable. */ -forge.task.createCondition = function() { +forge_task.createCondition = function() { var cond = { // all tasks that are blocked tasks: {} diff --git a/tests/unit/asn1.js b/tests/unit/asn1.js index 65a1db510..a99783cb9 100644 --- a/tests/unit/asn1.js +++ b/tests/unit/asn1.js @@ -10,11 +10,50 @@ var UTIL = require('../../lib/util'); ASSERT.equal(ASN1.oidToDer('1.2.840.113549').toHex(), '2a864886f70d'); }); + it('should convert a 32b OID to DER', function() { + ASSERT.equal(ASN1.oidToDer('1.2.4294967295').toHex(), '2a8fffffff7f'); + }); + + it('should not convert a >32b OID to DER', function() { + ASSERT.throws( + function() { + ASN1.oidToDer('1.2.4294967296'); + }, + /OID value too large; max is 32-bits./ + ); + }); + it('should convert an OID from DER', function() { var der = UTIL.hexToBytes('2a864886f70d'); ASSERT.equal(ASN1.derToOid(der), '1.2.840.113549'); }); + it('should convert a 32b OID from DER', function() { + var der = UTIL.hexToBytes('2a8fffffff7f'); + ASSERT.equal(ASN1.derToOid(der), '1.2.4294967295'); + }); + + it('should convert a >32b OID from DER', function() { + var der = UTIL.hexToBytes('2a9080808001'); + ASSERT.equal(ASN1.derToOid(der), '1.2.4294967297'); + }); + + it('should convert a max safe int OID from DER', function() { + var der = UTIL.hexToBytes('2a8fffffffffffff7f'); + ASSERT.equal(ASN1.derToOid(der), '1.2.9007199254740991'); + }); + + it('should not convert a >max safe int OID from DER', function() { + ASSERT.throws( + function() { + // '1.2.9007199254740992' + var der = UTIL.hexToBytes('2a9080808080808000'); + console.log(ASN1.derToOid(der)); + }, + /OID value too large; max is 53-bits./ + ); + }); + it('should convert INTEGER 0 to DER', function() { ASSERT.equal(ASN1.integerToDer(0).toHex(), '00'); }); @@ -441,7 +480,7 @@ var UTIL = require('../../lib/util'); der = ASN1.toDer(asn1); if(options.roundtrip) { // byte comparisons for round-trip testing can fail due to - // symantically safe changes such as changing the length encoding. + // semantically safe changes such as changing the length encoding. // test a roundtrip for data where it makes sense. ASSERT.equal( UTIL.bytesToHex(bytes), @@ -1047,7 +1086,7 @@ var UTIL = require('../../lib/util'); // could extend out to any structure size: _add(b, '02 06 FF FF FF FF FF FF'); // the roundtrip issue can exist for long lengths that could - // compress to short lenghts, this could be output as '02 02 01 23': + // compress to short lengths, this could be output as '02 02 01 23': _add(b, '02 81 02 01 23'); // also an issue for indefinite length structures that will // have a known length later: @@ -1278,7 +1317,7 @@ var UTIL = require('../../lib/util'); UTIL.bytesToHex(derOut), UTIL.bytesToHex(_h2b(hout))); } - // optimial + // optimal _test('02 01 01', '02 01 01'); _test('02 01 FF', '02 01 FF'); _test('02 02 00 FF', '02 02 00 FF'); diff --git a/tests/unit/des.js b/tests/unit/des.js index 77ac035e4..093756d2b 100644 --- a/tests/unit/des.js +++ b/tests/unit/des.js @@ -31,6 +31,22 @@ var UTIL = require('../../lib/util'); ASSERT.equal(decipher.output.getBytes(), 'foobar'); }); + it('should check des-cbc short IV', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc596')); + + var error = null; + try { + var cipher = CIPHER.createCipher('DES-CBC', key); + cipher.start({iv: iv}); + } catch(e) { + error = e; + } + ASSERT.ok(error, 'blocksize check should have failed'); + }); + // OpenSSL equivalent: // openssl enc -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt it('should des-cbc encrypt: foobar', function() { @@ -61,6 +77,90 @@ var UTIL = require('../../lib/util'); ASSERT.equal(decipher.output.getBytes(), 'foobar'); }); + // play.golang.org/p/LX_dP0cFuEt + it('should des-ctr encrypt: foobar', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer('foobar')); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '3a97fa79e631'); + }); + + // play.golang.org/p/6_MQBYzn04c + it('should des-ctr decrypt: foobar', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('6df74b7b4437'))); + cipher.finish(); + ASSERT.equal(cipher.output.getBytes(), 'foobar'); + }); + + // play.golang.org/p/i892aR7YsGK + it('should des-ctr encrypt: dead parrot', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer('dead parrot')); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '389df47fa733dcf4b99b7c'); + }); + + // play.golang.org/p/6L0LqPS9ARt + it('should des-ctr decrypt: 79f1527c5737f774f85c1a9399755d895ae7', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('79f1527c5737f774f85c1a9399755d895ae7'))); + cipher.finish(); + ASSERT.equal(cipher.output.getBytes(), 'riverrun, past Eve'); + }); + + // play.golang.org/p/WsSx6BXJniU + it('should des-ctr encrypt: 69742773206e6f742073696c6c7920656e6f756768', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('69742773206e6f742073696c6c7920656e6f756768'))); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '358cb268a72dd2f2eb87615060bd3a490e85136873'); + }); + + // play.golang.org/p/y01inAlMCEM + it('should des-ctr decrypt: 0a80bd81a4dc1303a62f', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('0a80bd81a4dc1303a62f'))); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '01189998819991197253'); + }); + // OpenSSL equivalent: // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt it('should 3des-ecb encrypt: foobar', function() { diff --git a/tests/unit/forge.js b/tests/unit/forge.js new file mode 100644 index 000000000..fdbe58a2f --- /dev/null +++ b/tests/unit/forge.js @@ -0,0 +1,2 @@ +// test loading the entire module +require('../../lib/index.js'); diff --git a/tests/unit/index.js b/tests/unit/index.js index 4eb72d765..dfc441efc 100644 --- a/tests/unit/index.js +++ b/tests/unit/index.js @@ -1,3 +1,4 @@ +require('./forge'); require('./util'); require('./md5'); require('./sha1'); @@ -22,3 +23,4 @@ require('./pkcs7'); require('./pkcs12'); require('./tls'); require('./ssh'); +require('../security'); diff --git a/tests/unit/pem.js b/tests/unit/pem.js index dd989596f..e31dcf342 100644 --- a/tests/unit/pem.js +++ b/tests/unit/pem.js @@ -70,6 +70,66 @@ var PEM = require('../../lib/pem'); '0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n' + '-----END RSA PRIVATE KEY-----\r\n'; + var _csrWithNew = '-----BEGIN NEW CERTIFICATE REQUEST-----\r\n' + + 'MIIE9jCCAt4CAQAwfjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCFZpcmdpbmlhMRMw\r\n' + + 'EQYDVQQHDApCbGFja3NidXJnMR0wGwYDVQQKDBREaWdpdGFsIEJhemFhciwgSW5j\r\n' + + 'LjEMMAoGA1UECwwDT1NTMRowGAYDVQQDDBFkaWdpdGFsYmF6YWFyLmNvbTCCAiIw\r\n' + + 'DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKbqOZ0oC5L+GFnuvwuWnq5J/wxQ\r\n' + + '6upw5qvA+zfHZYkqdC170OYKsfC67/W6591631xGhVden26/BdxilpeSX1hFVqPF\r\n' + + 'IND7KJvo039QdFQzmzBgqcY5cr11OT9jYjoQMPCehRmbmv6RNaKqTdITMrGZMFzk\r\n' + + 'HFWfshuY71A0+wlz2pOzi79qL7tdcm5s6Whge3/0AAZi19Ze148vCH+HHnbQ7jMH\r\n' + + 'bGJlFZhvGYd2D/clCVnG4w4mCX6scMBZXtf4k1qZAuyhEpTJl8vxCExQs2iCN8lw\r\n' + + '4tEJH979MQsTDCNf5EZOBzMa4tJtybvQcmFQT2Xjb/8qYT0GyBP+XyJ6nmY3S0R2\r\n' + + 'xZtIsuKlayTw1GG/cYg3OC73G1lbVFLYLh1R+nEs14XX5Dj3J0zTxLeWewFIL7FP\r\n' + + 'D77oRqTHoHNIWz3SJ3S0OTqCYr+5h4vjUOCyXdjCZMZSFOWfCjcMIqcUsysj05gL\r\n' + + 'YBw5z+ZUn17zEEKBuq1tjS1UInbLPBbDMYc1P0NAO5UltdpOs0FPXWgHtzpVoYgZ\r\n' + + '7W2mXSTgP3xfVicWK6SBP0ejJmcgt4eB5gKidfg0t1BbB/4TgHLrDgGZapVA4DrX\r\n' + + 'agUxalhOrvV0Pm3zWdn6DNGNQbtm0xOebzEFL2bDRangK3OnA4EtOMj39cK2f4bY\r\n' + + '6ENG38DrC/ctvFmHAgMBAAGgMzAxBgkqhkiG9w0BCQ4xJDAiMAsGA1UdDwQEAwIE\r\n' + + 'MDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAGXNXqKmv\r\n' + + 'Dzkvm+ZTTmwsjf8zlCp1M+QtPSvCMGGUJtqwIFarIKc1H5ZIyfh3p+ws1xDFw0ZK\r\n' + + 'xPyIleeCqMVPAL9me4l8oaQ2IoQ917rmcsdfbPh3/8JkU5rotoRBW0JtsMTx5A6U\r\n' + + '7FluYFeKVTM1GZo3TpMhG7NZFePtIJfP/hPwtNnIrBkMOLmvyfN68UO1uhazx5/a\r\n' + + 'Uanp1JF9+05hwNSIL/R6TC/RQdeA5b3fycDPfhHhot7Bs/FczgF6I7Qrmyb4pzmR\r\n' + + 'e0knYlOucs0CsV/qj2K2Iouu0lWA0nZQQsbBtvN8dExYZpGPl4LJqNGYF4rLsoep\r\n' + + 'VyDD79rwCM6oqYbQ6GXQJdzXnQoAJTTFyg8bGmj9osBaSb8WKfz1VspnHzsbryxT\r\n' + + 'LPCI9Drg9kB28f7PGN0KWZnmWgD2qV/UuVPjxNhHTC8nEHCQP0gPeHrRgCyhDT4n\r\n' + + 'WPluKuX1B+xO5aOXOSmKcHNufDrN1l/ErhOvYeAimPq1Ag74Z946s27fO0M00kHK\r\n' + + '+ex8zj29okA0QSsJuCVbOA1tFlyoRd7apN/z1mpcvpb+TDZgdH/HFyrMK1bH2J5u\r\n' + + 'I1iuhuP3g2HSdjLC0wuUA4u73WcbcH7X9tnAHymFgGa5pNUlRPllbIRWvCM+7UaY\r\n' + + 'x6n+naGYblpSHXiboXRsuGWUtTjvqNVdOxA=\r\n' + + '-----END NEW CERTIFICATE REQUEST-----\r\n'; + + var _csrWithoutNew = '-----BEGIN CERTIFICATE REQUEST-----\r\n' + + 'MIIE9jCCAt4CAQAwfjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCFZpcmdpbmlhMRMw\r\n' + + 'EQYDVQQHDApCbGFja3NidXJnMR0wGwYDVQQKDBREaWdpdGFsIEJhemFhciwgSW5j\r\n' + + 'LjEMMAoGA1UECwwDT1NTMRowGAYDVQQDDBFkaWdpdGFsYmF6YWFyLmNvbTCCAiIw\r\n' + + 'DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKbqOZ0oC5L+GFnuvwuWnq5J/wxQ\r\n' + + '6upw5qvA+zfHZYkqdC170OYKsfC67/W6591631xGhVden26/BdxilpeSX1hFVqPF\r\n' + + 'IND7KJvo039QdFQzmzBgqcY5cr11OT9jYjoQMPCehRmbmv6RNaKqTdITMrGZMFzk\r\n' + + 'HFWfshuY71A0+wlz2pOzi79qL7tdcm5s6Whge3/0AAZi19Ze148vCH+HHnbQ7jMH\r\n' + + 'bGJlFZhvGYd2D/clCVnG4w4mCX6scMBZXtf4k1qZAuyhEpTJl8vxCExQs2iCN8lw\r\n' + + '4tEJH979MQsTDCNf5EZOBzMa4tJtybvQcmFQT2Xjb/8qYT0GyBP+XyJ6nmY3S0R2\r\n' + + 'xZtIsuKlayTw1GG/cYg3OC73G1lbVFLYLh1R+nEs14XX5Dj3J0zTxLeWewFIL7FP\r\n' + + 'D77oRqTHoHNIWz3SJ3S0OTqCYr+5h4vjUOCyXdjCZMZSFOWfCjcMIqcUsysj05gL\r\n' + + 'YBw5z+ZUn17zEEKBuq1tjS1UInbLPBbDMYc1P0NAO5UltdpOs0FPXWgHtzpVoYgZ\r\n' + + '7W2mXSTgP3xfVicWK6SBP0ejJmcgt4eB5gKidfg0t1BbB/4TgHLrDgGZapVA4DrX\r\n' + + 'agUxalhOrvV0Pm3zWdn6DNGNQbtm0xOebzEFL2bDRangK3OnA4EtOMj39cK2f4bY\r\n' + + '6ENG38DrC/ctvFmHAgMBAAGgMzAxBgkqhkiG9w0BCQ4xJDAiMAsGA1UdDwQEAwIE\r\n' + + 'MDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAGXNXqKmv\r\n' + + 'Dzkvm+ZTTmwsjf8zlCp1M+QtPSvCMGGUJtqwIFarIKc1H5ZIyfh3p+ws1xDFw0ZK\r\n' + + 'xPyIleeCqMVPAL9me4l8oaQ2IoQ917rmcsdfbPh3/8JkU5rotoRBW0JtsMTx5A6U\r\n' + + '7FluYFeKVTM1GZo3TpMhG7NZFePtIJfP/hPwtNnIrBkMOLmvyfN68UO1uhazx5/a\r\n' + + 'Uanp1JF9+05hwNSIL/R6TC/RQdeA5b3fycDPfhHhot7Bs/FczgF6I7Qrmyb4pzmR\r\n' + + 'e0knYlOucs0CsV/qj2K2Iouu0lWA0nZQQsbBtvN8dExYZpGPl4LJqNGYF4rLsoep\r\n' + + 'VyDD79rwCM6oqYbQ6GXQJdzXnQoAJTTFyg8bGmj9osBaSb8WKfz1VspnHzsbryxT\r\n' + + 'LPCI9Drg9kB28f7PGN0KWZnmWgD2qV/UuVPjxNhHTC8nEHCQP0gPeHrRgCyhDT4n\r\n' + + 'WPluKuX1B+xO5aOXOSmKcHNufDrN1l/ErhOvYeAimPq1Ag74Z946s27fO0M00kHK\r\n' + + '+ex8zj29okA0QSsJuCVbOA1tFlyoRd7apN/z1mpcvpb+TDZgdH/HFyrMK1bH2J5u\r\n' + + 'I1iuhuP3g2HSdjLC0wuUA4u73WcbcH7X9tnAHymFgGa5pNUlRPllbIRWvCM+7UaY\r\n' + + 'x6n+naGYblpSHXiboXRsuGWUtTjvqNVdOxA=\r\n' + + '-----END CERTIFICATE REQUEST-----\r\n'; + describe('pem', function() { it('should decode and re-encode PEM messages', function() { var msgs = PEM.decode(_input); @@ -81,5 +141,19 @@ var PEM = require('../../lib/pem'); ASSERT.equal(output, _input); }); + + it('should decode a CSR from PEM with NEW in the labels', function() { + var csrs = PEM.decode(_csrWithNew); + for(var i = 0; i < csrs.length; ++i) { + ASSERT.equal(csrs[i].type, 'CERTIFICATE REQUEST'); + } + }); + + it('should decode a CSR from PEM without NEW in the labels', function() { + var csrs = PEM.decode(_csrWithoutNew); + for(var i = 0; i < csrs.length; ++i) { + ASSERT.equal(csrs[i].type, 'CERTIFICATE REQUEST'); + } + }); }); })(); diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index 0cdd28e01..d68946d8f 100644 --- a/tests/unit/rsa.js +++ b/tests/unit/rsa.js @@ -1,5 +1,6 @@ var ASSERT = require('assert'); var FORGE = require('../../lib/forge'); +var JSBN = require('../../lib/jsbn'); var MD = require('../../lib/md.all'); var MGF = require('../../lib/mgf'); var PKI = require('../../lib/pki'); @@ -580,7 +581,7 @@ var UTIL = require('../../lib/util'); /* Second step, use private key decryption to verify successful encryption. The encrypted message differs every time, since it is padded with random data. Therefore just rely on the decryption - routine to work, which is tested seperately against an externally + routine to work, which is tested separately against an externally provided encrypted message. */ key = PKI.privateKeyFromPem(params.privateKeyPem); ASSERT.equal(key.decrypt(data), message); @@ -773,5 +774,382 @@ var UTIL = require('../../lib/util'); }); } })(); + + describe('signature verification', function() { + + // NOTE: Tests in this section, and associated fixes, are largely derived + // from a detailed vulnerability report provided by Moosa Yahyazadeh + // (moosa-yahyazadeh@uiowa.edu). + + // params for tests + + // public modulus / 256 bytes + var N = new JSBN.BigInteger( + 'E932AC92252F585B3A80A4DD76A897C8B7652952FE788F6EC8DD640587A1EE56' + + '47670A8AD4C2BE0F9FA6E49C605ADF77B5174230AF7BD50E5D6D6D6D28CCF0A8' + + '86A514CC72E51D209CC772A52EF419F6A953F3135929588EBE9B351FCA61CED7' + + '8F346FE00DBB6306E5C2A4C6DFC3779AF85AB417371CF34D8387B9B30AE46D7A' + + '5FF5A655B8D8455F1B94AE736989D60A6F2FD5CADBFFBD504C5A756A2E6BB5CE' + + 'CC13BCA7503F6DF8B52ACE5C410997E98809DB4DC30D943DE4E812A47553DCE5' + + '4844A78E36401D13F77DC650619FED88D8B3926E3D8E319C80C744779AC5D6AB' + + 'E252896950917476ECE5E8FC27D5F053D6018D91B502C4787558A002B9283DA7', + 16); + + // private exponent + var d = new JSBN.BigInteger( + '009b771db6c374e59227006de8f9c5ba85cf98c63754505f9f30939803afc149' + + '8eda44b1b1e32c7eb51519edbd9591ea4fce0f8175ca528e09939e48f37088a0' + + '7059c36332f74368c06884f718c9f8114f1b8d4cb790c63b09d46778bfdc4134' + + '8fb4cd9feab3d24204992c6dd9ea824fbca591cd64cf68a233ad0526775c9848' + + 'fafa31528177e1f8df9181a8b945081106fd58bd3d73799b229575c4f3b29101' + + 'a03ee1f05472b3615784d9244ce0ed639c77e8e212ab52abddf4a928224b6b6f' + + '74b7114786dd6071bd9113d7870c6b52c0bc8b9c102cfe321dac357e030ed6c5' + + '80040ca41c13d6b4967811807ef2a225983ea9f88d67faa42620f42a4f5bdbe0' + + '3b', + 16); + + // public exponent + var e = new JSBN.BigInteger('3'); + + // hash function + // H = SHA-256 (OID = 0x608648016503040201) + + // message + var m = 'hello world!'; + + // to-be-signed RSA PKCS#1 v1.5 signature scheme input structure + // I + + // signature value obtained by I^d mod N + // S + + function _checkBadTailingGarbage(publicKey, S) { + var md = MD.sha256.create(); + md.update(m); + + ASSERT.throws(function() { + publicKey.verify(md.digest().getBytes(), S); + }, + /^Error: Unparsed DER bytes remain after ASN.1 parsing.$/); + } + + function _checkBadDigestInfo(publicKey, S, skipTailingGarbage) { + var md = MD.sha256.create(); + md.update(m); + + ASSERT.throws(function() { + publicKey.verify(md.digest().getBytes(), S, undefined, { + _parseAllDigestBytes: !skipTailingGarbage + }); + }, + /^Error: ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value.$/); + } + + function _checkGoodDigestInfo(publicKey, S, skipTailingGarbage) { + var md = MD.sha256.create(); + md.update(m); + + ASSERT.ok(publicKey.verify(md.digest().getBytes(), S, undefined, { + _parseAllDigestBytes: !skipTailingGarbage + })); + } + + it('should check DigestInfo structure', function() { + var publicKey = RSA.setPublicKey(N, e); + // 0xff bytes stolen from padding + // unchecked portion of PKCS#1 encoded message used to forge a + // signature when low public exponent is being used. + // See "Bleichenbacher's RSA signature forgery based on implementation + // error" by Hal Finney + // https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/ + + // 91 garbage byte injected as the value of a TLV replaced digest + // algorithm structure + var I = UTIL.binary.hex.decode( + '0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0030' + + '7f065b8888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888880420' + + '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9'); + var S = UTIL.binary.hex.decode( + 'e7410e05bdc38d1c72fab784be41df3d3de2ae83894d9ec86cb5fe343d5dc7d4' + + '5df2a36fc60363faf32f0d37ab457648af40a48a6c53ae7af0575e92cb1ffc23' + + '6d55e1325af8c71b3ac313f2630fb498b8e1546093aca1ed56026a96cb525d99' + + '1159a2d6ccbfd5ef63ae718f8ace2469e357ccf3f6a048bbf9760f5fb36b9dd3' + + '8fb330eab504f05078b83f5d8bd95dce8fccc6b46babd56f678300f2b39083e5' + + '3e04e79f503358a6222f8dd66b561fea3a51ecf3be16c9e2ea6ba8aaed9fbe6b' + + 'a510ff752e4529385f759d4d6120b15f65534248ed5bbb1307a7d0a983832969' + + '7f5fbae91f48e478dcbb77190f0d173b6cb8b1299cf4202570d25d11a7862b47'); + + _checkBadDigestInfo(publicKey, S); + }); + + it('should check tailing garbage and DigestInfo [1]', function() { + var publicKey = RSA.setPublicKey(N, e); + // bytes stolen from padding and unchecked tailing bytes used to forge + // a signature when low public exponent is used + + // 204 tailing garbage bytes injected after DigestInfo structure + var I = UTIL.binary.hex.decode( + '000100302f300b060960864801650304020104207509e5bda0c762d2bac7f90d' + + '758b5b2263fa01ccbc542ab5e3df163be08e6ca9888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888'); + var S = UTIL.binary.hex.decode( + 'c2ad2fa23c246ee98c453d69023e7ec05956b48bd0e287341ba9d342ad49b0ff' + + 'f2bcbb9adc50f1ccbfc54106305cc74a88db89ff94901a08359893a08426373e' + + '7949a8794798233445af6c48bc6ccbe278bdeb62c31e40c3bf0014af2faadcc9' + + 'ed7885756789a5b95c2a355fbb3f04412f42e0f9ed335ab51af8f091a62aaaaf' + + '6577422220917daaece3ca2f4e66dc4e0574356762592052b406768c31c25cf4' + + 'c1754e6da9dc3440e238c4f9b25cccc174dd1b17b027e0f9ce2763b86f0e6871' + + '690ddd018d2e774bc968c9c6e907a000daf5044ba31a0b9eefbd7b4b1ec466d2' + + '0bc1dd3f020cb1091af6b476416da3024ea046b09fbbbc4d2355da9a2bc6ddb9'); + + _checkBadTailingGarbage(publicKey, S); + _checkGoodDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [2]', function() { + var publicKey = RSA.setPublicKey(N, e); + // bytes stolen from padding and unchecked tailing bytes used to forge + // a signature when low public exponent is used + + // 215 tailing garbage bytes injected after DigestInfo structure + // unchecked digest algorithm structure + // combined with earlier issue + var I = UTIL.binary.hex.decode( + '0001003024010004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542a' + + 'b5e3df163be08e6ca98888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888' + + '8888888888888888888888888888888888888888888888888888888888888888'); + var S = UTIL.binary.hex.decode( + 'a7c5812d7fc0eef766a481aac18c8c48483daf9b5ffb6614bd98ebe4ecb746dd' + + '493cf5dd2cbe16ecaa0b52109b744930eda49316605fc823fd57a68b5b2c62e8' + + 'c1b158b26e1547a2e33cdd79427d7c513f07d02261ffe43db197d8cddca2b5b4' + + '3c1df85aaed6e91aadd44a46bff7f5c70f1acc1a193917e3908444632f30e69c' + + 'fe95d8036d3b6ad318eefd3952804f16613c969e6d13604bb4e723dfad24c42c' + + '8d9b5b16a9f5a4b40dcf17b167d319017740f9cc0836436c14d51c3d8a697f1f' + + 'a2b65196deb5c21b1559c7dea7f598007fa7320909825009f8bf376491c298d8' + + '155a382e967042db952e995d14b2f961e1b22f911d1b77895def1c7ef229c87e'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=3]', function() { + // signature forged without knowledge of private key for given message + // and low exponent e=3 + + // test data computed from a script + var N = new JSBN.BigInteger( + '2943851338959486749023220128247883872673446416188780128906858510' + + '0507839535636256317277708295678804401391394313946142335874609638' + + '6660819509361141525748702240343825617847432837639613499808068190' + + '7802897559477710338828027239284411238090037450817022107555351764' + + '1170327441791034393719271744724924194371070527213991317221667249' + + '0779727008421990374037994805699108447010306443226160454080397152' + + '7839457232809919202392450307767317822761454935119120485180507635' + + '9472439160130994385433568113626206477097769842080459156024112389' + + '4062006872333417793816670825914214968706669312685485046743622307' + + '25756397511775557878046572472650613407143'); + var e = new JSBN.BigInteger('3'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000002853ccc2cd32a8d430dd3bde37e70782ac82cdb7bc' + + 'e3c044219b50aefd689c20d3b840299f28e2fde6c67c8a7f9e528ac222fae947' + + 'a6dee0d812e3c3b3452171717396e8bedc3132d92d8317e3593642640d1431ef'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=5]', function() { + // signature forged without knowledge of private key for given message + // and low exponent e=5 + + // test data computed from a script + var N = new JSBN.BigInteger( + '2943851338959486749023220128247883872673446416188780128906858510' + + '0507839535636256317277708295678804401391394313946142335874609638' + + '6660819509361141525748702240343825617847432837639613499808068190' + + '7802897559477710338828027239284411238090037450817022107555351764' + + '1170327441791034393719271744724924194371070527213991317221667249' + + '0779727008421990374037994805699108447010306443226160454080397152' + + '7839457232809919202392450307767317822761454935119120485180507635' + + '9472439160130994385433568113626206477097769842080459156024112389' + + '4062006872333417793816670825914214968706669312685485046743622307' + + '25756397511775557878046572472650613407143'); + var e = new JSBN.BigInteger('5'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '000000000000000000000000005475fe2681d7125972bd2c2f2c7ab7b8003b03' + + 'd4a487d6dee07c14eb5212a9fe0071b93f84ba5bb4b0cfaf20c976b11d902013'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check tailing garbage and DigestInfo [e=17]', function() { + // signature forged without knowledge of private key for given message + // and low exponent e=17 + + // test data computed from a script + var N = new JSBN.BigInteger( + '9283656416612985262941143827717696579056959956800096804440022580' + + '8979605519224532102091105159037909758713334182004379540747102163' + + '0328875171430160513961779154294247563032373839871165519961382202' + + '8118288833646515747631246999476620608496831766892861810215014002' + + '6197665341672524640393361361575818164897153768964295647456396149' + + '0989544033629566558036444831495046301215543198107208071526376318' + + '9614817392787691228850316867637768748063173527415482321108924014' + + '0172719575883597580010690402077593789150581979877629529469651667' + + '0437057465296389148672556848624501468669295285428387365416747516' + + '1806526300547653933352115280843297169178217266705491556199868750' + + '3004910766820506445410432860104193197231996634882562129969319354' + + '2460060799067674344247887198933507132592770898312271636011037138' + + '9847292565155151851533347436854797090854109022697775636916157198' + + '8470890850961835279273782642105981947430594900197891694944702901' + + '0362775778664826653636547333219983468955600305523140183269580452' + + '7928125033990422010817859727072181449684606236639224708148897385' + + '6473081641220112881037032407068024585466913055187295801749427746' + + '8722193869883705529583737211815974801292292728082721785855274147' + + '9919792200010181565600099271483749952360303834740314188025547140' + + '4368096941701515529809239068018840617766710102093620675455198522' + + '9636814788735090951246816765035721775759652424641736739668936540' + + '4502328148572893125899985056273755530380627654934084609415976292' + + '9123186604266210829116435949633497856328752368587226250956046322' + + '5096226739991402761266388226652661345282274508037924611589455395' + + '6555120130786293751868059518231813715612891296160287687335835654' + + '3979850800254668550551247800296013251153132326459614458561196296' + + '9372672455541953777622436993987703564293487820434112162562492086' + + '8651475984366477254452308612460939500200990849949906321025068481' + + '9019640785570574553040761725312997166593985384222496507953730319' + + '8339986953399517682750248394628026225887174258267456078564070387' + + '3276539895054169432261639890044193773631304665663877617572725639' + + '9608670862191314058068741469812649057261850985814174869283757023' + + '5128900627675422927964369356691123905362222855545719945605604307' + + '2632528510813096225692258119794268564646732338755890857736163737' + + '9885700134409359441713832300526017978115395080312777381770201653' + + '4081581157881295739782000814998795398671806283018844936919299070' + + '5625387639000374694851356996772485803653791257029031861749956519' + + '3846941219138832785295572786934547608717304766525989212989524778' + + '5416834855450881318585909376917039'); + var e = new JSBN.BigInteger('17'); + var publicKey = RSA.setPublicKey(N, e); + + var S = UTIL.binary.hex.decode( + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000001eb90acbec1bf590ba1e50960db8381fb5bdc363d46379d09956560a6' + + '16b88616ce7fa4309dc45f47f5fa47d61bf66baa3d11732ce71768ded295f962'); + + _checkBadTailingGarbage(publicKey, S); + _checkBadDigestInfo(publicKey, S, true); + }); + + it('should check DigestInfo type octet [1]', function() { + var publicKey = RSA.setPublicKey(N, e); + // incorrect value for digest algorithm's type octet + // 0x0c instead of correct 0x06 + var I = UTIL.binary.hex.decode( + '0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffff0030310c0d060960864801650304020105000420' + + '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9'); + var S = UTIL.binary.hex.decode( + 'd8298a199e1b6ac18f3c0067a004bd9ff7af87be6ad857d73cc3d24ef06195b8' + + '2aaddb0194f8e61fc31453b9163062255e8baf9c480200d0991a5f764f63d5f6' + + 'afd283b9cd6afe54f0b7f738707b4eb6b8807539bb627e74db87a50413ab18e5' + + '04e37975aad1edc612bc8ecad53b81ea249deb5a2acc27e6419c61ab9acec660' + + '8f5ae6a2985ba0b6f42d831bc6cce4b044864154b935cf179967d129e0ad8eda' + + '9bfbb638121c3ff13c64d439632e62250d4be928a3deb112ef76a025c5d91805' + + '1e601878eac0049fc9d82be9ae3475deb7ca515c830c20b91b7bedf2184fef66' + + 'aea0bde62ccd1659afbfd1342322b095309451b1a87e007e640e368fb68a13c9'); + + _checkBadDigestInfo(publicKey, S); + }); + + it('should check DigestInfo type octet [2]', function() { + var publicKey = RSA.setPublicKey(N, e); + // incorrect value for hash value's type octet + // 0x0a instead of correct 0x04 + var I = UTIL.binary.hex.decode( + '0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffff003031300d060960864801650304020105000a20' + + '7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9'); + var S = UTIL.binary.hex.decode( + 'c1acdd3aef5f0439c254980295fc0d81b628df00726310a1041d79b5dd94c11d' + + '3bcaf0236763c77c25d9ab49522ed2a7d6ea3a4e483a29838acd48f2d60a7902' + + '75f4cd46e4b1d09c527a426ec373e8a21746ad3ea541d3b85ba4c303ff793ea8' + + 'a0a3458e93a7ec42ed66f675d7c299b0817ac95f7f45b2f48c09b3c070171f31' + + 'a33ac789da9943da5dabcda1c95b42531d45484ac1efde0fe0519077debb9318' + + '3e63de8f80d7f3cbfecb03cbb44ac4a2d56699e33fca0663b79ca627755fc4fc' + + '684b4ab358a0b4ac5b7e9d0cc18b6ab6300b40781502a1c03d34f31dd19d8119' + + '5f8a44bc03a2595a706f06f0cb39b8e3f4afe06675fe7439b057f1200a06f4fd'); + + _checkBadDigestInfo(publicKey, S); + }); + }); }); })(); diff --git a/tests/unit/x509.js b/tests/unit/x509.js index 43a9ea61b..474e97a89 100644 --- a/tests/unit/x509.js +++ b/tests/unit/x509.js @@ -1206,6 +1206,70 @@ var UTIL = require('../../lib/util'); ASSERT.ok(issuer.verify(cert)); }); + it('should calculate a certificate subject and issuer hash', function() { + var certPem = '-----BEGIN CERTIFICATE-----\r\n' + + 'MIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\n' + + 'VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\n' + + 'dGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\n' + + 'MGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\n' + + 'b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\n' + + 'Z29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n' + + '0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\n' + + 'gXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\n' + + 'gyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\n' + + 'KwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n' + + '0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\n' + + 'TqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\n' + + 'aXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\n' + + 'VgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\n' + + 'ZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n' + + '/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\n' + + 'Xp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n' + + '7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\n' + + 'LsngXwZSuL0=\r\n' + + '-----END CERTIFICATE-----\r\n'; + var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' + + 'MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\n' + + 'MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\n' + + 'aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\n' + + 'WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\n' + + 'R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\n' + + 'gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\n' + + 'NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\n' + + 'qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\n' + + 'oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\n' + + 'MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\n' + + 'Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\n' + + 'Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\n' + + 'BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n' + + '0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\n' + + 'UUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n' + + '-----END CERTIFICATE-----\r\n'; + var cert = PKI.certificateFromPem(certPem, true); + var issuer = PKI.certificateFromPem(issuerPem); + ASSERT.strictEqual(issuer.subject.hash, 'd43b6713ab1a8679f0b70e169e9df889ed387a4b'); + ASSERT.strictEqual(cert.subject.hash, 'fd90a93e35c96cd6959f45ec60ca76faa4ce8926'); + ASSERT.strictEqual(cert.issuer.hash, 'd43b6713ab1a8679f0b70e169e9df889ed387a4b'); + }); + + it('should verify certificate with sha1WithRSASignature signature', function() { + var certPem = '-----BEGIN CERTIFICATE-----\r\n' + + 'MIIBwjCCAS+gAwIBAgIQj2d4hVEz0L1DYFVhA9CxCzAJBgUrDgMCHQUAMA8xDTAL\r\n' + + 'BgNVBAMTBFZQUzEwHhcNMDcwODE4MDkyODUzWhcNMDgwODE3MDkyODUzWjAPMQ0w\r\n' + + 'CwYDVQQDEwRWUFMxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaqKn40uaU\r\n' + + 'DbFL1NXXZ8/b4ZqDJ6eSI5lysMZHfZDs60G3ocbNKofBvURIutabrFuBCB2S5f/z\r\n' + + 'ICan0LR4uFpGuZ2I/PuVaU8X5fT8gBh7L636cWzHPPScYts00OyywEq381UB7XwX\r\n' + + 'YuWpM5kUW5rkbq1JV3ystTR/4YnLl48YtQIDAQABoycwJTATBgNVHSUEDDAKBggr\r\n' + + 'BgEFBQcDATAOBgNVHQ8EBwMFALAAAAAwCQYFKw4DAh0FAAOBgQBuUrU+J2Z5WKcO\r\n' + + 'VNjJHFUKo8qpbn8jKQZDl2nvVaXCTXQZblz/qxOm4FaGGzJ/m3GybVZNVfdyHg+U\r\n' + + 'lmDpFpOITkvcyNc3xjJCf2GVBo/VvdtVt7Myq0IQtAi/CXRK22BRNhSt9uu2EcRu\r\n' + + 'HIXdFWHEzi6eD4PpNw/0X3ID6Gxk4A==\r\n' + + '-----END CERTIFICATE-----\r\n'; + var cert = PKI.certificateFromPem(certPem, true); + ASSERT.equal(cert.signatureOid, PKI.oids['sha1WithRSASignature']); + ASSERT.equal(cert.md.algorithm, 'sha1'); + }); + it('should verify certificate with sha256WithRSAEncryption signature', function() { var certPem = '-----BEGIN CERTIFICATE-----\r\n' + 'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' + diff --git a/tests/websockets/server-webid.js b/tests/websockets/server-webid.js index 6f7cf37b8..c2d4e2a1b 100644 --- a/tests/websockets/server-webid.js +++ b/tests/websockets/server-webid.js @@ -80,7 +80,7 @@ var getPublicKey = function(data, uri, callback) { var hex = CERT + 'hex'; var decimal = CERT + 'decimal'; - // gets a resource identifer from a node + // gets a resource identifier from a node var getResource = function(node, key) { var rval = null; @@ -174,9 +174,10 @@ var fetchUrl = function(url, callback, redirects) { console.log('Fetching URL: \"' + url + '\"'); // parse URL - url = forge.util.parseUrl(url); - var client = http.createClient( - url.port, url.fullHost, url.scheme === 'https'); + url = new URL(url); + var client = http.createClient({ + url: url + }); var request = client.request('GET', url.path, { Host: url.host, Accept: 'application/rdf+xml' diff --git a/webpack.config.js b/webpack.config.js index 806401eff..df0db4d34 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,6 @@ * Copyright 2011-2016 Digital Bazaar, Inc. */ const path = require('path'); -const webpack = require('webpack'); // build multiple outputs module.exports = []; @@ -79,6 +78,7 @@ outputs.forEach(info => { // plain unoptimized unminified bundle const bundle = Object.assign({}, common, { + mode: 'development', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.js', @@ -95,6 +95,7 @@ outputs.forEach(info => { // optimized and minified bundle const minify = Object.assign({}, common, { + mode: 'production', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.min.js', @@ -103,6 +104,7 @@ outputs.forEach(info => { }, devtool: 'cheap-module-source-map', plugins: [ + /* new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { @@ -113,6 +115,7 @@ outputs.forEach(info => { } //beautify: true }) + */ ] }); if(info.library === null) {