diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..7fa4468b3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + root: true, + env: { + browser: true, + commonjs: true, + node: true + }, + extends: [ + 'digitalbazaar' + ], + parserOptions: { + ecmaVersion: 5, + sourceType: 'script' + }, + rules: { + // overrides to support ES5, remove when updated to ES20xx + 'no-unused-vars': 'warn', + 'no-var': 'off', + 'object-shorthand': 'off', + 'prefer-const': 'off', + // fix when code is globally reformatted + 'max-len': 'off' + } +}; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..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 3c9cc9129..01519a399 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ *.py[co] *.sw[nop] *~ -.bower.json .cdtproject .classpath .cproject .nyc_output .project .settings +.vscode TAGS coverage dist diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index cc52fb3ae..000000000 --- a/.jscsrc +++ /dev/null @@ -1,83 +0,0 @@ -{ - "excludeFiles": [ - "./lib/jsbn.js" - ], - "disallowKeywords": ["with"], - "disallowKeywordsOnNewLine": ["else", "catch"], - // FIXME: enable this? - //"disallowImplicitTypeConversion": ["string"], - "disallowMixedSpacesAndTabs": true, - "disallowMultipleLineBreaks": true, - // FIXME: enable this or do we prefer to - // use w/angular directive templates? - //"disallowMultipleLineStrings": true, - "disallowNewlineBeforeBlockStatements": true, - //"disallowSpaceAfterKeywords": [ - // "if", - // "for", - // "while", - // "do", - // "switch", - // "catch" - //], - "disallowSpaceAfterObjectKeys": true, - "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], - "disallowSpaceBeforeBinaryOperators": [","], - "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], - "disallowSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - //"disallowSpacesInCallExpression": true, - "disallowSpacesInFunctionDeclaration": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInNamedFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - //"disallowSpacesInsideArrayBrackets": true, - "disallowSpacesInsideParentheses": true, - "disallowTrailingComma": true, - "disallowTrailingWhitespace": true, - //"maximumLineLength": { - // "value": 80, - // "allExcept": ["comments", "regex"] - //}, - "requireCommaBeforeLineBreak": true, - "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], - "requireLineFeedAtFileEnd": true, - "requireSemicolons": true, - "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], - "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], - //"requireSpaceBeforeObjectValues": true, - "requireSpaceAfterKeywords": [ - "else", - "do", - "return", - "try" - ], - //"requireSpaceBeforeKeywords": [ - // "else", - // "while", - // "catch" - //], - "requireSpaceBeforeBlockStatements": true, - "requireSpacesInConditionalExpression": { - "afterTest": true, - "beforeConsequent": true, - "afterConsequent": true, - "beforeAlternate": true - }, - //"requireSpacesInForStatement": true, - "requireSpacesInFunction": { - "beforeOpeningCurlyBrace": true - }, - "safeContextKeyword": "self", - //"validateIndentation": 2, - "validateLineBreaks": "LF", - // FIXME: enable doc checks (update to use newer jscs jsdoc module) - //"validateJSDoc": { - // "checkParamNames": true, - // "requireParamTypes": true - //}, - "validateParameterSeparator": ", " -} diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index ef5cdec92..000000000 --- a/.jshintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sub": true, - "esversion": 6 -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fc33ed895..000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: node_js -node_js: - - "4" - - "6" - - "8" - - "node" -sudo: false -install: npm install -script: - - if [ "x$BUNDLER" = "x" ]; then npm test; fi - - if [ "x$BUNDLER" != "x" ]; then npm run test-karma; fi -# only run karma tests for one node version -matrix: - include: - - node_js: "6" - env: BUNDLER=webpack - - node_js: "6" - env: BUNDLER=browserify -notifications: - email: - on_success: change - on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index f45b768fd..2987c54b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,273 @@ Forge ChangeLog =============== +## 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 + non-invasive patches to keep it working will be considered. However, more + modern tools no longer support old Node.js versions making testing difficult. + +### Removed +- **BREAKING**: Remove `util.getPath`, `util.setPath`, and `util.deletePath`. + `util.setPath` had a potential prototype pollution security issue when used + with unsafe inputs. These functions are not used by `forge` itself. They date + from an early time when `forge` was targeted at providing general helper + functions. The library direction changed to be more focused on cryptography. + Many other excellent libraries are more suitable for general utilities. If + you need a replacement for these functions, consider `get`, `set`, and `unset` + from [lodash](https://lodash.com/). But also consider the potential similar + security issues with those APIs. + +## 0.9.2 - 2020-09-01 + +### Changed +- Added `util.setPath` security note to function docs and to README. + +### Notes +- **SECURITY**: The `util.setPath` function has the potential to cause + prototype pollution if used with unsafe input. + - This function is **not** used internally by `forge`. + - The rest of the library is unaffected by this issue. + - **Do not** use unsafe input with this function. + - Usage with known input should function as expected. (Including input + intentionally using potentially problematic keys.) + - No code changes will be made to address this issue in 0.9.x. The current + behavior *could* be considered a feature rather than a security issue. + 0.10.0 will be released that removes `util.getPath` and `util.setPath`. + Consider `get` and `set` from [lodash](https://lodash.com/) if you need + replacements. But also consider the potential similar security issues with + those APIs. + - https://snyk.io/vuln/SNYK-JS-NODEFORGE-598677 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7720 + +## 0.9.1 - 2019-09-26 + +### Fixed +- Ensure DES-CBC given IV is long enough for block size. + +## 0.9.0 - 2019-09-04 + +### Added +- Add ed25519.publicKeyFromAsn1 and ed25519.privateKeyFromAsn1 APIs. +- A few OIDs used in EV certs. + +### Fixed +- Improve ed25519 NativeBuffer check. + +## 0.8.5 - 2019-06-18 + +### Fixed +- Remove use of `const`. + +## 0.8.4 - 2019-05-22 + +### Changed +- Replace all instances of Node.js `new Buffer` with `Buffer.from` and `Buffer.alloc`. + +## 0.8.3 - 2019-05-15 + +### Fixed +- Use basic character set for code. + +## 0.8.2 - 2019-03-18 + +### Fixed +- Fix tag calculation when continuing an AES-GCM block. + +### Changed +- Switch to eslint. + +## 0.8.1 - 2019-02-23 + +### Fixed +- Fix off-by-1 bug with kem random generation. + +## 0.8.0 - 2019-01-31 + +### Fixed +- Handle creation of certificates with `notBefore` and `notAfter` dates less + than Jan 1, 1950 or greater than or equal to Jan 1, 2050. + +### Added +- Add OID 2.5.4.13 "description". +- Add OID 2.16.840.1.113730.1.13 "nsComment". + - Also handle extension when creating a certificate. +- `pki.verifyCertificateChain`: + - Add `validityCheckDate` option to allow checking the certificate validity + period against an arbitrary `Date` or `null` for no check at all. The + current date is used by default. +- `tls.createConnection`: + - Add `verifyOptions` option that passes through to + `pki.verifyCertificateChain`. Can be used for the above `validityCheckDate` + option. + +### Changed +- Support WebCrypto API in web workers. +- `rsa.generateKeyPair`: + - Use `crypto.generateKeyPair`/`crypto.generateKeyPairSync` on Node.js if + available (10.12.0+) and not in pure JS mode. + - Use JS fallback in `rsa.generateKeyPair` if `prng` option specified since + this isn't supported by current native APIs. + - Only run key generation comparison tests if keys will be deterministic. +- PhantomJS is deprecated, now using Headless Chrome with Karma. +- **Note**: Using Headless Chrome vs PhantomJS may cause newer JS features to + slip into releases without proper support for older runtimes and browsers. + Please report such issues and they will be addressed. +- `pki.verifyCertificateChain`: + - Signature changed to `(caStore, chain, options)`. Older `(caStore, chain, + verify)` signature is still supported. New style is to to pass in a + `verify` option. + +## 0.7.6 - 2018-08-14 + +### Added +- Test on Node.js 10.x. +- Support for PKCS#7 detached signatures. + +### Changed +- Improve webpack/browser detection. + ## 0.7.5 - 2018-03-30 ### Fixed 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 4a92660de..6f3279efb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![npm package](https://nodei.co/npm/node-forge.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/node-forge/) -[![Build status](https://img.shields.io/travis/digitalbazaar/forge.svg?branch=master)](https://travis-ci.org/digitalbazaar/forge) +[![Build Status](https://github.com/digitalbazaar/forge/workflows/Main%20Checks/badge.svg)](https://github.com/digitalbazaar/forge/actions?query=workflow%3A%22Main+Checks%22) A native implementation of [TLS][] (and various other cryptographic tools) in [JavaScript][]. @@ -80,7 +80,6 @@ Documentation * [Tasks](#task) * [Utilities](#util) * [Logging](#log) -* [Debugging](#debug) * [Flash Networking Support](#flash) ### Other @@ -106,7 +105,7 @@ not be regularly updated. If you want to use forge with [Node.js][], it is available through `npm`: -https://npmjs.org/package/node-forge +https://www.npmjs.com/package/node-forge Installation: @@ -121,24 +120,12 @@ var forge = require('node-forge'); The npm package includes pre-built `forge.min.js`, `forge.all.min.js`, and `prime.worker.min.js` using the [UMD][] format. -### Bundle / Bower - -Each release is published in a separate repository as pre-built and minimized -basic forge bundles using the [UMD][] format. - -https://github.com/digitalbazaar/forge-dist - -This bundle can be used in many environments. In particular it can be installed -with [Bower][]: - - bower install forge - ### jsDelivr CDN To use it via [jsDelivr](https://www.jsdelivr.com/package/npm/node-forge) include this in your html: ```html - + ``` ### unpkg CDN @@ -146,7 +133,7 @@ To use it via [jsDelivr](https://www.jsdelivr.com/package/npm/node-forge) includ To use it via [unpkg](https://unpkg.com/#/) include this in your html: ```html - + ``` ### Development Requirements @@ -209,8 +196,6 @@ forge you need. Testing ------- -See the [testing README](./tests/README.md) for full details. - ### Prepare to run tests npm install @@ -221,10 +206,10 @@ Forge natively runs in a [Node.js][] environment: npm test -### Running automated tests with PhantomJS +### Running automated tests with Headless Chrome -Automated testing is done via [Karma][]. By default it will run the tests in a -headless manner with PhantomJS. +Automated testing is done via [Karma][]. By default it will run the tests with +Headless Chrome. npm run test-karma @@ -241,7 +226,7 @@ By default [webpack][] is used. [Browserify][] can also be used. You can also specify one or more browsers to use. - npm run test-karma -- --browsers Chrome,Firefox,Safari,PhantomJS + npm run test-karma -- --browsers Chrome,Firefox,Safari,ChromeHeadless The reporter option and `BUNDLER` environment variable can also be used. @@ -906,7 +891,7 @@ var signature = ED25519.sign({ // sign a message passed as a buffer var signature = ED25519.sign({ // also accepts a forge ByteBuffer or Uint8Array - message: new Buffer('test', 'utf8'), + message: Buffer.from('test', 'utf8'), privateKey: privateKey }); @@ -932,7 +917,7 @@ var verified = ED25519.verify({ // sign a message passed as a buffer var verified = ED25519.verify({ // also accepts a forge ByteBuffer or Uint8Array - message: new Buffer('test', 'utf8'), + message: Buffer.from('test', 'utf8'), // node.js Buffer, Uint8Array, forge ByteBuffer, or binary string signature: signature, // node.js Buffer, Uint8Array, forge ByteBuffer, or binary string @@ -961,14 +946,14 @@ __Examples__ var rsa = forge.pki.rsa; // generate an RSA key pair synchronously -// *NOT RECOMMENDED* -- can be significantly slower than async and will not -// use native APIs if available. +// *NOT RECOMMENDED*: Can be significantly slower than async and may block +// JavaScript execution. Will use native Node.js 10.12.0+ API if possible. var keypair = rsa.generateKeyPair({bits: 2048, e: 0x10001}); // generate an RSA key pair asynchronously (uses web workers if available) // use workers: -1 to run a fast core estimator to optimize # of workers -// *RECOMMENDED* - can be significantly faster than sync -- and will use -// native APIs if available. +// *RECOMMENDED*: Can be significantly faster than sync. Will use native +// Node.js 10.12.0+ or WebCrypto API if possible. rsa.generateKeyPair({bits: 2048, workers: 2}, function(err, keypair) { // keypair.privateKey, keypair.publicKey }); @@ -1378,6 +1363,10 @@ p7.addSigner({ p7.sign(); var pem = forge.pkcs7.messageToPem(p7); +// PKCS#7 Sign in detached mode. +// Includes the signature and certificate without the signed data. +p7.sign({detached: true}); + ``` @@ -1407,15 +1396,17 @@ var privateKeyInfo = pki.wrapRsaPrivateKey(rsaPrivateKey); // convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM var pem = pki.privateKeyInfoToPem(privateKeyInfo); -// encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo +// encrypts a PrivateKeyInfo using a custom password and +// outputs an EncryptedPrivateKeyInfo var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo( - privateKeyInfo, 'password', { + privateKeyInfo, 'myCustomPasswordHere', { algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des' }); -// decrypts an ASN.1 EncryptedPrivateKeyInfo +// decrypts an ASN.1 EncryptedPrivateKeyInfo that was encrypted +// with a custom password var privateKeyInfo = pki.decryptPrivateKeyInfo( - encryptedPrivateKeyInfo, 'password'); + encryptedPrivateKeyInfo, 'myCustomPasswordHere'); // converts an EncryptedPrivateKeyInfo to PEM var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo); @@ -1448,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(); @@ -1959,16 +1950,12 @@ bytes.getBytes(/* count */); // convert a forge buffer into a Node.js Buffer // make sure you specify the encoding as 'binary' var forgeBuffer = forge.util.createBuffer(); -var nodeBuffer = new Buffer(forgeBuffer.getBytes(), 'binary'); +var nodeBuffer = Buffer.from(forgeBuffer.getBytes(), 'binary'); // convert a Node.js Buffer into a forge buffer // make sure you specify the encoding as 'binary' -var nodeBuffer = new Buffer(); +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 ``` @@ -1984,19 +1971,6 @@ __Examples__ // TODO ``` - - -### Debugging - -Provides storage of debugging information normally inaccessible in -closures for viewing/investigation. - -__Examples__ - -```js -// TODO -``` - ### Flash Networking Support @@ -2017,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. @@ -2035,8 +2009,8 @@ When using this code please keep the following in mind: Library Background ------------------ -* http://digitalbazaar.com/2010/07/20/javascript-tls-1/ -* http://digitalbazaar.com/2010/07/20/javascript-tls-2/ +* https://digitalbazaar.com/2010/07/20/javascript-tls-1/ +* https://digitalbazaar.com/2010/07/20/javascript-tls-2/ Contact ------- @@ -2044,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 --------- @@ -2056,40 +2031,41 @@ Financial support is welcome and helps contribute to futher development: [#forgejs]: https://webchat.freenode.net/?channels=#forgejs [0.6.x]: https://github.com/digitalbazaar/forge/tree/0.6.x -[3DES]: http://en.wikipedia.org/wiki/Triple_DES -[AES]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard -[ASN.1]: http://en.wikipedia.org/wiki/ASN.1 -[Bower]: https://bower.io/ +[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 [Browserify]: http://browserify.org/ -[CBC]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation -[CFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation -[CTR]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +[CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +[CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation [CommonJS]: https://en.wikipedia.org/wiki/CommonJS -[DES]: http://en.wikipedia.org/wiki/Data_Encryption_Standard -[ECB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation -[Fortuna]: http://en.wikipedia.org/wiki/Fortuna_(PRNG) -[GCM]: http://en.wikipedia.org/wiki/GCM_mode -[HMAC]: http://en.wikipedia.org/wiki/HMAC -[JavaScript]: http://en.wikipedia.org/wiki/JavaScript +[DES]: https://en.wikipedia.org/wiki/Data_Encryption_Standard +[ECB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +[Fortuna]: https://en.wikipedia.org/wiki/Fortuna_(PRNG) +[GCM]: https://en.wikipedia.org/wiki/GCM_mode +[HMAC]: https://en.wikipedia.org/wiki/HMAC +[JavaScript]: https://en.wikipedia.org/wiki/JavaScript [Karma]: https://karma-runner.github.io/ -[MD5]: http://en.wikipedia.org/wiki/MD5 -[Node.js]: http://nodejs.org/ -[OFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation -[PKCS#10]: http://en.wikipedia.org/wiki/Certificate_signing_request -[PKCS#12]: http://en.wikipedia.org/wiki/PKCS_%E2%99%AF12 -[PKCS#5]: http://en.wikipedia.org/wiki/PKCS -[PKCS#7]: http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax +[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 +[PKCS#12]: https://en.wikipedia.org/wiki/PKCS_%E2%99%AF12 +[PKCS#5]: https://en.wikipedia.org/wiki/PKCS +[PKCS#7]: https://en.wikipedia.org/wiki/Cryptographic_Message_Syntax [PayPal]: https://www.paypal.com/ -[RC2]: http://en.wikipedia.org/wiki/RC2 -[SHA-1]: http://en.wikipedia.org/wiki/SHA-1 -[SHA-256]: http://en.wikipedia.org/wiki/SHA-256 -[SHA-384]: http://en.wikipedia.org/wiki/SHA-384 -[SHA-512]: http://en.wikipedia.org/wiki/SHA-512 +[RC2]: https://en.wikipedia.org/wiki/RC2 +[SHA-1]: https://en.wikipedia.org/wiki/SHA-1 +[SHA-256]: https://en.wikipedia.org/wiki/SHA-256 +[SHA-384]: https://en.wikipedia.org/wiki/SHA-384 +[SHA-512]: https://en.wikipedia.org/wiki/SHA-512 [Subresource Integrity]: https://www.w3.org/TR/SRI/ -[TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security +[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security [UMD]: https://github.com/umdjs/umd -[X.509]: http://en.wikipedia.org/wiki/X.509 +[X.509]: https://en.wikipedia.org/wiki/X.509 [freenode]: https://freenode.net/ [unpkg]: https://unpkg.com/ [webpack]: https://webpack.github.io/ -[TweetNaCl]: https://github.com/dchest/tweetnacl-js +[TweetNaCl.js]: https://github.com/dchest/tweetnacl-js 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 584de5392..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...'); @@ -103,7 +103,7 @@ try { console.log('Certificate verified.'); } return true; - }); + }); } catch(ex) { console.log('Certificate verification failure: ' + JSON.stringify(ex, null, 2)); 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 a2e249c9b..73d07e566 100644 --- a/examples/sign-p7.js +++ b/examples/sign-p7.js @@ -19,7 +19,7 @@ try { type: forge.pki.oids.messageDigest // value will be auto-populated at signing time }, { - type: forge.pki.oids.signingTime, + type: forge.pki.oids.signingTime // value will be auto-populated at signing time //value: new Date('2050-01-01T00:00:00Z') }] @@ -42,8 +42,8 @@ function createSigner(name) { console.log('Creating signer "' + name + '"...'); // generate a keypair - console.log('Generating 1024-bit key-pair...'); - var keys = forge.pki.rsa.generateKeyPair(1024); + console.log('Generating 2048-bit key-pair...'); + var keys = forge.pki.rsa.generateKeyPair(2048); console.log('Key-pair created:'); console.log(forge.pki.privateKeyToPem(keys.privateKey)); console.log(forge.pki.publicKeyToPem(keys.publicKey)); diff --git a/flash/policyserver.js b/flash/policyserver.js index bf1b04b6a..3f46bfc04 100644 --- a/flash/policyserver.js +++ b/flash/policyserver.js @@ -23,14 +23,14 @@ let policyFile = // Looks for a request string and returns the policy file. exports.policyServer = function(port) { let prefix = '[policy-server] '; - let server = net.createServer((socket) => { + let server = net.createServer(socket => { let remoteAddress = socket.remoteAddress + ':' + socket.remotePort; console.log(prefix + 'new client connection from %s', remoteAddress); // deal with strings socket.setEncoding('utf8'); - socket.on('data', (d) => { + socket.on('data', d => { if(d.indexOf('') === 0) { console.log(prefix + 'policy file request from: %s', remoteAddress); socket.write(policyFile); @@ -41,11 +41,11 @@ exports.policyServer = function(port) { socket.once('close', () => { console.log(prefix + 'connection from %s closed', remoteAddress); }); - socket.on('error', (err) => { + socket.on('error', err => { console.error( prefix + 'connection %s error: %s', remoteAddress, err.message); }); - }).on('error', (err) => { + }).on('error', err => { throw err; }); server.listen(port, () => { diff --git a/karma.conf.js b/karma.conf.js index 8422ea762..a61407dc1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -9,6 +9,15 @@ module.exports = function(config) { var preprocessors = []; // webworker bundle preprocessors (always use webpack) var workerPreprocessors = ['webpack', 'sourcemap']; + // files/patterns to load + var files = [ + 'tests/karma/index.js', + // for webworkers + { + pattern: 'lib/prime.worker.js', + watched: false, included: false, served: true, nocache: false + } + ]; if(bundler === 'browserify') { frameworks.push(bundler); @@ -16,6 +25,7 @@ module.exports = function(config) { } else if(bundler === 'webpack') { preprocessors.push(bundler); preprocessors.push('sourcemap'); + files.push('tests/karma/web-worker-rsa.js'); } else { throw Error('Unknown bundler'); } @@ -29,27 +39,23 @@ module.exports = function(config) { frameworks: frameworks, // list of files / patterns to load in the browser - files: [ - 'tests/unit/index.js', - // for webworkers - { - pattern: 'lib/prime.worker.js', - watched: false, included: false, served: true, nocache: false - } - ], + files: files, // list of files to exclude exclude: [ ], // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + // available preprocessors: + // https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - 'tests/unit/index.js': preprocessors, + 'tests/unit/**.js': preprocessors, + 'tests/karma/**.js': preprocessors, 'lib/prime.worker.js': workerPreprocessors }, webpack: { + mode: 'development', devtool: 'inline-source-map', node: { Buffer: false, @@ -77,16 +83,20 @@ module.exports = function(config) { colors: true, // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + // possible values: + // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, - // enable / disable watching file and executing tests whenever any file changes + // enable / disable watching file and executing tests whenever any file + // changes autoWatch: false, // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - //browsers: ['PhantomJS', 'Chrome', 'Firefox', 'Safari'], - browsers: ['PhantomJS'], + // available browser launchers: + // https://npmjs.org/browse/keyword/karma-launcher + //browsers: ['ChromeHeadless', 'Chrome', 'Firefox', 'Safari'], + browsers: ['ChromeHeadless'], customLaunchers: { IE9: { diff --git a/lib/aes.js b/lib/aes.js index d64062085..3c1ddb29b 100644 --- a/lib/aes.js +++ b/lib/aes.js @@ -648,7 +648,7 @@ function initialize() { * of Nb*(Nr + 1) words: the algorithm requires an initial set of Nb words, * and each of the Nr rounds requires Nb words of key data. The resulting * key schedule consists of a linear array of 4-byte words, denoted [wi ], - * with i in the range 0 ≤ i < Nb(Nr + 1). + * with i in the range 0 <= i < Nb(Nr + 1). * * KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk) * AES-128 (Nb=4, Nk=4, Nr=10) @@ -704,7 +704,7 @@ function _expandKey(key, decrypt) { w[i] = w[i - Nk] ^ temp; } - /* When we are updating a cipher block we always use the code path for + /* When we are updating a cipher block we always use the code path for encryption whether we are decrypting or not (to shorten code and simplify the generation of look up tables). However, because there are differences in the decryption algorithm, other than just swapping @@ -805,7 +805,7 @@ function _updateBlock(w, input, output, decrypt) { byte state[4,Nb] state = in AddRoundKey(state, w[0, Nb-1]) - for round = 1 step 1 to Nr–1 + for round = 1 step 1 to Nr-1 SubBytes(state) ShiftRows(state) MixColumns(state) @@ -1017,7 +1017,7 @@ function _updateBlock(w, input, output, decrypt) { InvSubBytes(state) AddRoundKey(state, w[0, Nb-1]) */ - // Note: rows are shifted inline + // Note: rows are shifted inline output[0] = (sub[a >>> 24] << 24) ^ (sub[b >>> 16 & 255] << 16) ^ diff --git a/lib/aesCipherSuites.js b/lib/aesCipherSuites.js index aeb91c1c3..fed60f369 100644 --- a/lib/aesCipherSuites.js +++ b/lib/aesCipherSuites.js @@ -16,7 +16,7 @@ var tls = module.exports = forge.tls; * Supported cipher suites. */ tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = { - id: [0x00,0x2f], + id: [0x00, 0x2f], name: 'TLS_RSA_WITH_AES_128_CBC_SHA', initSecurityParameters: function(sp) { sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes; @@ -32,7 +32,7 @@ tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = { initConnectionState: initConnectionState }; tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = { - id: [0x00,0x35], + id: [0x00, 0x35], name: 'TLS_RSA_WITH_AES_256_CBC_SHA', initSecurityParameters: function(sp) { sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes; @@ -199,10 +199,8 @@ function decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt) { * * @return true on success, false on failure. */ -var count = 0; function decrypt_aes_cbc_sha1(record, s) { var rval = false; - ++count; var iv; if(record.version.minor === tls.Versions.TLS_1_0.minor) { diff --git a/lib/asn1-validator.js b/lib/asn1-validator.js new file mode 100644 index 000000000..2be328501 --- /dev/null +++ b/lib/asn1-validator.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2019 Digital Bazaar, Inc. + */ + +var forge = require('./forge'); +require('./asn1'); +var asn1 = forge.asn1; + +exports.privateKeyValidator = { + // PrivateKeyInfo + name: 'PrivateKeyInfo', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + // Version (INTEGER) + name: 'PrivateKeyInfo.version', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.INTEGER, + constructed: false, + capture: 'privateKeyVersion' + }, { + // privateKeyAlgorithm + name: 'PrivateKeyInfo.privateKeyAlgorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'AlgorithmIdentifier.algorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OID, + constructed: false, + capture: 'privateKeyOid' + }] + }, { + // PrivateKey + name: 'PrivateKeyInfo', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OCTETSTRING, + constructed: false, + capture: 'privateKey' + }] +}; + +exports.publicKeyValidator = { + name: 'SubjectPublicKeyInfo', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + captureAsn1: 'subjectPublicKeyInfo', + value: [{ + name: 'SubjectPublicKeyInfo.AlgorithmIdentifier', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'AlgorithmIdentifier.algorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OID, + constructed: false, + capture: 'publicKeyOid' + }] + }, + // capture group for ed25519PublicKey + { + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.BITSTRING, + constructed: false, + composed: true, + captureBitStringValue: 'ed25519PublicKey' + } + // FIXME: this is capture group for rsaPublicKey, use it in this API or + // discard? + /* { + // subjectPublicKey + name: 'SubjectPublicKeyInfo.subjectPublicKey', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.BITSTRING, + constructed: false, + value: [{ + // RSAPublicKey + name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + optional: true, + captureAsn1: 'rsaPublicKey' + }] + } */ + ] +}; diff --git a/lib/asn1.js b/lib/asn1.js index af9fe524d..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,11 +631,12 @@ function _fromDer(bytes, remaining, depth, options) { } } else { value = bytes.getBytes(length); + remaining -= length; } } // add BIT STRING contents if available - var asn1Options = bitStringContents === undefined ? null : { + var asn1Options = bitStringContents === undefined ? null : { bitStringContents: bitStringContents }; @@ -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/cipherModes.js b/lib/cipherModes.js index b6831e4bd..339915cc1 100644 --- a/lib/cipherModes.js +++ b/lib/cipherModes.js @@ -119,7 +119,7 @@ modes.cbc.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } else { // save IV as "previous" block - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._prev = this._iv.slice(0); } }; @@ -215,7 +215,7 @@ modes.cfb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -359,7 +359,7 @@ modes.ofb.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -444,7 +444,7 @@ modes.ctr.prototype.start = function(options) { throw new Error('Invalid IV parameter.'); } // use IV as first input - this._iv = transformIV(options.iv); + this._iv = transformIV(options.iv, this.blockSize); this._inBlock = this._iv.slice(0); this._partialBytes = 0; }; @@ -652,7 +652,7 @@ modes.gcm.prototype.encrypt = function(input, output, finish) { this._partialOutput.putInt32(input.getInt32() ^ this._outBlock[i]); } - if(partialBytes === 0 || finish) { + if(partialBytes <= 0 || finish) { // handle overflow prior to hashing if(finish) { // get block overflow @@ -954,7 +954,7 @@ modes.gcm.prototype.generateSubHashTable = function(mid, bits) { /** Utility functions */ -function transformIV(iv) { +function transformIV(iv, blockSize) { if(typeof iv === 'string') { // convert iv string into byte buffer iv = forge.util.createBuffer(iv); @@ -968,9 +968,21 @@ function transformIV(iv) { iv.putByte(tmp[i]); } } + + if(iv.length() < blockSize) { + throw new Error( + 'Invalid IV length; got ' + iv.length() + + ' bytes and expected ' + blockSize + ' bytes.'); + } + if(!forge.util.isArray(iv)) { // convert iv byte buffer into 32-bit integer array - iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]; + var ints = []; + var blocks = blockSize / 4; + for(var i = 0; i < blocks; ++i) { + ints.push(iv.getInt32()); + } + iv = ints; } return iv; diff --git a/lib/debug.js b/lib/debug.js deleted file mode 100644 index 26756350e..000000000 --- a/lib/debug.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Debugging support for web applications. - * - * @author David I. Lehn - * - * Copyright 2008-2013 Digital Bazaar, Inc. - */ -var forge = require('./forge'); - -/* DEBUG API */ -module.exports = forge.debug = forge.debug || {}; - -// Private storage for debugging. -// Useful to expose data that is otherwise unviewable behind closures. -// NOTE: remember that this can hold references to data and cause leaks! -// format is "forge._debug.. = data" -// Example: -// (function() { -// var cat = 'forge.test.Test'; // debugging category -// var sState = {...}; // local state -// forge.debug.set(cat, 'sState', sState); -// })(); -forge.debug.storage = {}; - -/** - * Gets debug data. Omit name for all cat data Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to get (optional). - * @return object with requested debug data or undefined. - */ -forge.debug.get = function(cat, name) { - var rval; - if(typeof(cat) === 'undefined') { - rval = forge.debug.storage; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - rval = forge.debug.storage[cat]; - } else { - rval = forge.debug.storage[cat][name]; - } - } - return rval; -}; - -/** - * Sets debug data. - * - * @param cat name of debugging category. - * @param name name of data to set. - * @param data data to set. - */ -forge.debug.set = function(cat, name, data) { - if(!(cat in forge.debug.storage)) { - forge.debug.storage[cat] = {}; - } - forge.debug.storage[cat][name] = data; -}; - -/** - * Clears debug data. Omit name for all cat data. Omit name and cat for - * all data. - * - * @param cat name of debugging category. - * @param name name of data to clear or omit to clear entire category. - */ -forge.debug.clear = function(cat, name) { - if(typeof(cat) === 'undefined') { - forge.debug.storage = {}; - } else if(cat in forge.debug.storage) { - if(typeof(name) === 'undefined') { - delete forge.debug.storage[cat]; - } else { - delete forge.debug.storage[cat][name]; - } - } -}; diff --git a/lib/des.js b/lib/des.js index 6d2f5174f..ed8239aa9 100644 --- a/lib/des.js +++ b/lib/des.js @@ -7,7 +7,8 @@ * Paul Tero, July 2001 * http://www.tero.co.uk/des/ * - * Optimised for performance with large blocks by Michael Hayworth, November 2001 + * Optimised for performance with large blocks by + * Michael Hayworth, November 2001 * http://www.netdealing.com * * THIS SOFTWARE IS PROVIDED "AS IS" AND diff --git a/lib/ed25519.js b/lib/ed25519.js index 4054bbff8..f3e6faaaa 100644 --- a/lib/ed25519.js +++ b/lib/ed25519.js @@ -1,7 +1,7 @@ /** * JavaScript implementation of Ed25519. * - * Copyright (c) 2017-2018 Digital Bazaar, Inc. + * Copyright (c) 2017-2019 Digital Bazaar, Inc. * * This implementation is based on the most excellent TweetNaCl which is * in the public domain. Many thanks to its contributors: @@ -13,6 +13,9 @@ require('./jsbn'); require('./random'); require('./sha512'); require('./util'); +var asn1Validator = require('./asn1-validator'); +var publicKeyValidator = asn1Validator.publicKeyValidator; +var privateKeyValidator = asn1Validator.privateKeyValidator; if(typeof BigInteger === 'undefined') { var BigInteger = forge.jsbn.BigInteger; @@ -64,6 +67,75 @@ ed25519.generateKeyPair = function(options) { return {publicKey: pk, privateKey: sk}; }; +/** + * Converts a private key from a RFC8410 ASN.1 encoding. + * + * @param obj - The asn1 representation of a private key. + * + * @returns {Object} keyInfo - The key information. + * @returns {Buffer|Uint8Array} keyInfo.privateKeyBytes - 32 private key bytes. + */ +ed25519.privateKeyFromAsn1 = function(obj) { + var capture = {}; + var errors = []; + var valid = forge.asn1.validate(obj, privateKeyValidator, capture, errors); + if(!valid) { + var error = new Error('Invalid Key.'); + error.errors = errors; + throw error; + } + var oid = forge.asn1.derToOid(capture.privateKeyOid); + var ed25519Oid = forge.oids.EdDSA25519; + if(oid !== ed25519Oid) { + throw new Error('Invalid OID "' + oid + '"; OID must be "' + + ed25519Oid + '".'); + } + var privateKey = capture.privateKey; + // manually extract the private key bytes from nested octet string, see FIXME: + // https://github.com/digitalbazaar/forge/blob/master/lib/asn1.js#L542 + var privateKeyBytes = messageToNativeBuffer({ + message: forge.asn1.fromDer(privateKey).value, + encoding: 'binary' + }); + // TODO: RFC8410 specifies a format for encoding the public key bytes along + // with the private key bytes. `publicKeyBytes` can be returned in the + // future. https://tools.ietf.org/html/rfc8410#section-10.3 + return {privateKeyBytes: privateKeyBytes}; +}; + +/** + * Converts a public key from a RFC8410 ASN.1 encoding. + * + * @param obj - The asn1 representation of a public key. + * + * @return {Buffer|Uint8Array} - 32 public key bytes. + */ +ed25519.publicKeyFromAsn1 = function(obj) { + // get SubjectPublicKeyInfo + var capture = {}; + var errors = []; + var valid = forge.asn1.validate(obj, publicKeyValidator, capture, errors); + if(!valid) { + var error = new Error('Invalid Key.'); + error.errors = errors; + throw error; + } + var oid = forge.asn1.derToOid(capture.publicKeyOid); + var ed25519Oid = forge.oids.EdDSA25519; + if(oid !== ed25519Oid) { + throw new Error('Invalid OID "' + oid + '"; OID must be "' + + ed25519Oid + '".'); + } + var publicKeyBytes = capture.ed25519PublicKey; + if(publicKeyBytes.length !== ed25519.constants.PUBLIC_KEY_BYTE_LENGTH) { + throw new Error('Key length is invalid.'); + } + return messageToNativeBuffer({ + message: publicKeyBytes, + encoding: 'binary' + }); +}; + ed25519.publicKeyFromPrivateKey = function(options) { options = options || {}; var privateKey = messageToNativeBuffer({ @@ -89,9 +161,13 @@ ed25519.sign = function(options) { message: options.privateKey, encoding: 'binary' }); - if(privateKey.length !== ed25519.constants.PRIVATE_KEY_BYTE_LENGTH) { + if(privateKey.length === ed25519.constants.SEED_BYTE_LENGTH) { + var keyPair = ed25519.generateKeyPair({seed: privateKey}); + privateKey = keyPair.privateKey; + } else if(privateKey.length !== ed25519.constants.PRIVATE_KEY_BYTE_LENGTH) { throw new TypeError( '"options.privateKey" must have a byte length of ' + + ed25519.constants.SEED_BYTE_LENGTH + ' or ' + ed25519.constants.PRIVATE_KEY_BYTE_LENGTH); } @@ -147,7 +223,7 @@ ed25519.verify = function(options) { function messageToNativeBuffer(options) { var message = options.message; - if(message instanceof Uint8Array) { + if(message instanceof Uint8Array || message instanceof NativeBuffer) { return message; } @@ -168,7 +244,7 @@ function messageToNativeBuffer(options) { if(typeof message === 'string') { if(typeof Buffer !== 'undefined') { - return new Buffer(message, encoding); + return Buffer.from(message, encoding); } message = new ByteBuffer(message, encoding); } else if(!(message instanceof ByteBuffer)) { @@ -217,7 +293,7 @@ function sha512(msg, msgLen) { md.update(buffer.getBytes(msgLen), 'binary'); var hash = md.digest().getBytes(); if(typeof Buffer !== 'undefined') { - return new Buffer(hash, 'binary'); + return Buffer.from(hash, 'binary'); } var out = new NativeBuffer(ed25519.constants.HASH_BYTE_LENGTH); for(var i = 0; i < 64; ++i) { 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/kem.js b/lib/kem.js index f2d8d7805..1967016df 100644 --- a/lib/kem.js +++ b/lib/kem.js @@ -53,14 +53,14 @@ forge.kem.rsa.create = function(kdf, options) { * key: the secret key to use for encrypting a message. */ kem.encrypt = function(publicKey, keyLength) { - // generate a random r where 1 > r > n + // generate a random r where 1 < r < n var byteLength = Math.ceil(publicKey.n.bitLength() / 8); var r; do { r = new BigInteger( forge.util.bytesToHex(prng.getBytesSync(byteLength)), 16).mod(publicKey.n); - } while(r.equals(BigInteger.ZERO)); + } while(r.compareTo(BigInteger.ONE) <= 0); // prepend r with zeros r = forge.util.hexToBytes(r.toString(16)); 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 c908c7c86..d1504eb16 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -34,15 +34,23 @@ _IN('1.2.840.113549.1.1.10', 'RSASSA-PSS'); _IN('1.2.840.113549.1.1.11', 'sha256WithRSAEncryption'); _IN('1.2.840.113549.1.1.12', 'sha384WithRSAEncryption'); _IN('1.2.840.113549.1.1.13', 'sha512WithRSAEncryption'); +// Edwards-curve Digital Signature Algorithm (EdDSA) Ed25519 +_IN('1.3.101.112', 'EdDSA25519'); _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 @@ -102,15 +110,25 @@ _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'); // X.509 extension OIDs _IN('2.16.840.1.113730.1.1', 'nsCertType'); +_IN('2.16.840.1.113730.1.13', 'nsComment'); // deprecated in theory; still widely used _I_('2.5.29.1', 'authorityKeyIdentifier'); // deprecated, use .35 _I_('2.5.29.2', 'keyAttributes'); // obsolete use .37 or .15 _I_('2.5.29.3', 'certificatePolicies'); // deprecated, use .32 diff --git a/lib/pbkdf2.js b/lib/pbkdf2.js index ece98850b..714560e38 100644 --- a/lib/pbkdf2.js +++ b/lib/pbkdf2.js @@ -51,8 +51,8 @@ module.exports = forge.pbkdf2 = pkcs5.pbkdf2 = function( // default prf to SHA-1 md = 'sha1'; } - p = new Buffer(p, 'binary'); - s = new Buffer(s, 'binary'); + p = Buffer.from(p, 'binary'); + s = Buffer.from(s, 'binary'); if(!callback) { if(crypto.pbkdf2Sync.length === 4) { return crypto.pbkdf2Sync(p, s, c, dkLen).toString('binary'); 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/pkcs1.js b/lib/pkcs1.js index 5fae4a07e..a3af9242e 100644 --- a/lib/pkcs1.js +++ b/lib/pkcs1.js @@ -119,7 +119,7 @@ pkcs1.encode_rsa_oaep = function(key, message, options) { var PS = ''; var PS_length = maxLength - message.length; - for (var i = 0; i < PS_length; i++) { + for(var i = 0; i < PS_length; i++) { PS += '\x00'; } diff --git a/lib/pkcs12.js b/lib/pkcs12.js index 19ff2a9ed..cd06c494a 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -68,7 +68,7 @@ * PKCS12Attribute ::= SEQUENCE { * attrId ATTRIBUTE.&id ({PKCS12AttrSet}), * attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId}) - * } -- This type is compatible with the X.500 type ’Attribute’ + * } -- This type is compatible with the X.500 type 'Attribute' * * PKCS12AttrSet ATTRIBUTE ::= { * friendlyName | -- from PKCS #9 diff --git a/lib/pkcs7.js b/lib/pkcs7.js index 66f3277f2..3a5d845c5 100644 --- a/lib/pkcs7.js +++ b/lib/pkcs7.js @@ -328,8 +328,11 @@ p7.createSignedData = function() { /** * Signs the content. + * @param options Options to apply when signing: + * [detached] boolean. If signing should be done in detached mode. Defaults to false. */ - sign: function() { + sign: function(options) { + options = options || {}; // auto-generate content info if(typeof msg.content !== 'object' || msg.contentInfo === null) { // use Data ContentInfo @@ -349,12 +352,16 @@ p7.createSignedData = function() { content = forge.util.encodeUtf8(msg.content); } - msg.contentInfo.value.push( - // [0] EXPLICIT content - asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, - content) - ])); + if (options.detached) { + msg.detachedContent = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, content); + } else { + msg.contentInfo.value.push( + // [0] EXPLICIT content + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, + content) + ])); + } } } @@ -437,10 +444,22 @@ p7.createSignedData = function() { } function addSignerInfos(mds) { - // Note: ContentInfo is a SEQUENCE with 2 values, second value is - // the content field and is optional for a ContentInfo but required here - // since signers are present - if(msg.contentInfo.value.length < 2) { + var content; + + if (msg.detachedContent) { + // Signature has been made in detached mode. + content = msg.detachedContent; + } else { + // Note: ContentInfo is a SEQUENCE with 2 values, second value is + // the content field and is optional for a ContentInfo but required here + // since signers are present + // get ContentInfo content + content = msg.contentInfo.value[1]; + // skip [0] EXPLICIT content wrapper + content = content.value[0]; + } + + if(!content) { throw new Error( 'Could not sign PKCS#7 message; there is no content to sign.'); } @@ -448,11 +467,6 @@ p7.createSignedData = function() { // get ContentInfo content type var contentType = asn1.derToOid(msg.contentInfo.value[0].value); - // get ContentInfo content - var content = msg.contentInfo.value[1]; - // skip [0] EXPLICIT content wrapper - content = content.value[0]; - // serialize content var bytes = asn1.toDer(content); @@ -823,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 } }; @@ -1110,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 b2eb1d430..d3bd22e05 100644 --- a/lib/prng.js +++ b/lib/prng.js @@ -267,13 +267,12 @@ prng.create = function(plugin) { function defaultSeedFile(needed) { // use window.crypto.getRandomValues strong source of entropy if available var getRandomValues = null; - if(typeof window !== 'undefined') { - var _crypto = window.crypto || window.msCrypto; - if(_crypto && _crypto.getRandomValues) { - getRandomValues = function(arr) { - return _crypto.getRandomValues(arr); - }; - } + var globalScope = forge.util.globalScope; + var _crypto = globalScope.crypto || globalScope.msCrypto; + if(_crypto && _crypto.getRandomValues) { + getRandomValues = function(arr) { + return _crypto.getRandomValues(arr); + }; } var b = forge.util.createBuffer(); @@ -318,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/random.js b/lib/random.js index d4f1928ca..d4e4bea9e 100644 --- a/lib/random.js +++ b/lib/random.js @@ -115,14 +115,14 @@ var _ctx = spawnPrng(); // add other sources of entropy only if window.crypto.getRandomValues is not // available -- otherwise this source will be automatically used by the prng var getRandomValues = null; -if(typeof window !== 'undefined') { - var _crypto = window.crypto || window.msCrypto; - if(_crypto && _crypto.getRandomValues) { - getRandomValues = function(arr) { - return _crypto.getRandomValues(arr); - }; - } +var globalScope = forge.util.globalScope; +var _crypto = globalScope.crypto || globalScope.msCrypto; +if(_crypto && _crypto.getRandomValues) { + getRandomValues = function(arr) { + return _crypto.getRandomValues(arr); + }; } + if(forge.options.usePureJavaScript || (!forge.util.isNodejs && !getRandomValues)) { // if this is a web worker, do not use weak entropy, instead register to diff --git a/lib/rsa.js b/lib/rsa.js index fd4d988d5..f3b320212 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -74,9 +74,14 @@ if(typeof BigInteger === 'undefined') { var BigInteger = forge.jsbn.BigInteger; } +var _crypto = forge.util.isNodejs ? require('crypto') : null; + // shortcut for asn.1 API var asn1 = forge.asn1; +// shortcut for util API +var util = forge.util; + /* * RSA encryption and decryption, see RFC 2313. */ @@ -259,6 +264,40 @@ 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, + constructed: false + }] + }, { + // digest + name: 'DigestInfo.digest', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OCTETSTRING, + constructed: false, + capture: 'digest' + }] +}; + /** * Wrap digest in DigestInfo object. * @@ -682,7 +721,7 @@ pki.rsa.stepKeyPairGenerationState = function(state, n) { var THIRTY = new BigInteger(null); THIRTY.fromInt(30); var deltaIdx = 0; - var op_or = function(x, y) { return x|y; }; + var op_or = function(x, y) {return x | y;}; // keep stepping until time limit is reached or done var t1 = +new Date(); @@ -731,7 +770,7 @@ pki.rsa.stepKeyPairGenerationState = function(state, n) { // ensure number is coprime with e state.pqState = (state.num.subtract(BigInteger.ONE).gcd(state.e) - .compareTo(BigInteger.ONE) === 0) ? 3 : 0; + .compareTo(BigInteger.ONE) === 0) ? 3 : 0; } else if(state.pqState === 3) { // store p or q state.pqState = 0; @@ -820,7 +859,7 @@ pki.rsa.stepKeyPairGenerationState = function(state, n) { * @param [bits] the size for the private key in bits, defaults to 2048. * @param [e] the public exponent to use, defaults to 65537. * @param [options] options for key-pair generation, if given then 'bits' - * and 'e' must *not* be given: + * and 'e' must *not* be given: * bits the size for the private key in bits, (default: 2048). * e the public exponent to use, (default: 65537 (0x10001)). * workerScript the worker script URL. @@ -830,7 +869,7 @@ pki.rsa.stepKeyPairGenerationState = function(state, n) { * numbers for each web worker to check per work assignment, * (default: 100). * prng a custom crypto-secure pseudo-random number generator to use, - * that must define "getBytesSync". + * that must define "getBytesSync". Disables use of native APIs. * algorithm the algorithm to use (default: 'PRIMEINC'). * @param [callback(err, keypair)] called once the operation completes. * @@ -883,63 +922,109 @@ pki.rsa.generateKeyPair = function(bits, e, options, callback) { e = options.e || 0x10001; } - // if native code is permitted and a callback is given, use native - // key generation code if available and if parameters are acceptable - if(!forge.options.usePureJavaScript && callback && + // use native code if permitted, available, and parameters are acceptable + if(!forge.options.usePureJavaScript && !options.prng && bits >= 256 && bits <= 16384 && (e === 0x10001 || e === 3)) { - if(_detectSubtleCrypto('generateKey') && _detectSubtleCrypto('exportKey')) { - // use standard native generateKey - return window.crypto.subtle.generateKey({ - name: 'RSASSA-PKCS1-v1_5', - modulusLength: bits, - publicExponent: _intToUint8Array(e), - hash: {name: 'SHA-256'} - }, true /* key can be exported*/, ['sign', 'verify']) - .then(function(pair) { - return window.crypto.subtle.exportKey('pkcs8', pair.privateKey); - // avoiding catch(function(err) {...}) to support IE <= 8 - }).then(undefined, function(err) { - callback(err); - }).then(function(pkcs8) { - if(pkcs8) { - var privateKey = pki.privateKeyFromAsn1( - asn1.fromDer(forge.util.createBuffer(pkcs8))); - callback(null, { - privateKey: privateKey, - publicKey: pki.setRsaPublicKey(privateKey.n, privateKey.e) - }); - } - }); - } - if(_detectSubtleMsCrypto('generateKey') && - _detectSubtleMsCrypto('exportKey')) { - var genOp = window.msCrypto.subtle.generateKey({ - name: 'RSASSA-PKCS1-v1_5', - modulusLength: bits, - publicExponent: _intToUint8Array(e), - hash: {name: 'SHA-256'} - }, true /* key can be exported*/, ['sign', 'verify']); - genOp.oncomplete = function(e) { - var pair = e.target.result; - var exportOp = window.msCrypto.subtle.exportKey( - 'pkcs8', pair.privateKey); - exportOp.oncomplete = function(e) { - var pkcs8 = e.target.result; - var privateKey = pki.privateKeyFromAsn1( - asn1.fromDer(forge.util.createBuffer(pkcs8))); + if(callback) { + // try native async + if(_detectNodeCrypto('generateKeyPair')) { + return _crypto.generateKeyPair('rsa', { + modulusLength: bits, + publicExponent: e, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }, function(err, pub, priv) { + if(err) { + return callback(err); + } callback(null, { - privateKey: privateKey, - publicKey: pki.setRsaPublicKey(privateKey.n, privateKey.e) + privateKey: pki.privateKeyFromPem(priv), + publicKey: pki.publicKeyFromPem(pub) }); + }); + } + if(_detectSubtleCrypto('generateKey') && + _detectSubtleCrypto('exportKey')) { + // use standard native generateKey + return util.globalScope.crypto.subtle.generateKey({ + name: 'RSASSA-PKCS1-v1_5', + modulusLength: bits, + publicExponent: _intToUint8Array(e), + hash: {name: 'SHA-256'} + }, true /* key can be exported*/, ['sign', 'verify']) + .then(function(pair) { + return util.globalScope.crypto.subtle.exportKey( + 'pkcs8', pair.privateKey); + // avoiding catch(function(err) {...}) to support IE <= 8 + }).then(undefined, function(err) { + callback(err); + }).then(function(pkcs8) { + if(pkcs8) { + var privateKey = pki.privateKeyFromAsn1( + asn1.fromDer(forge.util.createBuffer(pkcs8))); + callback(null, { + privateKey: privateKey, + publicKey: pki.setRsaPublicKey(privateKey.n, privateKey.e) + }); + } + }); + } + if(_detectSubtleMsCrypto('generateKey') && + _detectSubtleMsCrypto('exportKey')) { + var genOp = util.globalScope.msCrypto.subtle.generateKey({ + name: 'RSASSA-PKCS1-v1_5', + modulusLength: bits, + publicExponent: _intToUint8Array(e), + hash: {name: 'SHA-256'} + }, true /* key can be exported*/, ['sign', 'verify']); + genOp.oncomplete = function(e) { + var pair = e.target.result; + var exportOp = util.globalScope.msCrypto.subtle.exportKey( + 'pkcs8', pair.privateKey); + exportOp.oncomplete = function(e) { + var pkcs8 = e.target.result; + var privateKey = pki.privateKeyFromAsn1( + asn1.fromDer(forge.util.createBuffer(pkcs8))); + callback(null, { + privateKey: privateKey, + publicKey: pki.setRsaPublicKey(privateKey.n, privateKey.e) + }); + }; + exportOp.onerror = function(err) { + callback(err); + }; }; - exportOp.onerror = function(err) { + genOp.onerror = function(err) { callback(err); }; - }; - genOp.onerror = function(err) { - callback(err); - }; - return; + return; + } + } else { + // try native sync + if(_detectNodeCrypto('generateKeyPairSync')) { + var keypair = _crypto.generateKeyPairSync('rsa', { + modulusLength: bits, + publicExponent: e, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + return { + privateKey: pki.privateKeyFromPem(keypair.privateKey), + publicKey: pki.publicKeyFromPem(keypair.publicKey) + }; + } } } @@ -1003,7 +1088,7 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { } }; } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) { - scheme = { encode: function(e) { return e; } }; + scheme = {encode: function(e) {return e;}}; } else if(typeof scheme === 'string') { throw new Error('Unsupported encryption scheme: "' + scheme + '".'); } @@ -1041,40 +1126,84 @@ 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) { - if(typeof scheme === 'string') { - scheme = scheme.toUpperCase(); - } else if(scheme === undefined) { - scheme = 'RSASSA-PKCS1-V1_5'; - } - - if(scheme === 'RSASSA-PKCS1-V1_5') { - scheme = { - verify: function(digest, d) { - // remove padding - d = _decodePkcs1_v1_5(d, key, true); - // d is ASN.1 BER-encoded DigestInfo - var obj = asn1.fromDer(d); - // compare the given digest to the decrypted one - return digest === obj.value[1].value; - } - }; - } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { - scheme = { - verify: function(digest, d) { - // remove padding - d = _decodePkcs1_v1_5(d, key, true); - return digest === d; - } - }; - } - - // do rsa decryption w/o any decoding, then verify -- which does decoding - var d = pki.rsa.decrypt(signature, key, true, false); - return scheme.verify(digest, d, key.n.bitLength()); + 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 = { + verify: function(digest, d) { + // remove padding + d = _decodePkcs1_v1_5(d, key, true); + // d is ASN.1 BER-encoded DigestInfo + 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; + } + + // compare the given digest to the decrypted one + return digest === capture.digest; + } + }; + } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { + scheme = { + verify: function(digest, d) { + // remove padding + d = _decodePkcs1_v1_5(d, key, true); + return digest === d; + } + }; + } + + // do rsa decryption w/o any decoding, then verify -- which does decoding + var d = pki.rsa.decrypt(signature, key, true, false); + return scheme.verify(digest, d, key.n.bitLength()); }; return key; @@ -1132,7 +1261,7 @@ pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function( var d = pki.rsa.decrypt(data, key, false, false); if(scheme === 'RSAES-PKCS1-V1_5') { - scheme = { decode: _decodePkcs1_v1_5 }; + scheme = {decode: _decodePkcs1_v1_5}; } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') { scheme = { decode: function(d, key) { @@ -1140,7 +1269,7 @@ pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function( } }; } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) { - scheme = { decode: function(d) { return d; } }; + scheme = {decode: function(d) {return d;}}; } else { throw new Error('Unsupported encryption scheme: "' + scheme + '".'); } @@ -1182,10 +1311,10 @@ pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function( } if(scheme === undefined || scheme === 'RSASSA-PKCS1-V1_5') { - scheme = { encode: emsaPkcs1v15encode }; + scheme = {encode: emsaPkcs1v15encode}; bt = 0x01; } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { - scheme = { encode: function() { return md; } }; + scheme = {encode: function() {return md;}}; bt = 0x01; } @@ -1220,7 +1349,7 @@ pki.wrapRsaPrivateKey = function(rsaKey) { // PrivateKey asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, asn1.toDer(rsaKey).getBytes()) - ]); + ]); }; /** @@ -1727,6 +1856,17 @@ function _getMillerRabinTests(bits) { return 2; } +/** + * Performs feature detection on the Node crypto interface. + * + * @param fn the feature (function) to detect. + * + * @return true if detected, false if not. + */ +function _detectNodeCrypto(fn) { + return forge.util.isNodejs && typeof _crypto[fn] === 'function'; +} + /** * Performs feature detection on the SubtleCrypto interface. * @@ -1735,10 +1875,10 @@ function _getMillerRabinTests(bits) { * @return true if detected, false if not. */ function _detectSubtleCrypto(fn) { - return (typeof window !== 'undefined' && - typeof window.crypto === 'object' && - typeof window.crypto.subtle === 'object' && - typeof window.crypto.subtle[fn] === 'function'); + return (typeof util.globalScope !== 'undefined' && + typeof util.globalScope.crypto === 'object' && + typeof util.globalScope.crypto.subtle === 'object' && + typeof util.globalScope.crypto.subtle[fn] === 'function'); } /** @@ -1751,10 +1891,10 @@ function _detectSubtleCrypto(fn) { * @return true if detected, false if not. */ function _detectSubtleMsCrypto(fn) { - return (typeof window !== 'undefined' && - typeof window.msCrypto === 'object' && - typeof window.msCrypto.subtle === 'object' && - typeof window.msCrypto.subtle[fn] === 'function'); + return (typeof util.globalScope !== 'undefined' && + typeof util.globalScope.msCrypto === 'object' && + typeof util.globalScope.msCrypto.subtle === 'object' && + typeof util.globalScope.msCrypto.subtle[fn] === 'function'); } function _intToUint8Array(x) { diff --git a/lib/sha1.js b/lib/sha1.js index 28776ee2f..5f84eb667 100644 --- a/lib/sha1.js +++ b/lib/sha1.js @@ -113,12 +113,12 @@ sha1.create = function() { return md; }; - /** - * Produces the digest. - * - * @return a byte buffer containing the digest value. - */ - md.digest = function() { + /** + * Produces the digest. + * + * @return a byte buffer containing the digest value. + */ + md.digest = function() { /* Note: Here we copy the remaining bytes in the input buffer and add the appropriate SHA-1 padding. Then we do the final update on a copy of the state so that if the user wants to get diff --git a/lib/sha512.js b/lib/sha512.js index 763a9c573..e09b442af 100644 --- a/lib/sha512.js +++ b/lib/sha512.js @@ -81,7 +81,7 @@ sha512.create = function(algorithm) { // determine digest length by algorithm name (default) var digestLength = 64; - switch (algorithm) { + switch(algorithm) { case 'SHA-384': digestLength = 48; break; diff --git a/lib/tls.js b/lib/tls.js index e953fb89b..fadfd646f 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -3528,40 +3528,48 @@ var _alertDescToCertError = function(desc) { */ tls.verifyCertificateChain = function(c, chain) { try { - // verify chain - forge.pki.verifyCertificateChain(c.caStore, chain, - function verify(vfd, depth, chain) { - // convert pki.certificateError to tls alert description - var desc = _certErrorToAlertDesc(vfd); - - // call application callback - var ret = c.verify(c, vfd, depth, chain); - if(ret !== true) { - if(typeof ret === 'object' && !forge.util.isArray(ret)) { - // throw custom error - var error = new Error('The application rejected the certificate.'); - error.send = true; - error.alert = { - level: tls.Alert.Level.fatal, - description: tls.Alert.Description.bad_certificate - }; - if(ret.message) { - error.message = ret.message; - } - if(ret.alert) { - error.alert.description = ret.alert; - } - throw error; - } + // Make a copy of c.verifyOptions so that we can modify options.verify + // without modifying c.verifyOptions. + var options = {}; + for (var key in c.verifyOptions) { + options[key] = c.verifyOptions[key]; + } + + options.verify = function(vfd, depth, chain) { + // convert pki.certificateError to tls alert description + var desc = _certErrorToAlertDesc(vfd); - // convert tls alert description to pki.certificateError - if(ret !== vfd) { - ret = _alertDescToCertError(ret); + // call application callback + var ret = c.verify(c, vfd, depth, chain); + if(ret !== true) { + if(typeof ret === 'object' && !forge.util.isArray(ret)) { + // throw custom error + var error = new Error('The application rejected the certificate.'); + error.send = true; + error.alert = { + level: tls.Alert.Level.fatal, + description: tls.Alert.Description.bad_certificate + }; + if(ret.message) { + error.message = ret.message; } + if(ret.alert) { + error.alert.description = ret.alert; + } + throw error; } - return ret; - }); + // convert tls alert description to pki.certificateError + if(ret !== vfd) { + ret = _alertDescToCertError(ret); + } + } + + return ret; + }; + + // verify chain + forge.pki.verifyCertificateChain(c.caStore, chain, options); } catch(ex) { // build tls error if not already customized var err = ex; @@ -3718,6 +3726,7 @@ tls.createConnection = function(options) { virtualHost: options.virtualHost || null, verifyClient: options.verifyClient || false, verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;}, + verifyOptions: options.verifyOptions || {}, getCertificate: options.getCertificate || null, getPrivateKey: options.getPrivateKey || null, getSignature: options.getSignature || null, @@ -4247,6 +4256,10 @@ forge.tls.createSessionCache = tls.createSessionCache; * verifyClient: true to require a client certificate in server mode, * 'optional' to request one, false not to (default: false). * verify: a handler used to custom verify certificates in the chain. + * verifyOptions: an object with options for the certificate chain validation. + * See documentation of pki.verifyCertificateChain for possible options. + * verifyOptions.verify is ignored. If you wish to specify a verify handler + * use the verify key. * getCertificate: an optional callback used to get a certificate or * a chain of certificates (as an array). * getPrivateKey: an optional callback used to get a private key. diff --git a/lib/util.js b/lib/util.js index eff883fa7..aaede5ad2 100644 --- a/lib/util.js +++ b/lib/util.js @@ -13,8 +13,10 @@ var util = module.exports = forge.util = forge.util || {}; // define setImmediate and nextTick (function() { - // use native nextTick - if(typeof process !== 'undefined' && process.nextTick) { + // use native nextTick (unless we're in webpack) + // webpack (or better node-libs-browser polyfill) sets process.browser. + // this way we can detect webpack properly + if(typeof process !== 'undefined' && process.nextTick && !process.browser) { util.nextTick = process.nextTick; if(typeof setImmediate === 'function') { util.setImmediate = setImmediate; @@ -108,6 +110,19 @@ var util = module.exports = forge.util = forge.util || {}; util.isNodejs = typeof process !== 'undefined' && process.versions && process.versions.node; + +// 'self' will also work in Web Workers (instance of WorkerGlobalScope) while +// it will point to `window` in the main thread. +// To remain compatible with older browsers, we fall back to 'window' if 'self' +// is not available. +util.globalScope = (function() { + if(util.isNodejs) { + return global; + } + + return typeof self === 'undefined' ? window : self; +})(); + // define isArray util.isArray = Array.isArray || function(x) { return Object.prototype.toString.call(x) === '[object Array]'; @@ -264,7 +279,7 @@ util.ByteStringBuffer.prototype.fillWithByte = function(b, n) { /** * Puts bytes in this buffer. * - * @param bytes the bytes (as a UTF-8 encoded string) to put. + * @param bytes the bytes (as a binary encoded string) to put. * * @return this buffer. */ @@ -552,11 +567,13 @@ util.ByteStringBuffer.prototype.getSignedInt = function(n) { }; /** - * Reads bytes out into a UTF-8 string and clears them from the buffer. + * Reads bytes out as a binary encoded string and clears them from the + * buffer. Note that the resulting string is binary encoded (in node.js this + * encoding is referred to as `binary`, it is *not* `utf8`). * * @param count the number of bytes to read, undefined or null for all. * - * @return a UTF-8 string of bytes. + * @return a binary encoded string of bytes. */ util.ByteStringBuffer.prototype.getBytes = function(count) { var rval; @@ -576,12 +593,12 @@ util.ByteStringBuffer.prototype.getBytes = function(count) { }; /** - * Gets a UTF-8 encoded string of the bytes from this buffer without modifying - * the read pointer. + * Gets a binary encoded string of the bytes from this buffer without + * modifying the read pointer. * * @param count the number of bytes to get, omit to get all. * - * @return a string full of UTF-8 encoded characters. + * @return a string full of binary encoded characters. */ util.ByteStringBuffer.prototype.bytes = function(count) { return (typeof(count) === 'undefined' ? @@ -1213,11 +1230,12 @@ util.DataBuffer.prototype.getSignedInt = function(n) { }; /** - * Reads bytes out into a UTF-8 string and clears them from the buffer. + * Reads bytes out as a binary encoded string and clears them from the + * buffer. * * @param count the number of bytes to read, undefined or null for all. * - * @return a UTF-8 string of bytes. + * @return a binary encoded string of bytes. */ util.DataBuffer.prototype.getBytes = function(count) { // TODO: deprecate this method, it is poorly named and @@ -1240,12 +1258,12 @@ util.DataBuffer.prototype.getBytes = function(count) { }; /** - * Gets a UTF-8 encoded string of the bytes from this buffer without modifying - * the read pointer. + * Gets a binary encoded string of the bytes from this buffer without + * modifying the read pointer. * * @param count the number of bytes to get, omit to get all. * - * @return a string full of UTF-8 encoded characters. + * @return a string full of binary encoded characters. */ util.DataBuffer.prototype.bytes = function(count) { // TODO: deprecate this method, it is poorly named, add "getString()" @@ -1392,12 +1410,13 @@ util.DataBuffer.prototype.toString = function(encoding) { /** End Buffer w/UInt8Array backing */ /** - * Creates a buffer that stores bytes. A value may be given to put into the - * buffer that is either a string of bytes or a UTF-16 string that will - * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding). + * Creates a buffer that stores bytes. A value may be given to populate the + * buffer with data. This value can either be string of encoded bytes or a + * regular string of characters. When passing a string of binary encoded + * bytes, the encoding `raw` should be given. This is also the default. When + * passing a string of characters, the encoding `utf8` should be given. * - * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode - * as UTF-8. + * @param [input] a string with encoded bytes to store in the buffer. * @param [encoding] (default: 'raw', other: 'utf8'). */ util.createBuffer = function(input, encoding) { @@ -1626,24 +1645,27 @@ util.decode64 = function(input) { }; /** - * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript - * string). Non-ASCII characters will be encoded as multiple bytes according - * to UTF-8. + * Encodes the given string of characters (a standard JavaScript + * string) as a binary encoded string where the bytes represent + * a UTF-8 encoded string of characters. Non-ASCII characters will be + * encoded as multiple bytes according to UTF-8. * - * @param str the string to encode. + * @param str a standard string of characters to encode. * - * @return the UTF-8 encoded string. + * @return the binary encoded string. */ util.encodeUtf8 = function(str) { return unescape(encodeURIComponent(str)); }; /** - * Decodes a UTF-8 encoded string into a UTF-16 string. + * Decodes a binary encoded string that contains bytes that + * represent a UTF-8 encoded string of characters -- into a + * string of characters (a standard JavaScript string). * - * @param str the string to decode. + * @param str the binary encoded string to decode. * - * @return the UTF-16 encoded string (standard JavaScript string). + * @return the resulting standard string of characters. */ util.decodeUtf8 = function(str) { return decodeURIComponent(escape(str)); @@ -2236,354 +2258,6 @@ util.clearItems = function(api, id, location) { _callStorageFunction(_clearItems, arguments, location); }; -/** - * Parses the scheme, host, and port from an http(s) url. - * - * @param str the url string. - * - * @return the parsed url object or null if the url is invalid. - */ -util.parseUrl = function(str) { - // FIXME: this regex looks a bit broken - var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g; - regex.lastIndex = 0; - var m = regex.exec(str); - var url = (m === null) ? null : { - full: str, - scheme: m[1], - host: m[2], - port: m[3], - path: m[4] - }; - if(url) { - url.fullHost = url.host; - if(url.port) { - if(url.port !== 80 && url.scheme === 'http') { - url.fullHost += ':' + url.port; - } else if(url.port !== 443 && url.scheme === 'https') { - url.fullHost += ':' + url.port; - } - } else if(url.scheme === 'http') { - url.port = 80; - } else if(url.scheme === 'https') { - url.port = 443; - } - url.full = url.scheme + '://' + url.fullHost; - } - return url; -}; - -/* Storage for query variables */ -var _queryVariables = null; - -/** - * Returns the window location query variables. Query is parsed on the first - * call and the same object is returned on subsequent calls. The mapping - * is from keys to an array of values. Parameters without values will have - * an object key set but no value added to the value array. Values are - * unescaped. - * - * ...?k1=v1&k2=v2: - * { - * "k1": ["v1"], - * "k2": ["v2"] - * } - * - * ...?k1=v1&k1=v2: - * { - * "k1": ["v1", "v2"] - * } - * - * ...?k1=v1&k2: - * { - * "k1": ["v1"], - * "k2": [] - * } - * - * ...?k1=v1&k1: - * { - * "k1": ["v1"] - * } - * - * ...?k1&k1: - * { - * "k1": [] - * } - * - * @param query the query string to parse (optional, default to cached - * results from parsing window location search query). - * - * @return object mapping keys to variables. - */ -util.getQueryVariables = function(query) { - var parse = function(q) { - var rval = {}; - var kvpairs = q.split('&'); - for(var i = 0; i < kvpairs.length; i++) { - var pos = kvpairs[i].indexOf('='); - var key; - var val; - if(pos > 0) { - key = kvpairs[i].substring(0, pos); - val = kvpairs[i].substring(pos + 1); - } else { - key = kvpairs[i]; - val = null; - } - if(!(key in rval)) { - rval[key] = []; - } - // disallow overriding object prototype keys - if(!(key in Object.prototype) && val !== null) { - rval[key].push(unescape(val)); - } - } - return rval; - }; - - var rval; - if(typeof(query) === 'undefined') { - // set cached variables if needed - if(_queryVariables === null) { - if(typeof(window) !== 'undefined' && window.location && window.location.search) { - // parse window search query - _queryVariables = parse(window.location.search.substring(1)); - } else { - // no query variables available - _queryVariables = {}; - } - } - rval = _queryVariables; - } else { - // parse given query - rval = parse(query); - } - return rval; -}; - -/** - * Parses a fragment into a path and query. This method will take a URI - * fragment and break it up as if it were the main URI. For example: - * /bar/baz?a=1&b=2 - * results in: - * { - * path: ["bar", "baz"], - * query: {"k1": ["v1"], "k2": ["v2"]} - * } - * - * @return object with a path array and query object. - */ -util.parseFragment = function(fragment) { - // default to whole fragment - var fp = fragment; - var fq = ''; - // split into path and query if possible at the first '?' - var pos = fragment.indexOf('?'); - if(pos > 0) { - fp = fragment.substring(0, pos); - fq = fragment.substring(pos + 1); - } - // split path based on '/' and ignore first element if empty - var path = fp.split('/'); - if(path.length > 0 && path[0] === '') { - path.shift(); - } - // convert query into object - var query = (fq === '') ? {} : util.getQueryVariables(fq); - - return { - pathString: fp, - queryString: fq, - path: path, - query: query - }; -}; - -/** - * Makes a request out of a URI-like request string. This is intended to - * be used where a fragment id (after a URI '#') is parsed as a URI with - * path and query parts. The string should have a path beginning and - * delimited by '/' and optional query parameters following a '?'. The - * query should be a standard URL set of key value pairs delimited by - * '&'. For backwards compatibility the initial '/' on the path is not - * required. The request object has the following API, (fully described - * in the method code): - * { - * path: . - * query: , - * getPath(i): get part or all of the split path array, - * getQuery(k, i): get part or all of a query key array, - * getQueryLast(k, _default): get last element of a query key array. - * } - * - * @return object with request parameters. - */ -util.makeRequest = function(reqString) { - var frag = util.parseFragment(reqString); - var req = { - // full path string - path: frag.pathString, - // full query string - query: frag.queryString, - /** - * Get path or element in path. - * - * @param i optional path index. - * - * @return path or part of path if i provided. - */ - getPath: function(i) { - return (typeof(i) === 'undefined') ? frag.path : frag.path[i]; - }, - /** - * Get query, values for a key, or value for a key index. - * - * @param k optional query key. - * @param i optional query key index. - * - * @return query, values for a key, or value for a key index. - */ - getQuery: function(k, i) { - var rval; - if(typeof(k) === 'undefined') { - rval = frag.query; - } else { - rval = frag.query[k]; - if(rval && typeof(i) !== 'undefined') { - rval = rval[i]; - } - } - return rval; - }, - getQueryLast: function(k, _default) { - var rval; - var vals = req.getQuery(k); - if(vals) { - rval = vals[vals.length - 1]; - } else { - rval = _default; - } - return rval; - } - }; - return req; -}; - -/** - * Makes a URI out of a path, an object with query parameters, and a - * fragment. Uses jQuery.param() internally for query string creation. - * If the path is an array, it will be joined with '/'. - * - * @param path string path or array of strings. - * @param query object with query parameters. (optional) - * @param fragment fragment string. (optional) - * - * @return string object with request parameters. - */ -util.makeLink = function(path, query, fragment) { - // join path parts if needed - path = jQuery.isArray(path) ? path.join('/') : path; - - var qstr = jQuery.param(query || {}); - fragment = fragment || ''; - return path + - ((qstr.length > 0) ? ('?' + qstr) : '') + - ((fragment.length > 0) ? ('#' + fragment) : ''); -}; - -/** - * Follows a path of keys deep into an object hierarchy and set a value. - * If a key does not exist or it's value is not an object, create an - * object in it's place. This can be destructive to a object tree if - * leaf nodes are given as non-final path keys. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - * @param value the value to set. - */ -util.setPath = function(object, keys, value) { - // need to start at an object - if(typeof(object) === 'object' && object !== null) { - var i = 0; - var len = keys.length; - while(i < len) { - var next = keys[i++]; - if(i == len) { - // last - object[next] = value; - } else { - // more - var hasNext = (next in object); - if(!hasNext || - (hasNext && typeof(object[next]) !== 'object') || - (hasNext && object[next] === null)) { - object[next] = {}; - } - object = object[next]; - } - } - } -}; - -/** - * Follows a path of keys deep into an object hierarchy and return a value. - * If a key does not exist, create an object in it's place. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - * @param _default value to return if path not found. - * - * @return the value at the path if found, else default if given, else - * undefined. - */ -util.getPath = function(object, keys, _default) { - var i = 0; - var len = keys.length; - var hasNext = true; - while(hasNext && i < len && - typeof(object) === 'object' && object !== null) { - var next = keys[i++]; - hasNext = next in object; - if(hasNext) { - object = object[next]; - } - } - return (hasNext ? object : _default); -}; - -/** - * Follow a path of keys deep into an object hierarchy and delete the - * last one. If a key does not exist, do nothing. - * Used to avoid exceptions from missing parts of the path. - * - * @param object the starting object. - * @param keys an array of string keys. - */ -util.deletePath = function(object, keys) { - // need to start at an object - if(typeof(object) === 'object' && object !== null) { - var i = 0; - var len = keys.length; - while(i < len) { - var next = keys[i++]; - if(i == len) { - // last - delete object[next]; - } else { - // more - if(!(next in object) || - (typeof(object[next]) !== 'object') || - (object[next] === null)) { - break; - } - object = object[next]; - } - } - } -}; - /** * Check if an object is empty. * diff --git a/lib/x509.js b/lib/x509.js index 0f9ff6684..2877810c1 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -251,8 +251,8 @@ var x509CertificateValidator = { constructed: true, captureAsn1: 'certSubject' }, - // SubjectPublicKeyInfo - publicKeyValidator, + // SubjectPublicKeyInfo + publicKeyValidator, { // issuerUniqueID (optional) name: 'Certificate.TBSCertificate.issuerUniqueID', @@ -464,32 +464,33 @@ var certificationRequestValidator = { captureAsn1: 'csr', value: [ certificationRequestInfoValidator, { - // AlgorithmIdentifier (signature algorithm) - name: 'CertificationRequest.signatureAlgorithm', - tagClass: asn1.Class.UNIVERSAL, - type: asn1.Type.SEQUENCE, - constructed: true, - value: [{ - // algorithm - name: 'CertificationRequest.signatureAlgorithm.algorithm', + // AlgorithmIdentifier (signature algorithm) + name: 'CertificationRequest.signatureAlgorithm', tagClass: asn1.Class.UNIVERSAL, - type: asn1.Type.OID, - constructed: false, - capture: 'csrSignatureOid' + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + // algorithm + name: 'CertificationRequest.signatureAlgorithm.algorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OID, + constructed: false, + capture: 'csrSignatureOid' + }, { + name: 'CertificationRequest.signatureAlgorithm.parameters', + tagClass: asn1.Class.UNIVERSAL, + optional: true, + captureAsn1: 'csrSignatureParams' + }] }, { - name: 'CertificationRequest.signatureAlgorithm.parameters', + // signature + name: 'CertificationRequest.signature', tagClass: asn1.Class.UNIVERSAL, - optional: true, - captureAsn1: 'csrSignatureParams' - }] - }, { - // signature - name: 'CertificationRequest.signature', - tagClass: asn1.Class.UNIVERSAL, - type: asn1.Type.BITSTRING, - constructed: false, - captureBitStringValue: 'csrSignature' - }] + type: asn1.Type.BITSTRING, + constructed: false, + captureBitStringValue: 'csrSignature' + } + ] }; /** @@ -688,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. * @@ -709,13 +805,15 @@ pki.certificateFromPem = function(pem, computeHash, strict) { if(msg.type !== 'CERTIFICATE' && msg.type !== 'X509 CERTIFICATE' && msg.type !== 'TRUSTED CERTIFICATE') { - var error = new Error('Could not convert certificate from PEM; PEM header type ' + + var error = new Error( + 'Could not convert certificate from PEM; PEM header type ' + 'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".'); error.headerType = msg.type; throw error; } if(msg.procType && msg.procType.type === 'ENCRYPTED') { - throw new Error('Could not convert certificate from PEM; PEM is encrypted.'); + throw new Error( + 'Could not convert certificate from PEM; PEM is encrypted.'); } // convert DER to ASN.1 object @@ -822,14 +920,14 @@ pki.getPublicKeyFingerprint = function(key, options) { var bytes; switch(type) { - case 'RSAPublicKey': - bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes(); - break; - case 'SubjectPublicKeyInfo': - bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes(); - break; - default: - throw new Error('Unknown fingerprint type "' + options.type + '".'); + case 'RSAPublicKey': + bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes(); + break; + case 'SubjectPublicKeyInfo': + bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes(); + break; + default: + throw new Error('Unknown fingerprint type "' + options.type + '".'); } // hash public key bytes @@ -1062,46 +1160,22 @@ pki.createCertificate = function() { if(!cert.issued(child)) { var issuer = child.issuer; var subject = cert.subject; - var error = new Error('The parent certificate did not issue the given child ' + + var error = new Error( + '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); @@ -1110,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; @@ -1329,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); @@ -1368,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); }; @@ -1375,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; } @@ -1383,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); }; @@ -1390,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; } @@ -1598,24 +1607,24 @@ pki.certificateExtensionFromAsn1 = function(ext) { // Note: Support for types 1,2,6,7,8 switch(gn.type) { - // rfc822Name - case 1: - // dNSName - case 2: - // uniformResourceIdentifier (URI) - case 6: - break; - // IPAddress - case 7: - // convert to IPv4/IPv6 string representation - altName.ip = forge.util.bytesToIP(gn.value); - break; - // registeredID - case 8: - altName.oid = asn1.derToOid(gn.value); - break; - default: - // unsupported + // rfc822Name + case 1: + // dNSName + case 2: + // uniformResourceIdentifier (URI) + case 6: + break; + // IPAddress + case 7: + // convert to IPv4/IPv6 string representation + altName.ip = forge.util.bytesToIP(gn.value); + break; + // registeredID + case 8: + altName.oid = asn1.derToOid(gn.value); + break; + default: + // unsupported } } } else if(e.name === 'subjectKeyIdentifier') { @@ -1673,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); @@ -1843,37 +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 || @@ -1883,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; @@ -2272,6 +2186,15 @@ function _fillMissingExtensionFields(e, options) { asn1.Class.CONTEXT_SPECIFIC, altName.type, false, value)); } + } else if(e.name === 'nsComment' && options.cert) { + // sanity check value is ASCII (req'd) and not too big + if(!(/^[\x00-\x7F]*$/.test(e.comment)) || + (e.comment.length < 1) || (e.comment.length > 128)) { + throw new Error('Invalid "nsComment" content.'); + } + // IA5STRING opaque comment + e.value = asn1.create( + asn1.Class.UNIVERSAL, asn1.Type.IA5STRING, false, e.comment); } else if(e.name === 'subjectKeyIdentifier' && options.cert) { var ski = options.cert.generateSubjectKeyIdentifier(); e.subjectKeyIdentifier = ski.toHex(); @@ -2308,15 +2231,17 @@ function _fillMissingExtensionFields(e, options) { seq.push( asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, false, serialNumber)); } - } else if (e.name === 'cRLDistributionPoints') { + } else if(e.name === 'cRLDistributionPoints') { e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []); var seq = e.value.value; // Create sub SEQUENCE of DistributionPointName - var subSeq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []); + var subSeq = asn1.create( + asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []); // Create fullName CHOICE - var fullNameGeneralNames = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []); + var fullNameGeneralNames = asn1.create( + asn1.Class.CONTEXT_SPECIFIC, 0, true, []); var altName; for(var n = 0; n < e.altNames.length; ++n) { altName = e.altNames[n]; @@ -2345,7 +2270,8 @@ function _fillMissingExtensionFields(e, options) { } // Add to the parent SEQUENCE - subSeq.value.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [fullNameGeneralNames])); + subSeq.value.push(asn1.create( + asn1.Class.CONTEXT_SPECIFIC, 0, true, [fullNameGeneralNames])); seq.push(subSeq); } @@ -2368,44 +2294,44 @@ function _fillMissingExtensionFields(e, options) { */ function _signatureParametersToAsn1(oid, params) { switch(oid) { - case oids['RSASSA-PSS']: - var parts = []; - - if(params.hash.algorithmOid !== undefined) { - parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, - asn1.oidToDer(params.hash.algorithmOid).getBytes()), - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') - ]) - ])); - } + case oids['RSASSA-PSS']: + var parts = []; - if(params.mgf.algorithmOid !== undefined) { - parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, - asn1.oidToDer(params.mgf.algorithmOid).getBytes()), + if(params.hash.algorithmOid !== undefined) { + parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, - asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()), + asn1.oidToDer(params.hash.algorithmOid).getBytes()), asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') ]) - ]) - ])); - } + ])); + } - if(params.saltLength !== undefined) { - parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, - asn1.integerToDer(params.saltLength).getBytes()) - ])); - } + if(params.mgf.algorithmOid !== undefined) { + parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, + asn1.oidToDer(params.mgf.algorithmOid).getBytes()), + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, + asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()), + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') + ]) + ]) + ])); + } - return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts); + if(params.saltLength !== undefined) { + parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, + asn1.integerToDer(params.saltLength).getBytes()) + ])); + } + + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts); - default: - return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''); + default: + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''); } } @@ -2465,6 +2391,29 @@ function _CRIAttributesToAsn1(csr) { return rval; } +var jan_1_1950 = new Date('1950-01-01T00:00:00Z'); +var jan_1_2050 = new Date('2050-01-01T00:00:00Z'); + +/** + * Converts a Date object to ASN.1 + * Handles the different format before and after 1st January 2050 + * + * @param date date object. + * + * @return the ASN.1 object representing the date. + */ +function _dateToAsn1(date) { + if(date >= jan_1_1950 && date < jan_1_2050) { + return asn1.create( + asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false, + asn1.dateToUtcTime(date)); + } else { + return asn1.create( + asn1.Class.UNIVERSAL, asn1.Type.GENERALIZEDTIME, false, + asn1.dateToGeneralizedTime(date)); + } +} + /** * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate. * @@ -2474,6 +2423,8 @@ function _CRIAttributesToAsn1(csr) { */ pki.getTBSCertificate = function(cert) { // TBSCertificate + var notBefore = _dateToAsn1(cert.validity.notBefore); + var notAfter = _dateToAsn1(cert.validity.notAfter); var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ // version asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ @@ -2497,12 +2448,8 @@ pki.getTBSCertificate = function(cert) { _dnToAsn1(cert.issuer), // validity asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ - // notBefore - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false, - asn1.dateToUtcTime(cert.validity.notBefore)), - // notAfter - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false, - asn1.dateToUtcTime(cert.validity.notAfter)) + notBefore, + notAfter ]), // subject _dnToAsn1(cert.subject), @@ -2749,7 +2696,7 @@ pki.createCaStore = function(certs) { ensureSubjectHasHash(cert.subject); - if(!caStore.hasCertificate(cert)) { // avoid duplicate certificates in store + if(!caStore.hasCertificate(cert)) { // avoid duplicate certificates in store if(cert.subject.hash in caStore.certs) { // subject hash already exists, append to array var tmp = caStore.certs[cert.subject.hash]; @@ -2874,7 +2821,7 @@ pki.createCaStore = function(certs) { // produce subject hash if it doesn't exist if(!subject.hash) { var md = forge.md.sha1.create(); - subject.attributes = pki.RDNAttributesAsArray(_dnToAsn1(subject), md); + subject.attributes = pki.RDNAttributesAsArray(_dnToAsn1(subject), md); subject.hash = md.digest().toHex(); } } @@ -2910,7 +2857,13 @@ pki.certificateError = { * @param caStore a certificate store to verify against. * @param chain the certificate chain to verify, with the root or highest * authority at the end (an array of certificates). - * @param verify called for every certificate in the chain. + * @param options a callback to be called for every certificate in the chain or + * an object with: + * verify a callback to be called for every certificate in the + * chain + * validityCheckDate the date against which the certificate + * validity period should be checked. Pass null to not check + * the validity period. By default, the current date is used. * * The verify callback has the following signature: * @@ -2926,7 +2879,7 @@ pki.certificateError = { * * @return true if successful, error thrown if not. */ -pki.verifyCertificateChain = function(caStore, chain, verify) { +pki.verifyCertificateChain = function(caStore, chain, options) { /* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate Section 6: Certification Path Validation See inline parentheticals related to this particular implementation. @@ -3056,13 +3009,26 @@ pki.verifyCertificateChain = function(caStore, chain, verify) { CAs that may appear below a CA before only end-entity certificates may be issued. */ + // if a verify callback is passed as the third parameter, package it within + // the options object. This is to support a legacy function signature that + // expected the verify callback as the third parameter. + if(typeof options === 'function') { + options = {verify: options}; + } + options = options || {}; + // copy cert chain references to another array to protect against changes // in verify callback chain = chain.slice(0); var certs = chain.slice(0); - // get current date - var now = new Date(); + var validityCheckDate = options.validityCheckDate; + // if no validityCheckDate is specified, default to the current date. Make + // sure to maintain the value null because it indicates that the validity + // period should not be checked. + if(typeof validityCheckDate === 'undefined') { + validityCheckDate = new Date(); + } // verify each cert in the chain using its parent, where the parent // is either the next in the chain or from the CA store @@ -3074,15 +3040,20 @@ pki.verifyCertificateChain = function(caStore, chain, verify) { var parent = null; var selfSigned = false; - // 1. check valid time - if(now < cert.validity.notBefore || now > cert.validity.notAfter) { - error = { - message: 'Certificate is not valid yet or has expired.', - error: pki.certificateError.certificate_expired, - notBefore: cert.validity.notBefore, - notAfter: cert.validity.notAfter, - now: now - }; + if(validityCheckDate) { + // 1. check valid time + if(validityCheckDate < cert.validity.notBefore || + validityCheckDate > cert.validity.notAfter) { + error = { + message: 'Certificate is not valid yet or has expired.', + error: pki.certificateError.certificate_expired, + notBefore: cert.validity.notBefore, + notAfter: cert.validity.notAfter, + // TODO: we might want to reconsider renaming 'now' to + // 'validityCheckDate' should this API be changed in the future. + now: validityCheckDate + }; + } } // 2. verify with parent from chain or CA store @@ -3229,7 +3200,7 @@ pki.verifyCertificateChain = function(caStore, chain, verify) { // call application callback var vfd = (error === null) ? true : error.error; - var ret = verify ? verify(vfd, depth, certs) : vfd; + var ret = options.verify ? options.verify(vfd, depth, certs) : vfd; if(ret === true) { // clear any set error error = null; @@ -3247,7 +3218,7 @@ pki.verifyCertificateChain = function(caStore, chain, verify) { // set custom message and error if(typeof ret === 'object' && !forge.util.isArray(ret)) { if(ret.message) { - error.message = ret.message; + error.message = ret.message; } if(ret.error) { error.error = ret.error; 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 6369621a9..68231c0ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-forge", - "version": "0.7.5", + "version": "1.3.0", "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.", "homepage": "https://github.com/digitalbazaar/forge", "author": { @@ -15,32 +15,33 @@ "Christoph Dorn " ], "devDependencies": { - "browserify": "^16.1.0", - "commander": "^2.14.1", - "cross-env": "^5.1.3", + "browserify": "^16.5.2", + "commander": "^2.20.0", + "cross-env": "^5.2.1", + "eslint": "^7.27.0", + "eslint-config-digitalbazaar": "^2.8.0", "express": "^4.16.2", - "jscs": "^3.0.7", - "jshint": "^2.9.5", - "karma": "^2.0.0", - "karma-browserify": "^5.2.0", - "karma-chrome-launcher": "^2.2.0", + "karma": "^4.4.1", + "karma-browserify": "^7.0.0", + "karma-chrome-launcher": "^3.1.0", "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", + "karma-firefox-launcher": "^1.3.0", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", - "karma-phantomjs-launcher": "^1.0.2", "karma-safari-launcher": "^1.0.0", - "karma-sauce-launcher": "^1.2.0", - "karma-sourcemap-loader": "^0.3.7", + "karma-sauce-launcher": "^2.0.2", + "karma-sourcemap-loader": "^0.3.8", "karma-tap-reporter": "0.0.6", - "karma-webpack": "^2.0.13", - "mocha": "^5.0.1", + "karma-webpack": "^4.0.2", + "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "nodejs-websocket": "^1.7.1", - "nyc": "^11.5.0", - "opts": "^1.2.2", - "webpack": "^3.11.0" + "nyc": "^15.1.0", + "opts": "^1.2.7", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12", + "worker-loader": "^2.0.0" }, "repository": { "type": "git", @@ -59,7 +60,7 @@ "dist/*.min.js.map" ], "engines": { - "node": "*" + "node": ">= 6.13.0" }, "keywords": [ "aes", @@ -94,18 +95,17 @@ "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", - "jscs": "jscs *.js lib/*.js tests/*.js tests/unit/*.js tests/legacy/*.js tests/issues/*.js tests/websockets/*.js", - "jshint": "jshint *.js lib/*.js tests/unit/*.js tests/legacy/*.js tests/issues/*.js tests/websockets/*.js", - "_jscs": "jscs *.js lib/*.js tests/*.js tests/unit/*.js tests/legacy/*.js tests/issues/*.js tests/websockets/*.js", - "_jshint": "jshint *.js lib/*.js tests/*.js tests/unit/*.js tests/legacy/*.js tests/issues/*.js tests/websockets/*.js" + "lint": "eslint *.js lib/*.js tests/*.js tests/**/*.js examples/*.js flash/*.js" }, "nyc": { "exclude": [ 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/benchmarks/so-44303784.js b/tests/benchmarks/so-44303784.js index 924e68c6c..05ec9977a 100644 --- a/tests/benchmarks/so-44303784.js +++ b/tests/benchmarks/so-44303784.js @@ -7,7 +7,7 @@ const forge = require('../..'); const assert = require('assert'); const crypto = require('crypto'); -const pwd = "aStringPassword"; +const pwd = 'aStringPassword'; const iv = forge.random.getBytesSync(16); const salt = forge.random.getBytesSync(16); const key = forge.pkcs5.pbkdf2(pwd, salt, 100, 16); @@ -63,9 +63,9 @@ function test_forge_chunk(bytes, chunkSize) { } function test_node(bytes) { - const bufb = new Buffer(bytes, 'binary'); - const ivb = new Buffer(iv, 'binary'); - const keyb = new Buffer(key, 'binary'); + const bufb = Buffer.from(bytes, 'binary'); + const ivb = Buffer.from(iv, 'binary'); + const keyb = Buffer.from(key, 'binary'); const start = new Date(); @@ -86,7 +86,7 @@ function test_node(bytes) { function data(megs) { // slower single chunk const start = new Date(); - var x = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + var x = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; var plain = ''; const minlen = megs * 1024 * 1024; while(plain.length < minlen) { @@ -117,7 +117,7 @@ function data_chunk(megs, chunkSize) { // faster with chunksize const start = new Date(); // make some large plain text bigger than some size - var x = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + var x = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; var plain = ''; const minlen = megs * 1024 * 1024; while(plain.length < minlen) { @@ -185,8 +185,10 @@ function compareImpl() { tns.forEach(res => assert(input.plain == res.plain)); const tn = tns.reduce((prev, cur) => prev.time < cur.time ? prev : cur); - csv += `${i}\t${tf.time}\t${i/tf.time}\t${tfc.time}\t${i/tfc.time}\t${tn.time}\t${i/tn.time}\t${tf.time/tn.time}\t${tfc.time/tn.time}\n`; - console.log(`m:${i} tf:${tf.time} tf/s:${i/tf.time} tfc:${tfc.time} tfc/s:${i/tfc.time} tn:${tn.time} tn/s:${i/tn.time} sf:${tf.time/tn.time} sfc:${tfc.time/tn.time}`); + /* eslint-disable max-len */ + csv += `${i}\t${tf.time}\t${i / tf.time}\t${tfc.time}\t${i / tfc.time}\t${tn.time}\t${i / tn.time}\t${tf.time / tn.time}\t${tfc.time / tn.time}\n`; + console.log(`m:${i} tf:${tf.time} tf/s:${i / tf.time} tfc:${tfc.time} tfc/s:${i / tfc.time} tn:${tn.time} tn/s:${i / tn.time} sf:${tf.time / tn.time} sfc:${tfc.time / tn.time}`); + /* eslint-enable max-len */ } console.log(csv); } @@ -196,7 +198,7 @@ function compareDecChunkSize() { let csv = ''; const input = data_chunk(megs, 1024 * 64); function _test(k) { - chunkSize = 1024 * k; + const chunkSize = 1024 * k; const tfcs = [ test_forge_chunk(input.encrypted, chunkSize), test_forge_chunk(input.encrypted, chunkSize), @@ -204,13 +206,13 @@ function compareDecChunkSize() { ]; tfcs.forEach(res => assert(input.plain == res.plain)); const tfc = tfcs.reduce((prev, cur) => prev.time < cur.time ? prev : cur); - csv += `${k}\t${tfc.time}\t${megs/tfc.time}\n`; - console.log(`k:${k} tfc:${tfc.time} tfc/s:${megs/tfc.time}`); + csv += `${k}\t${tfc.time}\t${megs / tfc.time}\n`; + console.log(`k:${k} tfc:${tfc.time} tfc/s:${megs / tfc.time}`); } // sweep KB chunkSize const sweep = [ - 1,2,4,8,16,32,64,96,128,160,192,256, - 320,384,448,512,576,640,704,768,832,896,960,1024 + 1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 256, + 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024 ]; sweep.forEach(k => _test(k)); console.log(csv); @@ -220,20 +222,20 @@ function compareEncChunkSize() { const megs = 10; let csv = ''; function _test(k) { - chunkSize = 1024 * k; + const chunkSize = 1024 * k; const dcs = [ data_chunk(megs, chunkSize), data_chunk(megs, chunkSize), data_chunk(megs, chunkSize) ]; const dc = dcs.reduce((prev, cur) => prev.time < cur.time ? prev : cur); - csv += `${k}\t${dc.time}\t${megs/dc.time}\n`; - console.log(`k:${k} dc:${dc.time} dc/s:${megs/dc.time}`); + csv += `${k}\t${dc.time}\t${megs / dc.time}\n`; + console.log(`k:${k} dc:${dc.time} dc/s:${megs / dc.time}`); } // sweep KB chunkSize const sweep = [ - 1,2,4,8,16,32,64,96,128,160,192,256, - 320,384,448,512,576,640,704,768,832,896,960,1024 + 1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 256, + 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024 ]; sweep.forEach(k => _test(k)); console.log(csv); 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/karma/index.js b/tests/karma/index.js new file mode 100644 index 000000000..8d95c9422 --- /dev/null +++ b/tests/karma/index.js @@ -0,0 +1,8 @@ +// The karma tests include all standard unit tests... +require('../unit'); + +// ...plus some tests that can only run in the browser (e.g. web worker +// compatibility) +// FIXME: if browserify testing is dropped, enable this and remove browserify +// specific code from karma.config.js +//require('./web-worker-rsa'); diff --git a/tests/karma/testWorker.js b/tests/karma/testWorker.js new file mode 100644 index 000000000..b7eb62f21 --- /dev/null +++ b/tests/karma/testWorker.js @@ -0,0 +1,65 @@ +var RSA = require('../../lib/rsa'); +var PKI = require('../../lib/pki'); + +// log on main thread +function _log(message) { + self.postMessage({ + type: 'log', + message: message + }); +} + +// The following code allows main thread scripts to make the worker call +// certain forge APIs within the worker (primarily to ensure compatibility) +// We define a light-weight protocol to simplify testing +// @param event +// event.data {Object} message content +// event.data.type {String} message type +// event.data.* {Any} type specific data +self.addEventListener('message', function(event) { + // Test scripts call worker.postMessage(data) + // we can access the payload via event.data + + //_log('message type: ' + event.data.type); + + // data.type defines what the worker should call + switch(event.data.type) { + case 'ping': + self.postMessage({ + type: 'pong' + }); + break; + case 'rsa.generateKeyPair': + //_log('keygen start'); + //RSA.generateKeyPair({bits: 512, workers: -1}, function(error, keyPair) { + RSA.generateKeyPair({bits: 512, workers: 1}, function(error, keyPair) { + //_log('keygen done'); + // We signal the outcome of the call via event.data with... + if(error) { + // ...event.data.type === 'error' if the call failed + self.postMessage({ + type: 'error', + error: error.toString() + }); + } else { + // ...event.data.type === 'success' if the call succeeded + self.postMessage({ + type: 'success', + keypair: { + publicKey: PKI.publicKeyToPem(keyPair.publicKey), + privateKey: PKI.privateKeyToPem(keyPair.privateKey) + } + }); + } + }); + break; + case 'stop': + self.close(); + break; + default: + self.postMessage({ + type: 'error', + error: 'Unknown message type: ' + event.data.type + }); + } +}); diff --git a/tests/karma/web-worker-rsa.js b/tests/karma/web-worker-rsa.js new file mode 100644 index 000000000..38d156254 --- /dev/null +++ b/tests/karma/web-worker-rsa.js @@ -0,0 +1,55 @@ +var ASSERT = require('assert'); + +// The worker-loader build the ./testWorker.js as a separate bundle and returns +// a constructor that saves us from having to know the public script path +// I.e.: new Worker('path/to/public/script.js') becomes new TestWorker() +var TestWorker = require('worker-loader!./testWorker'); +var testWorker = new TestWorker(); + +function _log(message) { + console.log('[main] ' + message); +} + +describe('web worker rsa', function() { + it('should generate key pairs when running forge in a web worker', function(done) { + // Make test worker call rsa.generateKeyPair() on its own side + //testWorker.postMessage({type: 'ping'}); + testWorker.postMessage({type: 'rsa.generateKeyPair'}); + + // Wait for a result (see testWorker.js for what event data to expect) + testWorker.addEventListener('message', function(event) { + //_log('message type: ' + event.data.type); + switch(event.data.type) { + case 'success': + // This is primarily to ensure that the node-forge code runs + // successfully in the worker (e.g. no usages of `window` or + // similar). So, comparing for structural sanity of the result only + // should be fine. + ASSERT.equal(typeof event.data.keypair, 'object'); + ASSERT.equal(typeof event.data.keypair.publicKey, 'string'); + ASSERT.equal(typeof event.data.keypair.privateKey, 'string'); + + // done with worker tests + testWorker.terminate(); + + done(); + break; + case 'error': + ASSERT.fail('web worker error: ' + event.data.result); + break; + case 'log': + console.log('[worker] ' + event.data.message); + break; + case 'pong': + // for debugging worker is alive + //_log('pong'); + break; + case 'stop': + testWorker.terminate(); + break; + default: + console.log('UNKNOWN MESSAGE TYPE: ' + event.data.type); + } + }); + }); +}); 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/aes.js b/tests/unit/aes.js index ef7c4693a..2cd5bd23e 100644 --- a/tests/unit/aes.js +++ b/tests/unit/aes.js @@ -40,79 +40,79 @@ var UTIL = require('../../lib/util'); }); it('should encrypt a single block with a 192-bit key', function() { - var key = [ - 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, - 0x10111213, 0x14151617]; - var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff]; - - var output = []; - var w = AES._expandKey(key, false); - AES._updateBlock(w, block, output, false); - - var out = UTIL.createBuffer(); - out.putInt32(output[0]); - out.putInt32(output[1]); - out.putInt32(output[2]); - out.putInt32(output[3]); - - ASSERT.equal(out.toHex(), 'dda97ca4864cdfe06eaf70a0ec0d7191'); + var key = [ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617]; + var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff]; + + var output = []; + var w = AES._expandKey(key, false); + AES._updateBlock(w, block, output, false); + + var out = UTIL.createBuffer(); + out.putInt32(output[0]); + out.putInt32(output[1]); + out.putInt32(output[2]); + out.putInt32(output[3]); + + ASSERT.equal(out.toHex(), 'dda97ca4864cdfe06eaf70a0ec0d7191'); }); it('should decrypt a single block with a 192-bit key', function() { - var key = [ - 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, - 0x10111213, 0x14151617]; - var block = [0xdda97ca4, 0x864cdfe0, 0x6eaf70a0, 0xec0d7191]; - - var output = []; - var w = AES._expandKey(key, true); - AES._updateBlock(w, block, output, true); - - var out = UTIL.createBuffer(); - out.putInt32(output[0]); - out.putInt32(output[1]); - out.putInt32(output[2]); - out.putInt32(output[3]); - - ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff'); + var key = [ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617]; + var block = [0xdda97ca4, 0x864cdfe0, 0x6eaf70a0, 0xec0d7191]; + + var output = []; + var w = AES._expandKey(key, true); + AES._updateBlock(w, block, output, true); + + var out = UTIL.createBuffer(); + out.putInt32(output[0]); + out.putInt32(output[1]); + out.putInt32(output[2]); + out.putInt32(output[3]); + + ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff'); }); it('should encrypt a single block with a 256-bit key', function() { - var key = [ - 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, - 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f]; - var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff]; - - var output = []; - var w = AES._expandKey(key, false); - AES._updateBlock(w, block, output, false); - - var out = UTIL.createBuffer(); - out.putInt32(output[0]); - out.putInt32(output[1]); - out.putInt32(output[2]); - out.putInt32(output[3]); - - ASSERT.equal(out.toHex(), '8ea2b7ca516745bfeafc49904b496089'); + var key = [ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f]; + var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff]; + + var output = []; + var w = AES._expandKey(key, false); + AES._updateBlock(w, block, output, false); + + var out = UTIL.createBuffer(); + out.putInt32(output[0]); + out.putInt32(output[1]); + out.putInt32(output[2]); + out.putInt32(output[3]); + + ASSERT.equal(out.toHex(), '8ea2b7ca516745bfeafc49904b496089'); }); it('should decrypt a single block with a 256-bit key', function() { - var key = [ - 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, - 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f]; - var block = [0x8ea2b7ca, 0x516745bf, 0xeafc4990, 0x4b496089]; - - var output = []; - var w = AES._expandKey(key, true); - AES._updateBlock(w, block, output, true); - - var out = UTIL.createBuffer(); - out.putInt32(output[0]); - out.putInt32(output[1]); - out.putInt32(output[2]); - out.putInt32(output[3]); - - ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff'); + var key = [ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f]; + var block = [0x8ea2b7ca, 0x516745bf, 0xeafc4990, 0x4b496089]; + + var output = []; + var w = AES._expandKey(key, true); + AES._updateBlock(w, block, output, true); + + var out = UTIL.createBuffer(); + out.putInt32(output[0]); + out.putInt32(output[1]); + out.putInt32(output[2]); + out.putInt32(output[3]); + + ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff'); }); // AES-128-ECB @@ -1080,7 +1080,8 @@ var UTIL = require('../../lib/util'); 'feffe9928665731c6d6a8f9467308308', 'feffe9928665731c6d6a8f9467308308', 'feffe9928665731c6d6a8f9467308308', - '00000000000000000000000000000000' + '00000000000000000000000000000000', + '31313131323232323333333334343434' ]; var ivs = [ @@ -1093,7 +1094,8 @@ var UTIL = require('../../lib/util'); '6a7a9538534f7da1e4c303d2a318a728' + 'c3c0c95156809539fcf0e2429a6b5254' + '16aedbf5a0de6a57a637b39b', - '000000000000000000000000' + '000000000000000000000000', + '313131323232333333343434' ]; var adatas = [ @@ -1106,6 +1108,7 @@ var UTIL = require('../../lib/util'); 'abaddad2', 'feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2', + '', '' ]; @@ -1128,7 +1131,9 @@ var UTIL = require('../../lib/util'); '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39', - '0000' + '0000', + '3131313131323232323231313131313232' + + '3232323131313131323232323231313131313232323232' ]; var outputs = [ @@ -1150,7 +1155,9 @@ var UTIL = require('../../lib/util'); 'be9112a5c3a211a8ba262a3cca7e2ca7' + '01e4a9a4fba43c90ccdcb281d48c7c6f' + 'd62875d2aca417034c34aee5', - '0388' + '0388', + '0d75de6b0ddea90e4846e5fafeccf82d91' + + '927f1b5e5074e29911be7d7fd2b317aea570a359354f2d' ]; var tags = [ @@ -1160,7 +1167,8 @@ var UTIL = require('../../lib/util'); '5bc94fbc3221a5db94fae95ae7121a47', '3612d2e79e3b0785561be14aaca2fccb', '619cc5aefffe0bfa462af43c1699d050', - '93dcdd26f79ec1dd9bff57204d9b33f5' + '93dcdd26f79ec1dd9bff57204d9b33f5', + '766028a0b2fa2fff04c564f3b960988f' ]; for(var i = 0; i < keys.length; ++i) { @@ -1211,6 +1219,22 @@ var UTIL = require('../../lib/util'); ASSERT.equal(out.toHex(), outputs[i]); ASSERT.equal(cipher.mode.tag.toHex(), tags[i]); }); + + it('should aes-128-gcm encrypt (blockSize/2+1 bytes at a time): ' + inputs[i], function() { + // encrypt + var cipher = CIPHER.createCipher('AES-GCM', key); + var size = cipher.blockSize / 2 + 1; + cipher.start({iv: iv, additionalData: adata}); + var input_ = UTIL.createBuffer(input); + var out = UTIL.createBuffer(); + while(input_.length() > 0) { + cipher.update(UTIL.createBuffer(input_.getBytes(size))); + out.putBytes(cipher.output.getBytes(size)); + } + cipher.finish(); + ASSERT.equal(out.toHex(), outputs[i]); + ASSERT.equal(cipher.mode.tag.toHex(), tags[i]); + }); })(i); } })(); @@ -1443,7 +1467,7 @@ var UTIL = require('../../lib/util'); var input = UTIL.hexToBytes(inputs[i]); var output = UTIL.hexToBytes(outputs[i]); - it('should aes-128-gcm encrypt: ' + inputs[i], function() { + it('should aes-256-gcm encrypt: ' + inputs[i], function() { // encrypt var cipher = CIPHER.createCipher('AES-GCM', key); cipher.start({iv: iv, additionalData: adata}); @@ -1453,7 +1477,7 @@ var UTIL = require('../../lib/util'); ASSERT.equal(cipher.mode.tag.toHex(), tags[i]); }); - it('should aes-128-gcm decrypt: ' + outputs[i], function() { + it('should aes-256-gcm decrypt: ' + outputs[i], function() { // decrypt var cipher = CIPHER.createDecipher('AES-GCM', key); cipher.start({ diff --git a/tests/unit/asn1.js b/tests/unit/asn1.js index a8be892bf..65a1db510 100644 --- a/tests/unit/asn1.js +++ b/tests/unit/asn1.js @@ -1,7 +1,6 @@ var ASSERT = require('assert'); var ASN1 = require('../../lib/asn1'); var UTIL = require('../../lib/util'); -var support = require('./support'); (function() { describe('asn1', function() { @@ -209,10 +208,6 @@ var support = require('./support'); })(); (function() { - // TODO: remove skipping PhantomJS tests when possible - // skip 2050 tests in PhantomJS - // likely due to https://bugs.webkit.org/show_bug.cgi?id=130123 - // can't parse dates between 2034-03-01 and 2100-02-28 var tests = [{ in: 'Jan 1 1949 00:00:00 GMT', out: '19490101000000Z' @@ -221,15 +216,13 @@ var support = require('./support'); out: '20000101000000Z' }, { in: 'Jan 1 2050 00:00:00 GMT', - out: '20500101000000Z', - phantomjsskip: true + out: '20500101000000Z' }, { in: 'Mar 1 2100 00:00:00 GMT', out: '21000301000000Z' }]; tests.forEach(function(test) { - var _it = (test.phantomjsskip && support.isPhantomJS) ? it.skip : it; - _it('should convert date "' + test.in + '" to generalized time', function() { + it('should convert date "' + test.in + '" to generalized time', function() { var d = ASN1.dateToGeneralizedTime(new Date(test.in)); ASSERT.equal(d, test.out); }); @@ -459,7 +452,7 @@ var support = require('./support'); // validator check if(!throws && options.v) { var capture = {}; - var errors = [] + var errors = []; var asn1ok = ASN1.validate(asn1, options.v, capture, errors); ASSERT.deepEqual(errors, []); if(options.captured) { diff --git a/tests/unit/des.js b/tests/unit/des.js index 77ac035e4..093756d2b 100644 --- a/tests/unit/des.js +++ b/tests/unit/des.js @@ -31,6 +31,22 @@ var UTIL = require('../../lib/util'); ASSERT.equal(decipher.output.getBytes(), 'foobar'); }); + it('should check des-cbc short IV', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc596')); + + var error = null; + try { + var cipher = CIPHER.createCipher('DES-CBC', key); + cipher.start({iv: iv}); + } catch(e) { + error = e; + } + ASSERT.ok(error, 'blocksize check should have failed'); + }); + // OpenSSL equivalent: // openssl enc -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt it('should des-cbc encrypt: foobar', function() { @@ -61,6 +77,90 @@ var UTIL = require('../../lib/util'); ASSERT.equal(decipher.output.getBytes(), 'foobar'); }); + // play.golang.org/p/LX_dP0cFuEt + it('should des-ctr encrypt: foobar', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer('foobar')); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '3a97fa79e631'); + }); + + // play.golang.org/p/6_MQBYzn04c + it('should des-ctr decrypt: foobar', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('6df74b7b4437'))); + cipher.finish(); + ASSERT.equal(cipher.output.getBytes(), 'foobar'); + }); + + // play.golang.org/p/i892aR7YsGK + it('should des-ctr encrypt: dead parrot', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer('dead parrot')); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '389df47fa733dcf4b99b7c'); + }); + + // play.golang.org/p/6L0LqPS9ARt + it('should des-ctr decrypt: 79f1527c5737f774f85c1a9399755d895ae7', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('79f1527c5737f774f85c1a9399755d895ae7'))); + cipher.finish(); + ASSERT.equal(cipher.output.getBytes(), 'riverrun, past Eve'); + }); + + // play.golang.org/p/WsSx6BXJniU + it('should des-ctr encrypt: 69742773206e6f742073696c6c7920656e6f756768', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('a1c06b381adf3651')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('818bcf76efc59662')); + + var cipher = CIPHER.createCipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('69742773206e6f742073696c6c7920656e6f756768'))); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '358cb268a72dd2f2eb87615060bd3a490e85136873'); + }); + + // play.golang.org/p/y01inAlMCEM + it('should des-ctr decrypt: 0a80bd81a4dc1303a62f', function() { + var key = new UTIL.createBuffer( + UTIL.hexToBytes('beefdeadbeefdead')); + var iv = new UTIL.createBuffer( + UTIL.hexToBytes('deadbeefdeadbeef')); + + var cipher = CIPHER.createDecipher('DES-CTR', key); + cipher.start({iv: iv}); + cipher.update(UTIL.createBuffer(UTIL.hexToBytes('0a80bd81a4dc1303a62f'))); + cipher.finish(); + ASSERT.equal(cipher.output.toHex(), '01189998819991197253'); + }); + // OpenSSL equivalent: // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt it('should 3des-ecb encrypt: foobar', function() { diff --git a/tests/unit/ed25519.js b/tests/unit/ed25519.js index fa204d8d9..a7af3ddac 100644 --- a/tests/unit/ed25519.js +++ b/tests/unit/ed25519.js @@ -1,7 +1,5 @@ var ASSERT = require('assert'); -var FORGE = require('../../lib/forge'); var ED25519 = require('../../lib/ed25519'); -var RANDOM = require('../../lib/random'); var SHA256 = require('../../lib/sha256'); var UTIL = require('../../lib/util'); @@ -62,6 +60,22 @@ var UTIL = require('../../lib/util'); ASSERT.equal(eb64(signature), b64Sha256Signature); }); + it('should sign a digest given 32 private key bytes', function() { + var pwd = 'password'; + var md = SHA256.create(); + md.update(pwd, 'utf8'); + var seed = md.digest().getBytes(); + var kp = ED25519.generateKeyPair({seed: seed}); + md = SHA256.create(); + md.update('test', 'utf8'); + var privateKey = kp.privateKey.slice(0, 32); + var signature = ED25519.sign({ + md: md, + privateKey: privateKey + }); + ASSERT.equal(eb64(signature), b64Sha256Signature); + }); + it('should sign a UTF-8 message', function() { var pwd = 'password'; var md = SHA256.create(); @@ -129,7 +143,7 @@ var UTIL = require('../../lib/util'); var seed = md.digest().getBytes(); var kp = ED25519.generateKeyPair({seed: seed}); var signature = ED25519.sign({ - message: new Buffer('test', 'utf8'), + message: Buffer.from('test', 'utf8'), privateKey: kp.privateKey }); ASSERT.equal(eb64(signature), b64Signature); @@ -181,7 +195,7 @@ var UTIL = require('../../lib/util'); var seed = md.digest().getBytes(); var kp = ED25519.generateKeyPair({seed: seed}); - var signature = new Buffer(db64(b64Signature).getBytes(), 'binary'); + var signature = Buffer.from(db64(b64Signature).getBytes(), 'binary'); var verified = ED25519.verify({ message: 'test', 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/pkcs1.js b/tests/unit/pkcs1.js index c26da03a5..6681802b3 100644 --- a/tests/unit/pkcs1.js +++ b/tests/unit/pkcs1.js @@ -686,13 +686,13 @@ var UTIL = require('../../lib/util'); message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=', seed: 'HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=', encrypted: 'AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp' - /* FIXME: could not convert 4.2', to SHA-256, message too long + /* FIXME: could not convert 4.2', to SHA-256, message too long }, { title: 'RSAES-OAEP Encryption Example 4.2', message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==', seed: '9UXViXWF49txqgy42nbFHQMq6WM=', encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe' - */ + */ }, { title: 'RSAES-OAEP Encryption Example 4.3', message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=', diff --git a/tests/unit/pkcs7.js b/tests/unit/pkcs7.js index c759d8720..e99c13867 100644 --- a/tests/unit/pkcs7.js +++ b/tests/unit/pkcs7.js @@ -5,7 +5,6 @@ var PKI = require('../../lib/pki'); var AES = require('../../lib/aes'); var DES = require('../../lib/des'); var UTIL = require('../../lib/util'); -var support = require('./support'); (function() { var _pem = { @@ -338,6 +337,43 @@ var support = require('./support'); 'BwEwHAYJKoZIhvcNAQkFMQ8XDTE4MDEyMjEyMzAwMlowIwYJKoZIhvcNAQkEMRYEFK1hHB7W5la2\r\n' + 'AWAHCWVgYPYyJzAxMAkGByqGSM44BAMELzAtAhUAsQXD04cP48o7HVHWJtVRHZEUkBICFHcuPVAu\r\n' + '7KVSbiWnFnDL0v87RSxhAAAAAAAA\r\n' + + '-----END PKCS7-----\r\n', + detachedSignature: + '-----BEGIN PKCS7-----\r\n' + + 'MIIGNAYJKoZIhvcNAQcCoIIGJTCCBiECAQExDzANBglghkgBZQMEAgEFADALBgkq\r\n' + + 'hkiG9w0BBwGgggO4MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCB\r\n' + + 'mzELMAkGA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5z\r\n' + + 'YmFjaDEVMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4x\r\n' + + 'FjAUBgNVBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBi\r\n' + + 'cm9rZW5waXBlLmRlMB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsx\r\n' + + 'CzAJBgNVBAYTAkRFMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2Jh\r\n' + + 'Y2gxFTATBgNVBAoMDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYw\r\n' + + 'FAYDVQQDDA1HZWllcmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJv\r\n' + + 'a2VucGlwZS5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4f\r\n' + + 'WevHqP1K1y/ewpMS3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck\r\n' + + '+iGdOLLFia6OSpM60tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f\r\n' + + '9XO+80eG4XYvb5ua1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9h\r\n' + + 'p/xs8mfH86mIKiD7f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55\r\n' + + 'ln2yYJUQ+adlgaYnPrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8Z\r\n' + + 'xhiIj7oBRqYcAocCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFj\r\n' + + 'PAoR4BlzKq/H3EPOqS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL\r\n' + + '2vzZQEcZn6paU/oZZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KG\r\n' + + 'UqJ9u/3/10lDqRwpsReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfW\r\n' + + 'QRVS9fS9CFwJehEXHAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvq\r\n' + + 'Bk7HjGrhcVS6kAsyZ9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cd\r\n' + + 'misfkjGCAkAwggI8AgEBMIGpMIGbMQswCQYDVQQGEwJERTESMBAGA1UECAwJRnJh\r\n' + + 'bmNvbmlhMRAwDgYDVQQHDAdBbnNiYWNoMRUwEwYDVQQKDAxTdGVmYW4gU2llZ2wx\r\n' + + 'EjAQBgNVBAsMCUdlaWVybGVpbjEWMBQGA1UEAwwNR2VpZXJsZWluIERFVjEjMCEG\r\n' + + 'CSqGSIb3DQEJARYUc3Rlc2llQGJyb2tlbnBpcGUuZGUCCQDUVBxA2DXi8zANBglg\r\n' + + 'hkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwLwYJKoZIhvcN\r\n' + + 'AQkEMSIEIC/0wwUPtS5pCJOLtE2nG0I+hr17VTeUqyxb02vkq/NjMBwGCSqGSIb3\r\n' + + 'DQEJBTEPFw01MDAxMDEwMDAwMDBaMA0GCSqGSIb3DQEBAQUABIIBAAF4/ivsVoaJ\r\n' + + 'aAR1vrq/DGfi9+SMwT6Zfk8Lssbh0NHsfXrko8oQ+01grokJqvDbVgdZOpeWt4dU\r\n' + + 'ZgWaqQnZC6kV5YbZNxMDSOP26svyiQ9UAN0q8V7oO9AHDEj2as/Zo9SIFNjgf6d4\r\n' + + 'Z4yuCgtGDtHVVqKMr9vL61v9yTsti4G9/8srhwoTS33/BNjD4icfPdNaGBSfgP+Z\r\n' + + '6m/90OGUus11leuRlpgs1hR1TU/ScfPIAAfemPigk18hox9vMMAdRs7JBGdKDDQr\r\n' + + 'c6mfUV75ZWEKFZM7y5bgX0IrolPexuMrOgeJzkzYtoMGwXA5fudHT3Nk53D3tLj3\r\n' + + 'x4KOfz69nJA=\r\n' + '-----END PKCS7-----\r\n' }; @@ -373,7 +409,7 @@ var support = require('./support'); ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256); ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']); - ASSERT.equal(p7.encryptedContent.parameter.data.length, 16); // IV + ASSERT.equal(p7.encryptedContent.parameter.data.length, 16); // IV }); it('should import indefinite length message from PEM', function() { @@ -640,13 +676,32 @@ var support = require('./support'); ASSERT.equal(pem, _pem.signedDataWithAttrs1950UTCTime); }); - // FIXME: remove skipping PhantomJS tests when possible - // skip 2049/2050 tests in PhantomJS - // likely due to https://bugs.webkit.org/show_bug.cgi?id=130123 - // can't parse dates between 2034-03-01 and 2100-02-28 - var _it = support.isPhantomJS ? it.skip : it; + it('should create PKCS#7 detached signature', function() { + var p7 = PKCS7.createSignedData(); + p7.content = UTIL.createBuffer('To be signed.', 'utf8'); + p7.addCertificate(_pem.certificate); + p7.addSigner({ + key: PKI.privateKeyFromPem(_pem.privateKey), + certificate: _pem.certificate, + digestAlgorithm: PKI.oids.sha256, + authenticatedAttributes: [{ + type: forge.pki.oids.contentType, + value: forge.pki.oids.data + }, { + type: forge.pki.oids.messageDigest + // value will be auto-populated at signing time + }, { + type: forge.pki.oids.signingTime, + // will be encoded as UTC time because it's >= 1950 + value: new Date('1950-01-01T00:00:00Z') + }] + }); + p7.sign({detached: true}); + var pem = PKCS7.messageToPem(p7); + ASSERT.equal(pem, _pem.detachedSignature); + }); - _it('should create PKCS#7 SignedData with content-type, message-digest, ' + + it('should create PKCS#7 SignedData with content-type, message-digest, ' + 'and signing-time attributes using UTCTime (2049)', function() { // verify with: // openssl smime -verify -in p7.pem -signer certificate.pem \ @@ -675,7 +730,7 @@ var support = require('./support'); ASSERT.equal(pem, _pem.signedDataWithAttrs2049UTCTime); }); - _it('should create PKCS#7 SignedData with content-type, message-digest, ' + + it('should create PKCS#7 SignedData with content-type, message-digest, ' + 'and signing-time attributes using GeneralizedTime (2050)', function() { // verify with: // openssl smime -verify -in p7.pem -signer certificate.pem \ diff --git a/tests/unit/rsa.js b/tests/unit/rsa.js index 763f2ab60..1cd9e072e 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'); @@ -127,6 +128,34 @@ var UTIL = require('../../lib/util'); }); } + // check if keygen params use deterministic algorithm + // NOTE: needs to match implementation details + function isDeterministic(isPrng, isAsync, isPurejs) { + // always needs to have a prng + if(!isPrng) { + return false; + } + if(UTIL.isNodejs) { + // Node versions >= 10.12.0 support native keyPair generation, + // which is non-deterministic + if(isAsync && !isPurejs && + typeof require('crypto').generateKeyPair === 'function') { + return false; + } + if(!isAsync && !isPurejs && + typeof require('crypto').generateKeyPairSync === 'function') { + return false; + } + } else { + // async browser code has race conditions with multiple workers + if(isAsync) { + return false; + } + } + // will run deterministic algorithm + return true; + } + it('should generate 512 bit key pair (sync)', function() { _genSync(); }); @@ -167,13 +196,19 @@ var UTIL = require('../../lib/util'); }); }); - it('should generate the same 512 bit key pair (sync+sync)', function() { + it('should generate same 512 bit key pair (prng+sync,prng+sync)', + function() { var pair1 = _genSync({samePrng: true}); var pair2 = _genSync({samePrng: true}); _pairCmp(pair1, pair2); }); - it('should generate the same 512 bit key pair (sync+purejs)', function() { + it('should generate same 512 bit key pair (prng+sync,prng+sync+purejs)', + function() { + if(!isDeterministic(true, false, false) || + !isDeterministic(true, false, true)) { + this.skip(); + } var pair1 = _genSync({samePrng: true}); // save var purejs = FORGE.options.usePureJavaScript; @@ -185,38 +220,61 @@ var UTIL = require('../../lib/util'); _pairCmp(pair1, pair2); }); - it('should generate 512 bit key pairs (sync+async)', function(done) { + it('should generate same 512 bit key pair ' + + '(prng+sync+purejs,prng+sync+purejs)', function() { + if(!isDeterministic(true, false, true) || + !isDeterministic(true, false, true)) { + this.skip(); + } + // save + var purejs = FORGE.options.usePureJavaScript; + // test pure mode + FORGE.options.usePureJavaScript = true; + var pair1 = _genSync({samePrng: true}); + var pair2 = _genSync({samePrng: true}); + // restore + FORGE.options.usePureJavaScript = purejs; + _pairCmp(pair1, pair2); + }); + + it('should generate same 512 bit key pair (prng+sync,prng+async)', + function(done) { + if(!isDeterministic(true, false, false) || + !isDeterministic(true, true, false)) { + this.skip(); + } var pair1 = _genSync({samePrng: true}); _genAsync({samePrng: true}, function(pair2) { - // check if the same on supported deterministic platforms - if(UTIL.isNodejs) { - _pairCmp(pair1, pair2); - } + _pairCmp(pair1, pair2); done(); }); }); - it('should generate 512 bit key pairs (async+sync)', function(done) { + it('should generate same 512 bit key pair (prng+async,prng+sync)', + function(done) { + if(!isDeterministic(true, true, false) || + !isDeterministic(true, false, false)) { + this.skip(); + } _genAsync({samePrng: true}, function(pair1) { var pair2 = _genSync({samePrng: true}); - // check if the same on supported deterministic platforms - if(UTIL.isNodejs) { - _pairCmp(pair1, pair2); - } + _pairCmp(pair1, pair2); done(); }); }); - it('should generate 512 bit key pairs (async+async)', function(done) { + it('should generate same 512 bit key pair (prng+async,prng+async)', + function(done) { + if(!isDeterministic(true, true, false) || + !isDeterministic(true, true, false)) { + this.skip(); + } var pair1; var pair2; // finish when both complete function _done() { if(pair1 && pair2) { - // check if the same on supported deterministic platforms - if(UTIL.isNodejs) { - _pairCmp(pair1, pair2); - } + _pairCmp(pair1, pair2); done(); } } @@ -253,7 +311,7 @@ var UTIL = require('../../lib/util'); it('should PKCS#8 encrypt and decrypt private key with ' + algorithm, function() { var privateKey = PKI.privateKeyFromPem(_pem.privateKey); var encryptedPem = PKI.encryptRsaPrivateKey( - privateKey, 'password', {algorithm: algorithm}); + privateKey, 'password', {algorithm: algorithm}); privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password'); ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey); }); @@ -269,10 +327,10 @@ var UTIL = require('../../lib/util'); ' encryption and ' + prfAlgorithm + ' PRF', function() { var privateKey = PKI.privateKeyFromPem(_pem.privateKey); var encryptedPem = PKI.encryptRsaPrivateKey( - privateKey, 'password', { - algorithm: algorithm, - prfAlgorithm: prfAlgorithm - }); + privateKey, 'password', { + algorithm: algorithm, + prfAlgorithm: prfAlgorithm + }); privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password'); ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey); }); @@ -716,5 +774,373 @@ 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.$/); + } + + 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); + _checkBadDigestInfo(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/support.js b/tests/unit/support.js deleted file mode 100644 index 5b0a7eb8a..000000000 --- a/tests/unit/support.js +++ /dev/null @@ -1,7 +0,0 @@ -// test support - -// true if running in PhantomJS -exports.isPhantomJS = - (typeof navigator !== 'undefined' && navigator.userAgent) ? - navigator.userAgent.indexOf('PhantomJS') !== -1 : - false; diff --git a/tests/unit/tls.js b/tests/unit/tls.js index 34460a12a..45b0712a4 100644 --- a/tests/unit/tls.js +++ b/tests/unit/tls.js @@ -12,7 +12,7 @@ require('../../lib/util'); // But that link is now dead. var secret = forge.util.createBuffer().fillWithByte(0xAB, 48).getBytes(); var seed = forge.util.createBuffer().fillWithByte(0xCD, 64).getBytes(); - var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector', seed, 104); + var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector', seed, 104); var expect = 'd3d4d1e349b5d515044666d51de32bab258cb521' + 'b6b053463e354832fd976754443bcf9a296519bc' + diff --git a/tests/unit/util.js b/tests/unit/util.js index 323dd78dc..7b4344397 100644 --- a/tests/unit/util.js +++ b/tests/unit/util.js @@ -5,7 +5,7 @@ var UTIL = require('../../lib/util'); // custom assertion to test array-like objects function assertArrayEqual(actual, expected) { ASSERT.equal(actual.length, expected.length); - for (var idx = 0; idx < expected.length; idx++) { + for(var idx = 0; idx < expected.length; idx++) { ASSERT.equal(actual[idx], expected[idx]); } } @@ -202,7 +202,7 @@ var UTIL = require('../../lib/util'); var b = UTIL.createBuffer(UTIL.hexToBytes('1234567887654321')); ASSERT.equal(b.getInt(32), 0x12345678); // FIXME: getInt bit shifts create signed int - ASSERT.equal(b.getInt(32), 0x87654321<<0); + ASSERT.equal(b.getInt(32), 0x87654321 << 0); ASSERT.equal(b.length(), 0); }); diff --git a/tests/unit/x509.js b/tests/unit/x509.js index 2a4c032be..474e97a89 100644 --- a/tests/unit/x509.js +++ b/tests/unit/x509.js @@ -200,6 +200,59 @@ var UTIL = require('../../lib/util'); '-----END CERTIFICATE-----\r\n' ]; + var _pem_past_2050 = { + privateKey: '-----BEGIN PRIVATE KEY-----\r\n' + + 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtQM1R5VoB8Hrr\r\n' + + 'hdL514H+oQN/FSIiHXsdoZlQqUEQ7yUBgC2c7fbq4Wm9AUHBkc/KfUGb9+ZeX6xo\r\n' + + '6L06M57QBD8nkSTStNHozlOwjnKu7DnlyW4j3ej9rBZzshLzAvWGZkx5sR1Eyz/2\r\n' + + '2Ns9PL+S4h3eiRGpTw3g3geI1wxBv3CCb/LUF5Wa4NWwIU1GP9S6d8MrOb3WGhR3\r\n' + + '64ZxajxSeX8R9WLsAo1Qx0dhsJ/UMwIibZ3hA9y1VBma2QyqQMzdY9OYC4oyE646\r\n' + + 'WfyJzHscpc6KNiVsWnYDP9+NStQlvaXMwhaPFpyVkLLB0vHho4oMXgEDTCH1iIe4\r\n' + + 'YGw01wQ/AgMBAAECggEAdx1wjHfFNEQkHr25WbDDXU9SWhMrjoz6UlsCT6SuaXgh\r\n' + + '1zBLK/OnqcEks5+jl/QqCqunahY8OnJI1S/+uX84Fwh0az2tNXjAQPFqNJ8bVgxv\r\n' + + 'mf6tTNeLEq04Gn856/4C1E6NEbWly+B5r7tUsHuNsuznYFKY4/DIN+wu/fPsJ17X\r\n' + + 'vbZiev6+OID/XCKPYWNRYiszBcmktGM5L+JLcYYPpxFYGFtw+khhUlazx0OmIuDB\r\n' + + 'AaePTtyXb1qQNKHv6wy7l+BTBKAlxoQQHMCw5qsnCr9RHed7LJr0kssUbVaseUOg\r\n' + + 'KMWtRG0Ms3ovOPWsVFC509gbGal8q+NSwyKd07SlQQKBgQDZX6bwE1H+V7r5GJwz\r\n' + + 'EGRLrMUDbbCb2qqZcSn+TCk9hsJ6jeSwz9KjZ2qONcmMl3WY6XAOdTjmJZl20wF9\r\n' + + 'GV58pZAoPhLh6tByX9RlUffXz+1KKMP0/5cxmwV8N0RBWrHcmRc6yMwJGZE7qBUh\r\n' + + 'JX/77xj9yPoDW+uOSGp+f0nZMQKBgQDMChqQF1cw7iIljzmtOhSnSEiCH7WYCJgp\r\n' + + 'um7KEGfFXxbNX07g6pojdjVFFf9uA6tPaiTnwXLc8wI8yiuwt8yPh60QTrcW/E/0\r\n' + + '+iQQGkwrFvWybbEuI++K6rOXdNMq7FEU467qN26lzHpagSEn6bXdss8L7AOcGRif\r\n' + + '0E/rSXlYbwKBgQDHFAA6vScJzmUxvyVG6ws/90IT6sClbHVzxB1WhX/7llDElvFM\r\n' + + 'MXlTJ+KBzacB+LC904VJ6Hes5+CN35/sZ3COrb7B7F+0wi4XocZO6OwYnZhPo9gb\r\n' + + 'qH1a9APpCGCdjid4xkhtEPs0llLZlQ2M5uA45ng37Xlz3Bp2m8HUilUi8QKBgQCa\r\n' + + 'RpV5F7zciWIWRipVGZJePeBdSz6SOwVan9V/QVJFQTXLiWHp3Fk5sPpsR0rAU1Pn\r\n' + + 'kxlehr2j5LZvYmoQj5jDedHYf7weTB7k23IDHu8ysYSLKjeK7K8FuZqbTUERtmdE\r\n' + + 'RTePbuRhxq9I2VRJioPxom680/KSx8L/q5GSFRcETwKBgDZhnBZ7+dIKlPAgfwPC\r\n' + + 'OCA2kKE+5UxyeAUeojNrSfHGdzF891PX+90D7sPfJi7SJU4E/UOMG/u8wUv5i2/+\r\n' + + '/nKOI6NwKB/m0FqpvxamuNJapn9RQ9bC7oz0Rj2Tho90mohwQG4DHF2uO8364Wb+\r\n' + + '9Bu1FCWmILPGcJM5iw6TJPA0\r\n' + + '-----END PRIVATE KEY-----\r\n', + certificate: '-----BEGIN CERTIFICATE-----\r\n' + + 'MIIDqjCCApKgAwIBAgIJAJvrXrdlRWTTMA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\r\n' + + 'BAYTAlVTMRAwDgYDVQQIDAdWaXJnaW5hMRMwEQYDVQQHDApCbGFja3NidXJnMQ0w\r\n' + + 'CwYDVQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtleGFtcGxlLm9y\r\n' + + 'ZzAiGA8yMDUwMDIwMTIzMDAyOVoYDzIwNTEwMjAxMjMwMDI5WjBoMQswCQYDVQQG\r\n' + + 'EwJVUzEQMA4GA1UECAwHVmlyZ2luYTETMBEGA1UEBwwKQmxhY2tzYnVyZzENMAsG\r\n' + + 'A1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDEUMBIGA1UEAwwLZXhhbXBsZS5vcmcw\r\n' + + 'ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtQM1R5VoB8HrrhdL514H+\r\n' + + 'oQN/FSIiHXsdoZlQqUEQ7yUBgC2c7fbq4Wm9AUHBkc/KfUGb9+ZeX6xo6L06M57Q\r\n' + + 'BD8nkSTStNHozlOwjnKu7DnlyW4j3ej9rBZzshLzAvWGZkx5sR1Eyz/22Ns9PL+S\r\n' + + '4h3eiRGpTw3g3geI1wxBv3CCb/LUF5Wa4NWwIU1GP9S6d8MrOb3WGhR364ZxajxS\r\n' + + 'eX8R9WLsAo1Qx0dhsJ/UMwIibZ3hA9y1VBma2QyqQMzdY9OYC4oyE646WfyJzHsc\r\n' + + 'pc6KNiVsWnYDP9+NStQlvaXMwhaPFpyVkLLB0vHho4oMXgEDTCH1iIe4YGw01wQ/\r\n' + + 'AgMBAAGjUzBRMB0GA1UdDgQWBBRxifZwjEsDjYgbajBq+e1r4krdwjAfBgNVHSME\r\n' + + 'GDAWgBRxifZwjEsDjYgbajBq+e1r4krdwjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG\r\n' + + 'SIb3DQEBCwUAA4IBAQB0V8zdJ1WebOvZNwl6WcbzNJRQePPnGp9pAbGuqpLZHvs6\r\n' + + 'geAocgmEqleGOsU9GT30MV1vtkR1IY6CWkVPeSiXS43HT8enoYCJX3AZd6ItUrQH\r\n' + + '8UonY8UqAmzsGLO+ttO5o6kEY6K0e1QUdmFkOh9Z6M9U3s3DASwrKQ/xFlHQ2mNi\r\n' + + 'h7pKaH2+XlDTrCjhO1ip0n4AwG5lgFJpJlVOZ9+Axzc146q/YZqrhXHYU152Wqo/\r\n' + + 'mFlygydsKNwWdpK5fwGBZkBR8AsZvNZaQ9Rr3Rr3y5Xz7+aPfLfWF5hW+d11ghuy\r\n' + + 'FDeZMUBehXXEJLXrirfmO2KFmy3iKrniJDDa35Lg\r\n' + + '-----END CERTIFICATE-----\r\n' + }; + describe('x509', function() { it('should convert SHA-1 based certificate to/from PEM', function() { var certificate = PKI.certificateFromPem(_pem.certificate); @@ -211,6 +264,11 @@ var UTIL = require('../../lib/util'); ASSERT.equal(PKI.certificateToPem(certificate), _pem_sha256.certificate); }); + it('should convert certificate not before < 2050 < not after to/from PEM', function() { + var certificate = PKI.certificateFromPem(_pem_past_2050.certificate); + ASSERT.equal(PKI.certificateToPem(certificate), _pem_past_2050.certificate); + }); + it('should convert SHA-512 based certificate to/from PEM', function() { var certificate = PKI.certificateFromPem(_pem_sha512.certificate); ASSERT.equal(PKI.certificateToPem(certificate), _pem_sha512.certificate); @@ -231,6 +289,11 @@ var UTIL = require('../../lib/util'); ASSERT.ok(certificate.verify(certificate)); }); + it('should verify not before < 2050 < not after self-signed certificate', function() { + var certificate = PKI.certificateFromPem(_pem_past_2050.certificate); + ASSERT.ok(certificate.verify(certificate)); + }); + it('should generate a certificate with authorityKeyIdentifier extension', function() { var keys = { privateKey: PKI.privateKeyFromPem(_pem.privateKey), @@ -360,6 +423,289 @@ var UTIL = require('../../lib/util'); }); }); + it('should generate a certificate with nsComment extension', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }]; + var dummyTestStr = 'node-forge is awesome'; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + extensions: [{ + name: 'nsComment', + comment: dummyTestStr + }], + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + // verify certificate encoding/parsing + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + + // verify nsComment extension + var index = findIndex(cert.extensions, {id: '2.16.840.1.113730.1.13'}); + ASSERT.ok(index !== -1); + var ext = cert.extensions[index]; + ASSERT.equal(ASN1.fromDer(ext.value).value, dummyTestStr); + + // verify certificate chain + var caStore = PKI.createCaStore(); + caStore.addCertificate(cert); + PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) { + ASSERT.equal(vfd, true); + ASSERT.ok(cert.verifySubjectKeyIdentifier()); + return true; + }); + }); + + it('should generate a certificate with postalCode attribute', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }, { + name: 'postalCode', + value: '24060' + }]; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + var index = findIndex(cert.subject.attributes, {type: '2.5.4.17'}); + ASSERT.ok(index !== -1); + var attribute = cert.subject.attributes[index]; + ASSERT.equal(attribute.name, 'postalCode'); + ASSERT.equal(attribute.value, '24060'); + }); + + it('should generate a certificate with businessCategory attribute', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }, { + name: 'businessCategory', + value: 'Test Organization' + }]; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + var index = findIndex(cert.subject.attributes, {type: '2.5.4.15'}); + ASSERT.ok(index !== -1); + var attribute = cert.subject.attributes[index]; + ASSERT.equal(attribute.name, 'businessCategory'); + ASSERT.equal(attribute.value, 'Test Organization'); + }); + + it('should generate a certificate with streetAddress attribute', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }, { + name: 'streetAddress', + value: 'Test Avenue' + }]; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + var index = findIndex(cert.subject.attributes, {type: '2.5.4.9'}); + ASSERT.ok(index !== -1); + var attribute = cert.subject.attributes[index]; + ASSERT.equal(attribute.name, 'streetAddress'); + ASSERT.equal(attribute.value, 'Test Avenue'); + }); + + it('should generate a certificate with jurisdictionOfIncorporationStateOrProvinceName attribute', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }, { + name: 'jurisdictionOfIncorporationStateOrProvinceName', + value: 'Delaware' + }]; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + var index = findIndex(cert.subject.attributes, {type: '1.3.6.1.4.1.311.60.2.1.2'}); + ASSERT.ok(index !== -1); + var attribute = cert.subject.attributes[index]; + ASSERT.equal(attribute.name, 'jurisdictionOfIncorporationStateOrProvinceName'); + ASSERT.equal(attribute.value, 'Delaware'); + }); + + it('should generate a certificate with jurisdictionOfIncorporationCountryName attribute', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }, { + name: 'jurisdictionOfIncorporationCountryName', + value: 'US' + }]; + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + var index = findIndex(cert.subject.attributes, {type: '1.3.6.1.4.1.311.60.2.1.3'}); + ASSERT.ok(index !== -1); + var attribute = cert.subject.attributes[index]; + ASSERT.equal(attribute.name, 'jurisdictionOfIncorporationCountryName'); + ASSERT.equal(attribute.value, 'US'); + }); + it('should generate and verify a self-signed certificate', function() { var keys = { privateKey: PKI.privateKeyFromPem(_pem.privateKey), @@ -406,6 +752,49 @@ var UTIL = require('../../lib/util'); }); }); + it('should generate a self-signed certificate after 2050', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }]; + var notBefore = new Date('2050-02-02'); + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true, + notBefore: notBefore + }); + + var pem = PKI.certificateToPem(cert); + cert = PKI.certificateFromPem(pem); + + var notAfter = new Date('2051-02-02'); + ASSERT.equal(cert.validity.notBefore.toString(), notBefore.toString()); + ASSERT.equal(cert.validity.notAfter.toString(), notAfter.toString()); + }); + it('should generate and fail to verify a self-signed certificate that is not in the CA store', function() { var keys = { privateKey: PKI.privateKeyFromPem(_pem.privateKey), @@ -636,6 +1025,143 @@ var UTIL = require('../../lib/util'); }); }); + it('should verify based on a custom specified validity date', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }]; + + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + extensions: [{ + name: 'authorityKeyIdentifier', + keyIdentifier: true, + authorityCertIssuer: true, + serialNumber: true + }], + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true + }); + var caStore = PKI.createCaStore(); + caStore.addCertificate(cert); + + var verifyDate = new Date(); + PKI.verifyCertificateChain(caStore, [cert], { + validityCheckDate: verifyDate, + verify: function(vfd, depth, chain) { + ASSERT.equal(vfd, true); + return true; + } + }); + + verifyDate = new Date(); + verifyDate.setFullYear(verifyDate.getFullYear() + 2); + PKI.verifyCertificateChain(caStore, [cert], { + validityCheckDate: verifyDate, + verify: function(vfd, depth, chain) { + ASSERT.equal(vfd, 'forge.pki.CertificateExpired'); + return true; + } + }); + + verifyDate = new Date(); + verifyDate.setFullYear(verifyDate.getFullYear() - 1); + PKI.verifyCertificateChain(caStore, [cert], { + validityCheckDate: verifyDate, + verify: function(vfd, depth, chain) { + ASSERT.equal(vfd, 'forge.pki.CertificateExpired'); + return true; + } + }); + }); + + it('should not verify the validity period if null is passed as validityCheckDate', function() { + var keys = { + privateKey: PKI.privateKeyFromPem(_pem.privateKey), + publicKey: PKI.publicKeyFromPem(_pem.publicKey) + }; + + var attrs = [{ + name: 'commonName', + value: 'example.org' + }, { + name: 'countryName', + value: 'US' + }, { + shortName: 'ST', + value: 'Virginia' + }, { + name: 'localityName', + value: 'Blacksburg' + }, { + name: 'organizationName', + value: 'Test' + }, { + shortName: 'OU', + value: 'Test' + }]; + + var pastDate = new Date(); + pastDate.setFullYear(pastDate.getFullYear() - 1); + + var cert = createCertificate({ + publicKey: keys.publicKey, + signingKey: keys.privateKey, + extensions: [{ + name: 'authorityKeyIdentifier', + keyIdentifier: true, + authorityCertIssuer: true, + serialNumber: true + }], + serialNumber: '01', + subject: attrs, + issuer: attrs, + isCA: true, + notBefore: pastDate, + notAfter: pastDate + }); + var caStore = PKI.createCaStore(); + caStore.addCertificate(cert); + + PKI.verifyCertificateChain(caStore, [cert], { + verify: function(vfd, depth, chain) { + ASSERT.equal(vfd, 'forge.pki.CertificateExpired'); + return true; + } + }); + + PKI.verifyCertificateChain(caStore, [cert], { + validityCheckDate: null, + verify: function(vfd, depth, chain) { + ASSERT.equal(vfd, true); + return true; + } + }); + }); + it('should verify certificate with sha1WithRSAEncryption signature', function() { var certPem = '-----BEGIN CERTIFICATE-----\r\n' + 'MIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\n' + @@ -680,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' + @@ -1203,14 +1793,20 @@ var UTIL = require('../../lib/util'); var issuer = options.issuer; var isCA = options.isCA; var serialNumber = options.serialNumber || '01'; + var notBefore = options.notBefore || new Date(); + var notAfter; + if(options.notAfter) { + notAfter = options.notAfter; + } else { + notAfter = new Date(notBefore); + notAfter.setFullYear(notAfter.getFullYear() + 1); + } var cert = PKI.createCertificate(); cert.publicKey = publicKey; cert.serialNumber = serialNumber; - cert.validity.notBefore = new Date(); - cert.validity.notAfter = new Date(); - cert.validity.notAfter.setFullYear( - cert.validity.notBefore.getFullYear() + 1); + cert.validity.notBefore = notBefore; + cert.validity.notAfter = notAfter; cert.setSubject(subject); cert.setIssuer(issuer); var extensions = options.extensions || []; diff --git a/tests/websockets/server-webid.js b/tests/websockets/server-webid.js index 70b46cac0..5319372bb 100644 --- a/tests/websockets/server-webid.js +++ b/tests/websockets/server-webid.js @@ -68,7 +68,7 @@ var getPublicKey = function(data, uri, callback) { //var RSA = rdf.Namespace('http://www.w3.org/ns/auth/rsa#'); var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; var CERT = 'http://www.w3.org/ns/auth/cert#'; - var RSA = 'http://www.w3.org/ns/auth/rsa#'; + var RSA = 'http://www.w3.org/ns/auth/rsa#'; var desc = RDF + 'Description'; var about = RDF + 'about'; var type = RDF + 'type'; @@ -92,9 +92,9 @@ var getPublicKey = function(data, uri, callback) { } // any other resource else if( - key in node && - typeof node[key] === 'object' && !forge.util.isArray(node[key]) && - '@' in node[key] && resource in node[key]['@']) { + key in node && + typeof node[key] === 'object' && !forge.util.isArray(node[key]) && + '@' in node[key] && resource in node[key]['@']) { rval = node[key]['@'][resource]; } @@ -115,8 +115,7 @@ var getPublicKey = function(data, uri, callback) { // normalize RDF descriptions to array if(!forge.util.isArray(result[desc])) { desc = [result[desc]]; - } - else { + } else { desc = result[desc]; } @@ -175,27 +174,27 @@ 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' + Host: url.host, + Accept: 'application/rdf+xml' }); request.addListener('response', function(response) { var body = ''; // error, return empty body if(response.statusCode >= 400) { - callback(body); + callback(body); } // follow redirect else if(response.statusCode === 302) { if(redirects > 0) { // follow redirect fetchUrl(response.headers.location, callback, --redirects); - } - else { + } else { // return empty body callback(body); } @@ -277,8 +276,7 @@ var authenticateWebId = function(c, state) { webID: url, rdf: forge.util.encode64(body) })); - } - else { + } else { // try next alt name authNext(); } @@ -426,8 +424,7 @@ var createTls = function(websocket) { // do WebID authentication try { authenticateWebId(c, state); - } - catch(ex) { + } catch(ex) { c.close(); } }, diff --git a/webpack.config.js b/webpack.config.js index 64e8578d6..df0db4d34 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,6 @@ * Copyright 2011-2016 Digital Bazaar, Inc. */ const path = require('path'); -const webpack = require('webpack'); // build multiple outputs module.exports = []; @@ -61,7 +60,7 @@ const outputs = [ //} ]; -outputs.forEach((info) => { +outputs.forEach(info => { // common to bundle and minified const common = { // each output uses the "forge" name but with different contents @@ -79,6 +78,7 @@ outputs.forEach((info) => { // plain unoptimized unminified bundle const bundle = Object.assign({}, common, { + mode: 'development', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.js', @@ -95,6 +95,7 @@ outputs.forEach((info) => { // optimized and minified bundle const minify = Object.assign({}, common, { + mode: 'production', output: { path: path.join(__dirname, 'dist'), filename: info.filenameBase + '.min.js', @@ -103,6 +104,7 @@ outputs.forEach((info) => { }, devtool: 'cheap-module-source-map', plugins: [ + /* new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { @@ -113,6 +115,7 @@ outputs.forEach((info) => { } //beautify: true }) + */ ] }); if(info.library === null) {