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 @@
[](https://nodei.co/npm/node-forge/)
-[](https://travis-ci.org/digitalbazaar/forge)
+[](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'