diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..a63a29963 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,73 @@ +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] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - 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: [14.x] + bundler: [webpack, browserify] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + 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: [14.x] +# steps: +# - uses: actions/checkout@v2 +# - name: Use Node.js ${{ matrix.node-version }} +# uses: actions/setup-node@v1 +# 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: [14.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + 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@v1 + with: + file: ./coverage/lcov.info + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 134b7dedd..01519a399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.py[co] *.sw[nop] *~ -.bower.json .cdtproject .classpath .cproject diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6ea8d7085..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: node_js -node_js: - - "6" - - "8" - - "10" - - "12" - - "14" - - "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: - - name: "Browser Unit Tests (webpack)" - node_js: "14" - env: BUNDLER=webpack - - name: "Browser Unit Tests (browserify)" - node_js: "14" - env: BUNDLER=browserify -notifications: - email: - on_success: change - on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index 81176fa81..27d0e3a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,154 @@ Forge ChangeLog =============== -## 0.10.0 - 2019-09-01 +## 1.3.1 - 2022-03-29 + +### Fixes +- RFC 3447 and RFC 8017 allow for optional `DigestAlgorithm` `NULL` parameters + for `sha*` algorithms and require `NULL` paramters 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 browers + 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 @@ -15,11 +162,11 @@ Forge ChangeLog 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, consier `get`, `set`, and `unset` + 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 - 2019-09-01 +## 0.9.2 - 2020-09-01 ### Changed - Added `util.setPath` security note to function docs and to README. 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..6f3279efb 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,7 +2018,8 @@ 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 --------- @@ -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/lib/asn1.js b/lib/asn1.js index e0fea0e08..4025f8a9e 100644 --- a/lib/asn1.js +++ b/lib/asn1.js @@ -411,6 +411,8 @@ 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 @@ -418,24 +420,31 @@ var _getValueLength = function(bytes, remaining) { * flag will be deprecated or removed as soon as schema support is * available. (default: true) * + * @throws Will throw an error for various malformed input conditions. + * * @return the parsed asn1 object. */ 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; } @@ -445,7 +454,15 @@ asn1.fromDer = function(bytes, options) { 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; }; /** @@ -566,7 +583,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 +631,7 @@ function _fromDer(bytes, remaining, depth, options) { } } else { value = bytes.getBytes(length); + remaining -= length; } } @@ -1391,7 +1408,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/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/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/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/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..5c73209f9 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 paramters + 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. * @@ -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 vaidator 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 identifer 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) { diff --git a/lib/util.js b/lib/util.js index 98dfd3427..aaede5ad2 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2258,261 +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) : ''); -}; - /** * Check if an object is empty. * diff --git a/lib/x509.js b/lib/x509.js index 95dbc2946..2877810c1 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; diff --git a/lib/xhr.js b/lib/xhr.js index e493c3b60..fa928352b 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; } } diff --git a/package.json b/package.json index f5cc879f9..1a63de157 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.10.0", + "version": "1.3.1", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { @@ -18,8 +18,8 @@ "browserify": "^16.5.2", "commander": "^2.20.0", "cross-env": "^5.2.1", - "eslint": "^7.8.1", - "eslint-config-digitalbazaar": "^2.5.0", + "eslint": "^7.27.0", + "eslint-config-digitalbazaar": "^2.8.0", "express": "^4.16.2", "karma": "^4.4.1", "karma-browserify": "^7.0.0", @@ -60,7 +60,7 @@ "dist/*.min.js.map" ], "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" }, "keywords": [ "aes", @@ -95,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/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/xhr.html b/tests/legacy/xhr.html index 5aa868f54..fbb086f2a 100644 --- a/tests/legacy/xhr.html +++ b/tests/legacy/xhr.html @@ -5,6 +5,7 @@ + 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/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..4607ecb12 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 = '?'; @@ -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/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..c881a4366 100644 --- a/tests/unit/index.js +++ b/tests/unit/index.js @@ -1,3 +1,4 @@ +require('./forge'); require('./util'); require('./md5'); require('./sha1'); 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..b1c1b64aa 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'); @@ -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..5319372bb 100644 --- a/tests/websockets/server-webid.js +++ b/tests/websockets/server-webid.js @@ -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'